Tag Archive: iOS


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.

There are a few major differences between the two but the most glaring is that UITextView does not support styles so you cannot create them stylistically the same as a UITextField. You cannot readily apply background images depending on state and you cannot apply styles such as UITextBorderStyleRoundedRect. To get around this in the most simple fashion it appears to me that the best method, and one of the simplest, is to create your own UITextView subclass and use that. For this you need a few images in @2x and normal formats containing the background colours you want. In my case I use these, created from a simple PSD that lets me easily alter the background colour:


For the UITextField components I can then apply the setting like so:

	[name setBackground:textfieldEditBG];
	[name setDisabledBackground:textfieldPlainBG];

Having first loaded the images into those variables with methods like:

[[UIImage imageNamed:@"UITextFieldRoundedRectD"]  stretchableImageWithLeftCapWidth:12 topCapHeight:12];

This means that when I just want to view the text it appears unadorned and when I enable it for editing with:

[desc setEnabled:YES]

I get the rounded rect effect. For the UITextView subclass I use a simple hack as my Plain variants are the same as the background of the view in which they are present:

//
//  UIRoundedTextView.h
//  Cooking Companion
//
//  Created by Paul Downs on 10/12/2010.
//  Copyright 2010 The Lost Souls. All rights reserved.
//

#import 

@interface UIRoundedTextView : UITextView {
	UIImage *borderImage;
}

@property (nonatomic, retain) UIImage *borderImage;

- (void)setBorderImage:(UIImage *)background;

@end
//
//  UIRoundedTextView.m
//  Cooking Companion
//
//  Created by Paul Downs on 10/12/2010.
//  Copyright 2010 The Lost Souls. All rights reserved.
//

#import "UIRoundedTextView.h"

@implementation UIRoundedTextView

@synthesize borderImage;

- (void)setBorderImage:(UIImage *)background {
	if (borderImage != background) {
		borderImage = nil;
		[borderImage release];
		borderImage = [[background stretchableImageWithLeftCapWidth:12 topCapHeight:12] retain];
		[self setClipsToBounds:NO];
	}
}

- (void)drawRect:(CGRect)rect {
	[super drawRect:rect];
	if (borderImage && [self isEditable]) {
		UIGraphicsGetCurrentContext();
		[borderImage drawInRect:rect];
		UIGraphicsEndImageContext();
	}
}

- (void)dealloc {
	[borderImage release];
	[super dealloc];
}

@end

I can then initialise such a view to display the adorned version in edit mode with:

	[desc setBorderImage:[UIImage imageNamed:@"UITextFieldRoundedRectD"]];
	[desc setClipsToBounds:NO];

Seeing as the drawRect mode has been overridden it does not interfere with the rendering of the text above the image. You could also use a transparent image here if you do not wish to change the background colour away from that of the main view behind it.

It’s important to note that any UITextView components in IB (xib/nib) files will need to have their class set to UIRoundedTextView. If anyone reads this, feel free to use the code above in your own projects.

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…

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