Tag Archive: objective-c


There are some interesting challenges with incorporating iCloud support, especially into an existing application. One, which I’ve been working through, is that you can no longer copy in a pre-existing store for the purposes of loading data. Such a move appears to work but does not upload anything to the cloud, the cloud store will only see future changes and not have the initial data set.
The solution to this is to use NSPersistentStoreCordinator’s migratePersistentStore:toURL:options:withType:error method. However I found the documentation somewhat lacking in clarity so here is the solution that now appears to be working for me.

There are some things I wanted to guarantee, which are probably not applicable to many so I’ll document them here:

- The application has gone through a few object model changes and I wanted to support people that may have skipped versions.
- The application’s data store location has changed on one occasion, when I moved the store out of Documents and into the Library directory due to adding iTunes document sharing.

Before we look at the code we’ll have a look at a rough outline of the procedure, in handy bullet points, step is 4 is the one that took some figuring out:

  1. Run everything following this asynchronously.
  2. Determine location of any existing stores, if not default to initial data set.
  3. Check to see if application supports iCloud, e.g. is running iOS 5.
  4. Configure store options if it does support iCloud.
  5. Lock the persistent store coordinator.
  6. If there is a cloud data set already, simply add it.
  7. If there isn’t DON’T add it, the migrate function adds it.
  8. Unlock persistent store coordinator.
  9. Send messages out so that view controllers that need to can redo their fetches.

A few caveats about the following, it’s not tested aside from ensuring it does migrate data into the store. I’ve not yet verified that the data then careers across the cloud to another device but I’ll update the post as soon as I do.

The code isn’t optimal, but that should be obvious! ;) The method should be called where in your code you’d normally add the store. A lot of the framework, minus the migrations stuff, is based on some example iCloud code.
Also note that the if block with the “//Migrate old store to new AND migrate into cloud” comment is devoid of code. In that block I would need to update the old store to match the new one and THEN migrate the store as I’m using my own migrations code which also allows importing of new data and a few other tweaks, which I think I’ve previously blogged.

CLOUD_NAME,STORE_NAME are #defines. DLog is a #define macro to NSLog that is blank in production environments.

- (void)aSynchronouslyAddPersistentStore {
  NSString *cloudPath = [[self applicationLibraryDirectory] stringByAppendingPathComponent:CLOUD_NAME];
  NSString *preCloudPath = [[self applicationLibraryDirectory] stringByAppendingPathComponent:STORE_NAME];
  NSString *defaultStorePath = [[NSBundle mainBundle]
                                pathForResource:@"InitialData" ofType:@"sqlite"];
  NSString *oldStorePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:STORE_NAME];

  // do this asynchronously since if this is the first time this particular device is syncing with preexisting
  // iCloud content it may take a long long time to download
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    NSURL *storeUrl = [NSURL fileURLWithPath:cloudPath];
    NSURL *migrateUrl = nil;

    /**
     Find out which store has the data we need to migrate,
     if any.
     */
    if (![fileManager fileExistsAtPath:cloudPath]) {
      if ([fileManager fileExistsAtPath:preCloudPath]) {
        // Migrate old application data.
        migrateUrl = [NSURL fileURLWithPath:preCloudPath];
      } else if ([fileManager fileExistsAtPath:oldStorePath]) {
        // Migrate old store to new AND migrate into cloud.
      } else if ([fileManager fileExistsAtPath:defaultStorePath]) {
        // Migrate (copy) in initial recipe data set.
        migrateUrl = [NSURL fileURLWithPath:defaultStorePath];
      }
    }

    // this needs to match the entitlements and provisioning profile
    NSURL *cloudURL = nil;
    if([fileManager respondsToSelector:@selector(URLForUbiquityContainerIdentifier:)])
    {
      cloudURL = [fileManager URLForUbiquityContainerIdentifier:nil];
      NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"chefsbook_v14b3"];
      cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
      DLog(@"cloudURL : %@", cloudURL);
    }

    //  The API to turn on Core Data iCloud support here.
    NSDictionary *options = nil;
    if (cloudURL) {
      options = [NSDictionary dictionaryWithObjectsAndKeys:
                 @"com.thelostsouls.chefsbook", NSPersistentStoreUbiquitousContentNameKey,
                 cloudURL, NSPersistentStoreUbiquitousContentURLKey,
                 [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                 [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption
                 ,nil];
    } 

    NSError *error = nil;
    [persistentStoreCoordinator_ lock];
    DLog(@"Persistent Store ****LOCKED****");

    /**
     If I migrate url is set don't just add, migrate!
     Otherwise proceed as planned.
     */
    if (migrateUrl) {
      NSPersistentStore *srcPS = [persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
                                                configuration:nil
                                                          URL:migrateUrl
                                                      options:nil
                                                        error:&error];
      if (![persistentStoreCoordinator_ migratePersistentStore:srcPS
                                                         toURL:storeUrl
                                                       options:options
                                                      withType:NSSQLiteStoreType
                                                         error:&error]) {
        DLog(@"Error migrating data: %@, %@", error, [error userInfo]);
        abort();
      }
    }
    else
    {
      if (![persistentStoreCoordinator_ addPersistentStoreWithType:NSSQLiteStoreType
                                                     configuration:nil
                                                               URL:storeUrl
                                                           options:options
                                                             error:&error]) {
        DLog(@"Error adding persistent store: %@, %@", error, [error userInfo]);
        abort();
      }
    }
    [persistentStoreCoordinator_ unlock];
    DLog(@"Persistent Store ****UNLOCKED****");

    // tell the UI on the main thread we finally added the store and then
    // post a custom notification to make your views do whatever they need to such as tell their
    // NSFetchedResultsController to -performFetch again now there is a real store
    dispatch_async(dispatch_get_main_queue(), ^{
      DLog(@"asynchronously added persistent store!");
      [[NSNotificationCenter defaultCenter]
       postNotificationName:AppDelegateSharedPSCAddedStore
       object:self userInfo:nil];
    });
  });

}

So there you go.

Still a worry are scenarios where iCloud support is enabled/disabled and/or the data deleted from the cloud via the settings menu. Hopefully nobody would do that and leave iCloud enabled or they’d end up with an empty application and no recovery. That’s also the reason I’m still supporting and improving the code that saves the docs out to iTunes.

Another thought just occurred to me. The following scenario needs serious consideration:

  1. User starts app with iCloud support, initial data set loaded.
  2. User proceeds to add a handful of data.
  3. User installs Chef’s Book on second device with iCloud enabled for app.
  4. App starts and copies in default data PLUS receiving iCloud data.

Will it fail, magically know it’s the same (eminently probable), or duplicate data?

Time to find out!

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Well I found out, it hangs permanently. Thanks to some helpful people on the apple forums I know why. So the above code is only partially correct. What needs to be done is to also query data on the cloud using NSMetadataQuery and examine the results, if there is cloud data you should use that and not add a persistent store in the normal way (I guess?). What I now need to find out, having successfully been able to query the cloud and find LOTS of data on there from my previous tests, is how you should add that data.

Then I’m going to rewrite the entire core data start up code from the bottom up.

Managed to implement rendering of recipes into multipage PDF documents. They look something like this:

Lasagne Recipe

One thing to note whilst writing the code was the usual inverted co-ordinates. I wanted an image in the top left, title and summary in the top right. After some internal debate I settled on a two column format for the rest. The image is easy, just use the UIImage drawInRect message once you’ve started your PDF context. As for the text the origin of your text box is the bottom left due to the inverted co-ordinates, so it is important to remember when calculating offsets that you need to set the offset.x and offset.y options to the margins of your page and adjust the size.height & size.width settings of the rect to ensure the text renders at the appropriate point. If you need to calculate the size of the rect the text occupies before rendering (not to adjust the rect for rendering but to calculate the location of the next rect) use CTFramesetterSuggestFrameSizeWithConstraints. For example when rendering the summary in the top right I use that function so I can decide whether the image or the summary is the larger and can therefore decide which value to use when calculating the available height for the columns. The following PDF has some debug code turned on that shows a red outline for the rect used to render the text and a small circle at the origin point of the rect. As you can see although the summary text is small the rect was the full height of the page.

Lasagne Recipe with Guidelines

As for rendering the core text itself I settled on a function like this:


+ (CFRange)renderTextRange:(CFRange)currentRange andFrameSetter:(CTFramesetterRef)frameSetter intoRect:(CGRect)frameRect {
	CGMutablePathRef framePath = CGPathCreateMutable();
	CGPathAddRect(framePath, NULL, frameRect);
	CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, currentRange, framePath, NULL);
	CGContextRef currentContext = UIGraphicsGetCurrentContext();
	CGContextSaveGState(currentContext);
	CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
	CGContextTranslateCTM(currentContext, 0, 792);
	CGContextScaleCTM(currentContext, 1.0, -1.0);
	CTFrameDraw(frameRef, currentContext);
	/*
	CGContextSetStrokeColorWithColor(currentContext, [[UIColor redColor] CGColor]);
	CGContextAddRect(currentContext, frameRect);
	CGContextStrokePath(currentContext);
	CGContextAddEllipseInRect(currentContext, CGRectMake(frameRect.origin.x-5, frameRect.origin.y-5, 10, 10));
	CGContextStrokePath(currentContext);
	 */
	CGContextRestoreGState(currentContext);
	CGPathRelease(framePath);
	currentRange = CTFrameGetVisibleStringRange(frameRef);
	currentRange.location += currentRange.length;
	currentRange.length = 0;
	CFRelease(frameRef);
	return currentRange;
}

The debug code is the commented out section. The Save/Restore CGState calls are for my own sanity as is the CGContextSetTextMatrix so I can ensure that drawing operations outside of this routine do not affect the drawing process. Although if you remove them keep in mind that the CGContextXXXCTM calls will need undoing somehow or each time the routine is called you’ll apply new and interesting transformations to your text.

If anyone wants me to post a full example block of code comment and I’ll update or make a new post.

As the project nears release my thoughts turn to what will be needed in the near future and how best to solve an intermittent bug that’s present now. The bug was resolving by doing something I should have done ages ago, be consistent with how I use data in table views. When developing the app the most complicated view did not use NSFetchedResultsController as it required 2 of them, one to drive each section.
Implementing this bought another problem to the fore, how to identify recipes uniquely in the DB? The obvious method is to use the objectID that core data uses but this changes until the recipe is finalised by saving it. The problem there is driving the sections from NSFetchResults whilst the recipe is being created. Each addition can change the objectID invalidating a simple predicate such as:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"recipe== %@",[recipe objectID]]];

The simplest solution is to create the recipe and immediately save it, blank, to the database then present it to the view. I have issues with that, crashes could lead to blank recipes polluting the integrity of the database and all sorts of problems. My minimum requirement is for the recipe to have a title (non unique) at least. So the solution I’ve known was needed has been implemented, UUID’s. For ease of use you want something that:

  • Adds UUIDs as part of the core db migration when the new managed object model is loaded.
  • Automatically creates and adds a UUID to a newly inserted recipe.

To help with this I created a helper class with the function:

+ (NSString*)UUIDString {
    CFUUIDRef theUUID = CFUUIDCreate(NULL);
    CFStringRef string = CFUUIDCreateString(NULL, theUUID);
    CFRelease(theUUID);
    return [NSMakeCollectable(string) autorelease];
}

To ensure the UUID is added to the core data object every time a recipe is created you can add this to the NSManagedObject subclass:

- (void)awakeFromInsert {
	[super awakeFromInsert];
	[self setValue:[KookaDIS UUIDString] forKey:@"uuid"];
}

Now for the method to update the database upon launch. Assuming you have automatic migrations turned on with the NSMigratePersistentStoresAutomaticallyOption option you need to create a NSEntityMigration subclass. I didn’t choose the best name for mine but it looks like this:

#import 

@interface AddUUIDToRecipeEMP : NSEntityMigrationPolicy {

}

@end
#import "AddUUIDToRecipeEMP.h"
#import "KookaDIS.h"

@implementation AddUUIDToRecipeEMP

- (BOOL)createDestinationInstancesForSourceInstance:(NSManagedObject*)src
									  entityMapping:(NSEntityMapping*)map
											manager:(NSMigrationManager*)mgr
											  error:(NSError**)error
{
	NSManagedObjectContext *destMOC = [mgr destinationContext];
	NSString *destEntityName = [map destinationEntityName];

	NSManagedObject *dest = [NSEntityDescription insertNewObjectForEntityForName:destEntityName
														  inManagedObjectContext:destMOC];

	NSArray *keys = [[[src entity] attributesByName] allKeys];
	NSDictionary *values = [src dictionaryWithValuesForKeys:keys];

	[dest setValuesForKeysWithDictionary:values];
	[dest setValue:[KookaDIS UUIDString] forKey:@"uuid"];
	// 511 is binary 111 111 111 or chmod 777.
	[dest setValue:[NSNumber numberWithInt:511] forKey:@"permissions"];

	[mgr associateSourceInstance:src withDestinationInstance:dest forEntityMapping:map];

	return YES;
}

@end

Ignore the permissions setValue for now, that’s for when we’ve covered the new UUID updates. So we now have a custom entity migration class. To use it create a new file in your project choosing “iOS->Resource->Mapping Model”. Within the mapping model click on the pre-made entity mapping that is relevant for you. In the far right hand pain you should see a Custom Policy field, simply type the name of the class you made in there. That’s it. Afaict this turns off any other automatic migration for that record so if you have other alterations you will need to code those changes manually too.

The above changes mean that the predicate for getting the data for the sections is consistent whether the recipe is new or not. It becomes:

[fetchRequest setPredicate:[NSPredicate predicateWithFormat:@"recipe.uuid == %@",recipe.uuid]];

The other thing I have considered is the potential inclusion of other recipes with permission and permissions for user uploads to the server. So I implemented a quick bitwise permissions system based on linux simple user/group/world permissions for read/write/execute. I’ve modified it to creater/owner/world. Owner would be someone with explicit permission or (heaven forbid) in app purchase and world would be any other download/import.

I felt that storing the data as an int is the simplest solution but to present that to the rest of the program in such a way would be anachronistic in such a modern language so I needed two routines to convert the info from an int to an NSDictionary and vice versa. Thus these two routines were born and added to the afore mentioned class:

NSString * const kKDISShareEnabled = @"KookaDocumentShareable";
NSString * const kKDISEditEnabled = @"KookaDocumentEditable";
NSString * const kKDISPrintEnabled = @"KookaDocumentPrintable";

NSString * const kKDISCreator = @"KookaDocumentCreator";
NSString * const kKDISOwner = @"KookaDocumentOwner";
NSString * const kKDISWorld = @"KookaDocumentWorld";

+ (NSDictionary *)permissionsDictionaryFromNumber:(NSNumber *)permissions {
	NSMutableDictionary *dictionary = [[[NSMutableDictionary alloc] init] autorelease];
	int value = [permissions intValue];
	int creator = value & 0x7;
	int owner = value >> 3 & 0x7;
	int world = value >> 6 & 0x7;

	NSNumber *cs = [NSNumber numberWithInt:creator & 0x1];
	NSNumber *ce = [NSNumber numberWithInt:creator >> 1 & 0x1];
	NSNumber *cp = [NSNumber numberWithInt:creator >> 2 & 0x1];
	NSNumber *os = [NSNumber numberWithInt:owner & 0x1 ];
	NSNumber *oe = [NSNumber numberWithInt:owner >> 1 & 0x1];
	NSNumber *op = [NSNumber numberWithInt:owner >> 2 & 0x1];
	NSNumber *ws = [NSNumber numberWithInt:world & 0x1];
	NSNumber *we = [NSNumber numberWithInt:world >> 1 & 0x1];
	NSNumber *wp = [NSNumber numberWithInt:world >> 2 & 0x1];

	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:cs,ce,cp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISCreator];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:os,oe,op,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISOwner];
	[dictionary setObject:[NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:ws,we,wp,nil]
													  forKeys:[NSArray arrayWithObjects:kKDISShareEnabled,kKDISEditEnabled,kKDISPrintEnabled,nil]]
				   forKey:kKDISWorld];

	return dictionary;
}

+ (NSNumber *)permissionsNumberFromDictionary:(NSDictionary *)dictionary {
	int value = 0;
	int world = [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISPrintEnabled] intValue] << 2;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISEditEnabled] intValue] << 1;
	world |= [[[dictionary valueForKey:kKDISWorld] valueForKey:kKDISShareEnabled] intValue];
	int owner = [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISPrintEnabled] intValue] << 2;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISEditEnabled] intValue] << 1;
	owner |= [[[dictionary valueForKey:kKDISOwner] valueForKey:kKDISShareEnabled] intValue];
	int creator = [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISPrintEnabled] intValue] << 2;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISEditEnabled] intValue] << 1;
	creator |= [[[dictionary valueForKey:kKDISCreator] valueForKey:kKDISShareEnabled] intValue];

	value |= world << 6 & 0x1c0;
	value |= owner << 3 & 0x38;
	value |= creator & 0x7;

	return [NSNumber numberWithInt:value];
}

I am not used to coding bitwise operations so my use of odd masks may be a very poor way of doing it but it works.

Why does the following code throw an exception and immediately sig kill on the second iteration at [e nextObject]:

		for (NSManagedObject *current in categories) {
			if ([lowercaseName isEqualToString:[current.name lowercaseString]] && category != current ) {

				// Move all the recipes.
				NSEnumerator *e = [[category valueForKey:@"recipe"] objectEnumerator];
				id object;
				while (object = [e nextObject]) {
					[(NSManagedObject *)object setCategory:current];
				}
				// Delete the category we imported.
				[moc deleteObject:category];

				// Reassign the current category to be returned instead.
				category = current;
				break;
			}
		}

Please note that in the above code, implied but not shown, category is an NSManagedObject and categories is an NSSet of NSManagedObjects. Also category will ALWAYS be present in categories.

The code above is intended to stop duplication of an NSManagedObject by finding out if one already exists and if it does moving it’s objects over to it.

The reason it crashes is because these objects (being NSManagedObjects) are very closely tied to the underlying database. After the first iteration through the while loop we have changed the underlying database, this change is immediately reflected in our NSManagedObject and therefore immediately reflected in the iterator. For example:

1) while (object = [e nextObject]) {

The iterator ‘e’ is attached to the managed object ‘category’, it contains 2 objects. The first of which is assigned to ‘object’.

2) [(NSManagedObject *)object setCategory:current];

The managed object ‘object’ is re-assigned away from the managed object ‘category’ to the managed object ‘current’. The managed object ‘category’ therefore now only has one object.

3) while (object = [e nextObject]) {

An exception is thrown. The NSEnumerator (actually an NSFastEnumerator something or other) can also be written in code as:

for (object in category)

Internally it behaves very very much like a for loop using pointer arithmetic. For this reason on the second loop the pointer is no longer valid as what was object no. 2 is now object no. 1. We have moved the object to another NSManagedObject and the change is reflected immediately. The contents of the while loop can be modified to anything that does not move the objects within ‘category’ and the crash goes away.

What do we do about this? Well the first thought is to work on a copy of the data. Thankfully we are given very simple tools to make copies such as:

NSSet *fishes = [[atlantic fish] copy];
NSMutableSet *fishes = [[pacific fish] mutableCopy];

Right? In this instance yes. Be aware however that this will not always solve your problems. It might give you a copy of the surface data but it does NOT give you a deep copy of the underlying objects. So fishes is not the same as atlantic but it will contain the same ‘fish’. If you follow my rather horrible example. I expect it’s influenced by the tropical fish tank and conversation I was having about Danio’s whilst I write this. The fixed code reads:

		for (NSManagedObject *current in categories) {
			if ([lowercaseName isEqualToString:[current.name lowercaseString]] && category != current ) {

				// Move all the recipes.
				NSSet *workingCopy = [[category item] copy];
				NSEnumerator *e = [[category valueForKey:@"item"] objectEnumerator];
				id object;
				while (object = [e workingCopy]) {
					[(NSManagedObject *)object setCategory:current];
				}
				// Delete the category we imported.
				[workingCopy release];
				[moc deleteObject:category];

				// Reassign the current category to be returned instead.
				category = current;
				break;
			}
		}

iPad rotation problems, an update.

So having created a separate test app which does not exhibit the problem I ported some of that code into my app which then starts to exhibit the issue I conclude that the problem lies within the view containing the toolbar and the table view. I’d read elsewhere that having a view controller within a view controller is a bad idea so I’m refactoring the code to:

  • Get rid of the recipe view controller.
  • Incorporate it’s toolbar and split view code into the recipedetailtableviewcontroller_ipad class.

This actually simplifies things as we have less inter class interaction messages and delegates to sling around.  However this causes one minor problem.  The iPad class inherits from a shared parent ‘common’ class which itself inherits from UITableViewController.  The problem here is that the main view is expected to be a UITableView.  In the iPad it won’t be.  So I have to recode from bottom back up.  UITableViewController is a convenience class that defines the datasource and views for us.  So we can junk it in favour of a UIViewController and code the other stuff in manually.  What this means is we need to:

  • Turn the common class into a UIViewController with the UITableView stuff built in and appropriate delegate protocols added to the .h.
  • Ensure that the iPhone class has a view defined either in code or via a nib.
  • Then the iPad class should work from the xib that was originally used.

If this doesn’t fix the view rotation issue it does give me one less view controller and view to worry about and a much simpler overall schematic for the iPad version at the expense of more manual work further down, but no less efficient as the defined UITableViewController is simply a convenience system and not voodoo, and a slightly more complicated iPad recipe view controller as it will have code in it to handle the recipe table view popover and the splitview view view view.

UPDATE:  This solved the problem.  The common class is a UIViewController with a property for a UITableView.  The iPhone version creates the table programmatically without a nib interface file:

recipeTable = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 416) style:UITableViewStyleGrouped];

The iPad version uses the same nib file as before but with the UITableViewController deleted.  Right back to emailing recipes around the place.

Following on from dynamic clocks.

It would also be nice to draw multiple clocks if one is ‘full’ it starts another. This example draws and returns a maximum of three clocks but always returns a 90×30 image:


- (UIImage *)drawClocks:(NSNumber *)time {
	UIImage *image;
	int clock1=0, clock2=0, clock3=0;
	CGRect rect = CGRectMake(0,0,90,30);
	UIGraphicsBeginImageContext(rect.size);
	CGContextRef context = UIGraphicsGetCurrentContext();

	// We wish to draw clocks right to left according to how many minutes we have, max of 180m.
	int era = [time intValue];
	if(era>59) {
		clock1 = 60;
	}
	else {
		clock1 = era;
	}
	if (era>119) {
		clock2 = 60;
	} else {
		clock2 = era>60?era%60:0;
	}
	if (era>179) {
		clock3 = 60;
	} else {
		clock3 = era>120?era%60:0;
	}

	if (clock1) {
		UIImage *image1 = [self drawTime:[NSNumber numberWithInt:clock1]];
		CGContextDrawImage(context, CGRectMake(0, 0, 30, 30), [image1 CGImage]);
	}
	if (clock2) {
		UIImage *image2 = [self drawTime:[NSNumber numberWithInt:clock2]];
		CGContextDrawImage(context, CGRectMake(30, 0, 30, 30), [image2 CGImage]);
	}
	if (clock3) {
		UIImage *image3 = [self drawTime:[NSNumber numberWithInt:clock3]];
		CGContextDrawImage(context, CGRectMake(60, 0, 30, 30), [image3 CGImage]);
	}

	image = UIGraphicsGetImageFromCurrentImageContext();

	UIGraphicsEndImageContext();

	return image;
}

Also the last post mentioned that the eventual UI view would need to do the rotate. If this is added straight after the context is obtained:


	// Save our current state (effectively inverted y for iOS)
	CGContextSaveGState(context);
	// Move image UP
	CGContextTranslateCTM(context, 0, 30);
	// Flip Y Axis
	CGContextScaleCTM(context, 1.0, -1.0);
	// Rotate 90 degrees
	CGContextRotateCTM(context, -M_PI/2);
	// Move it Back down so it's in our frame again.
	CGContextTranslateCTM(context, -30, 0);

	// It really helps if you're stuck to draw your image on a bit of paper and flip/rotate it visually.
	// E.g. I draw my 45 minute clock on a bit of paper with 0 marked on the east then tried rotations/flips
	// to get the view I wanted.  then wrote them out above!

You don’t need to include my last comment… then add this before you get the image:

	// Restore original context.
	CGContextRestoreGState(context);

No more rotation is needed. It’s left as an exercise to whomever as to why the translations above produce the correct orientation. I really did draw it on a little square of paper. :P

Drawing little clock icons dynamically.

It’s kind of nice to be able to dynamically draw little ‘clock’ time icons. This function returns a UIImage of 30×30 pixels containing such an image:

static inline float radians(double degrees) { return (degrees * M_PI) / 180; }

- (UIImage *)drawTime:(NSNumber *)time {
	UIImage *image;
	CGRect rect = CGRectMake(0,0,30,30);
	UIGraphicsBeginImageContext(rect.size);
	CGContextRef context = UIGraphicsGetCurrentContext();
	// Calculate end angle in radians according to time..... e.g. 60mins == 360o == 360*M_PI/180
	CGFloat endAngle = radians((360/60)*[time doubleValue]);

	// Set colors
	CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
	CGContextSetFillColorWithColor(context, [[UIColor grayColor] CGColor]);
	// Set Pen width.
	CGContextSetLineWidth(context, 1);

	// Draw outer circle
	CGContextStrokeEllipseInRect(context, CGRectMake(1, 1, 28, 28));

	// Set rect to top left of where we want next circle
	CGContextStrokeEllipseInRect(context, CGRectMake(4, 4, 22, 22));

	// Draw segment as grey
	CGContextMoveToPoint(context, 15.0f, 15.0f);
	CGContextBeginPath(context);
	// Draw radius out so we end up with a segment drawn and NOT a chord
	CGContextAddLineToPoint(context, 15, 5);
	// Draw arc, note documentation states final argument is 1 for Clockwise and 0 for CCW.
	// However, iOS inverts the Y axis so 0 for CCW becomes CW.  OF COURSE!
	CGContextAddArc(context, 15, 15, 10, 0, endAngle, 0);
	// Add line back to middle
	CGContextAddLineToPoint(context, 15, 15);
	CGContextClosePath(context);
	CGContextFillPath(context);

	// Gimme ma image.
	image = UIGraphicsGetImageFromCurrentImageContext();

	/* Due more core graphics weirdness note from the documentation that:
	 startAngle
	 The angle to the starting point of the arc, measured in radians from the positive x-axis.

	   Thus the resulting image will need a -90o transformation to look like a clock.  E.g. 0 is North

	   Simple way is to apply an affine rotation transformation to the view as it will rotate from the centre point.
	   The CG Rotate will perform from the origin so you would need to translate, rotate, translate seeing as
	   The CG instance has it's Y axis inversed.  What crack smoking @!$% came up with this co-ordinate
	   system?  This isn't 3D graphics people.  It's 2D  stick to one co ordinate system not 2!
	 */
	// Close context
	UIGraphicsEndImageContext();

	return image;
}

Input value must be between 0 and 60 minutes. Also the result image will need a 90degree CCW transform applied, due to co-ordinate changes between Core Graphics and main iOS you may find, depending on where one applies it, that it becomes a 90 degree CW rotation. Ho hum. For values over 60 minutes I would recommend a helper function to draw several clocks alongside one another and return that resulting image.

UPDATE:
This entry continues this tale…

So so close.

Just two bugs away, probably, from having something working in its entirety. E.g. everything else is additional features rather than core functionality.

Current bugs:

1 – Render of StepTableCell is outside the bounds of the cell for reasons unknown, it’s not self.view or self.contentview issues.
2 – Content of custom table cell does not move when entering edit mode etc. Need to animoot manually!

To-Do:

- Add author information.
- Prettify… my graphics/icons do not look ‘consistent’.
- Ponder utensil code and usefulness.
- Ratings.
- Bluetooth content distribution.
- Web based content distribution.
- Account management to allow for authorship.
- iPad code.
- Preferences code.

There are other things as well but the two ‘bugs’ I find irritating. Another thing is the consistency of the UI design approach. I’m not sure which views are better served by being modal and/or whether final views such as the view to add instructions is better served by adding steps with a + icon in the top right (like the first Category view) or via edit mode and a special table cell (a la some rows in the middle of the main detail view).

A few inconsistencies in approach…

As I read through some of the code I’ve written I notice things I’ve done twice, unnecessarily. For example a custom table cell has a property (variable/object in normal language) called category of type Category. The table view controller to which it belongs has a routine called configureCell to update the data in it’s cell. It looks something like:

- (void)configureCell:(CategoryTableCell_iPhone *)cell atIndexPath:(NSIndexPath *)indexPath {
    NSManagedObject *mO = [self.fetchedResultsController objectAtIndexPath:indexPath];
	cell.category = (Category *)mO;
	[cell.name setText:[mO valueForKey:@"name"]];
	[cell.desc setText:[mO valueForKey:@"desc"]];
	[cell.icon setImage:[mO valueForKey:@"icon"]];
	[cell setNeedsDisplay];
}

Which is great, the cell itself holds a copy of the Category object and the configureCell method updates the fields and informs the table that it should refresh. Except…. I’m duplicating work. Inside the custom table cell code is this:

- (void)setCategory:(Category *)newCategory {
	if(category != newCategory)
	{
		[category release];
		category = newCategory;
		UIFont *cat = [UIFont fontWithName:@"Georgia" size:18];
		name.font = cat;
		desc.font = cat;
		name.text = category.name;
		desc.text = category.desc;
		[icon setImage:newCategory.icon];
	}
}

Which isn’t obviously called anywhere…. except when you declare a variable or object in objective-c you define getters and setters for it. This is normally done automagically with, at various points:

@property (nonatomic,retain) Category *category;
@synthesize category;

The synthesize bit is key. Of course what synthesize does is define two routines called getCategory and setCategory. By defining our own above we have overridden the default behaviour. Thus the bulk of my code in configureCell is redundant and should just read:

- (void)configureCell:(CategoryTableCell_iPhone *)cell atIndexPath:(NSIndexPath *)indexPath {
    NSManagedObject *mO = [self.fetchedResultsController objectAtIndexPath:indexPath];
	cell.category = (Category *)mO;
	[cell setNeedsDisplay];
}

It seems that depending on the book I have been reading depends on which methods are used or indeed whether both are implemented. You could do away with the custom setter method and implement it all in configure cell. Choose one way or the other, not both. :) Harmless to be sure but inefficient.

To me it seems that documentation on populating apple Core Data based stores is sorely lacking instructions on how to populate the database at runtime from remote sources. Most people electing to populate an SQL Lite database and then just copy it in when the app is first loaded.  UGH.  I wanted to retrieve the initial database at runtime and then also use this remote store to update my locally held database at the users request; merging the data intelligently.  The problem was trying to do it as the app launches and trying to do it at various points in the App Delegate or view controller startup screwed with stuff and caused EXC_BAD_ACCESS.  Which implies something somewhere got de-allocated/allocated when it shouldn’t have.  Here is the solution, the remote store is a plist or PropertyList, which is an xml format used a lot by apple and apple software.

I’ll assume a lot of prior knowledge here unless people comment saying they want more information.

Start a new project using core data, in my case it’s a universal iPhone Navigation Bar project.  Within this project I deleted the default ‘Event’ database stuff it set up and created a database and relationships of my own.  In this example I only use one table called ‘Category’ which holds nothing but ‘name’.  A custom cell was created to display this information which also has a UIImageView field loading a local image based on the name of the category.  References in Root View Controller were changed from ‘Event’ to ‘Category’ and from ‘timestamp’ to ‘name’.   Ignoring the code to display the custom cell itself it necessitated edits to:

- (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
- (NSFetchedResultsController *)fetchedResultsController

I’m not allowing editing here so insertNewObject is gone, but if you were you’d have to edit that to so you can move away from the example data.

The problem for me was not being able to ascertain where best to shove the following code to populate the database:


	// Read in property list
	NSPropertyListFormat *format;
	NSString *errorDesc;
	NSData *plistXML = [[NSData alloc] initWithContentsOfURL:source];

	NSDictionary *data = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML
																				   mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorDesc];

	NSMutableSet *scategories = [[NSMutableSet alloc] init];

	// Get all category names into mutable array
	for(id object in data) {
		for(id key in object) {
			if([key isEqualToString:@"type"])
				[scategories addObject:(NSString *)[object objectForKey:key]];
		}
	}

	NSMutableArray *categories = [[NSMutableArray alloc] initWithSet:scategories];
	[scategories release];
	[categories sortUsingSelector:@selector(compare:)];

	// Create fetch request to get all category names
	NSFetchRequest *categoriesRequest = [[[NSFetchRequest alloc] init] autorelease];
	[categoriesRequest setEntity:[NSEntityDescription entityForName:@"Category" inManagedObjectContext:managedObjectContext_]];
	[categoriesRequest setPredicate:[NSPredicate predicateWithFormat:@"(name in %@)",categories]];

	// Sort our data store categories too like
	[categoriesRequest setSortDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc]
																	 initWithKey:@"name" ascending:YES] autorelease]]];

	// Execute fetch
	NSError *error;
	NSArray *categoriesMatchingNames = [managedObjectContext_ executeFetchRequest:categoriesRequest error:&error];

	// We need to enumarate the categoriesMatchingNames array as we must control when next to fetch an object
	NSEnumerator *enumurateDSCategories = [categoriesMatchingNames objectEnumerator];

	// Get the names to parse
	for(NSString *type in categories) {
		Category *dsCategory = (Category *)[enumurateDSCategories nextObject];
		if( (dsCategory == nil) && ![type isEqualToString:[dsCategory name]] ) {
			Category *category = (Category *)[NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:managedObjectContext_];
			[category setName:type];
		}
	}

	if(![managedObjectContext_ save:&error])
	{
	 NSLog(@"Core data failed to save new objects!");
	}

	[categories release];
	[plistXML release];

	return TRUE;

I tried placing this in:


- (NSPersistentStoreCoordinator *)persistentStoreCoordinator 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions

and viewDidLoad, viewDidAppear and others within the Root View Controller. None worked, they populated the database but then crashed. I finally decided that the best approach would be to argue that if I had no categories defined the other data isn’t there either. To begin with I settled on a modal dialogue but your approach could vary. In the App Delegate I defined two functions, one to do the actual work later on, not yet implemented except for Category, and the other to use a local xml file initially anyway:


- (BOOL)importData {
	NSString *filePath = [[NSBundle mainBundle] pathForResource:@"dl" ofType:@"xml"];
	NSURL *localfile = [[NSURL alloc] initFileURLWithPath:filePath];
	[self mergeDataFromExternalSource:localfile];
	return TRUE;
}

- (BOOL)mergeDataFromExternalSource:(NSURL *)source {
	// Read in property list
	NSPropertyListFormat *format;
	NSString *errorDesc;
	NSData *plistXML = [[NSData alloc] initWithContentsOfURL:source];

	// Get entire XML file into an NSDictionary object which is ideal for plist data
	NSDictionary *data = (NSDictionary *)[NSPropertyListSerialization propertyListFromData:plistXML
							   mutabilityOption:NSPropertyListImmutable format:format errorDescription:&errorDesc];

	// Use NSMutableSet as we are parsing the plist and we only want one instance of each category
	// A set does that for us unlike an array.
	NSMutableSet *scategories = [[NSMutableSet alloc] init];

	// Get all category names into mutable array
	for(id object in data) {
		for(id key in object) {
			if([key isEqualToString:@"type"])
				[scategories addObject:(NSString *)[object objectForKey:key]];
		}
	}

	// However we cannot sort sets so we now change it into an array anyway
	NSMutableArray *categories = [[NSMutableArray alloc] initWithSet:scategories];
	[scategories release];
	[categories sortUsingSelector:@selector(compare:)];

	// Create fetch request to get all category names
	NSFetchRequest *categoriesRequest = [[[NSFetchRequest alloc] init] autorelease];
	[categoriesRequest setEntity:[NSEntityDescription entityForName:@"Category" inManagedObjectContext:managedObjectContext_]];
	// Only fetch records that exist in our input data
	[categoriesRequest setPredicate:[NSPredicate predicateWithFormat:@"(name in %@)",categories]];

	// Sort our data store categories too so they match our input data.
	[categoriesRequest setSortDescriptors:[NSArray arrayWithObject:[[[NSSortDescriptor alloc]
												 initWithKey:@"name" ascending:YES] autorelease]]];

	// Execute fetch
	NSError *error;
	NSArray *categoriesMatchingNames = [managedObjectContext_ executeFetchRequest:categoriesRequest error:&error];

	// We need to enumarate the categoriesMatchingNames array as we must control when next to fetch an object
	// We only want to fetch the next object when it matches a category as we have n input categories but potential n-x
	// stored ones.
	NSEnumerator *enumurateDSCategories = [categoriesMatchingNames objectEnumerator];

	// Get an initial category
	Category *dsCategory = (Category *)[enumateDSCategories nextObject];
	// Get the names to parse
	for(NSString *type in categories) {
		// If the data store category is blank (we had none or run out) or they don't match add it.
		if( (dsCategory == nil) || ![type isEqualToString:[dsCategory name]] ) {
			Category *category = (Category *)[NSEntityDescription insertNewObjectForEntityForName:@"Category" inManagedObjectContext:managedObjectContext_];
			[category setName:type];
		}
		// If the data store category matches the input one get the next data store category
		if( [type isEqualToString:[dsCategory name]] ) {
			Category *dsCategory = (Category *)[enumurateDSCategories nextObject];
		}
	}

	if(![managedObjectContext_ save:&error])
	{
	 NSLog(@"Core data failed to save new objects!");
	}

	[categories release];
	[plistXML release];

	return TRUE;
}

in the Root View Controller source file (RootViewController.m) the following is modified/added:


- (void)viewDidAppear:(BOOL)animated {
	id  sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
	if ( [sectionInfo numberOfObjects] < 1) {
		UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"No Data."
														message:@"Initial data not found, loading defaults.\nHit cancel  and quit to configure a remote source in Settings.app."
													   delegate:self
											  cancelButtonTitle:@"Cancel"
											  otherButtonTitles:nil];
		[alert addButtonWithTitle:@"OK"];
		[alert show];
		[alert release];
	}

    [super viewDidAppear:animated];
}

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
	if ( buttonIndex == 1 )
		[((Delicious_LibraryAppDelegate *)[[UIApplication sharedApplication] delegate]) importData];
}

The above counts how many rows I have in my main table (which has one column, hence objectAtIndex is 0) and if it is less than 1 I assume we have no data and prompt the user. When the user hits OK the importData function is called. For now it just loads the local data but it could check the app settings and use the remote function.

Powered by WordPress | Theme: KLG based on Motion by 85ideas.