CMGResearch Ltd

Blogging about iOS and server side dev

Mutiple Devices and User Defaults

The new version of Sudoku Grab is going to be a universal app (iPhone and iPad) so it’s become even more important that user data sync across all the devices an user owns.

The types of data that we need to sync are: settings (colours, fonts etc..) and puzzle data. In this post I’m going to talk about settings and persisting settings in the cloud.

The mechanism that Apple recommends for doing this is the NSUbiquitousKeyValueStore and there is some pretty decent documentation on how to get it up and running in the Storing Preferences in iCloud document.

There’s a couple of thing to be aware of – the Key-Value data store is intended for small amounts of data:

The total space available in your app’s iCloud key-value storage is 1 MB per user. The maximum number of keys you can specify is 1024, and the size limit for each value associated with a key is 1 MB. For example, if you store a single large value of exactly 1 MB for a single key, that fully consumes your quota for a given user of your app. If you store 1 KB of data for each key, you can use 1,000 key-value pairs.

And:

The maximum length for a key string is 64 bytes using UTF8 encoding. The data size of your cumulative key strings does not count against your 1 MB total quota for iCloud key-value storage; rather, your key strings (which at maximum consume 64 KB) count against a user’s total iCloud allotment.

If you think you are going to be storing more than that amount of data then the key-value store is not for you and it’s probably worth asking yourself if you are really storing user defaults or some other kind of data.

Setting up for iCloud key-value store

The first thing to do is to enable your app for cloud synching. There seem to be too places where this needs to be done and I’m not sure if the xcode step also does the dev portal step – so I’d recommend doing both…

When you create your app Id you’ll need to switch on the iCloud App Services option – if you are updating an existing app then enabled the iCloud App Services and then I’d suggest regenerating your provisioning profile.

Setup App ID for iCloud

Now create/open your app select your target and modify the Capabilities section enabling iCloud and make sure you tick the “Use key-value store” checkbox. Setup project for iCloud

Getting values from iCloud

You’re now ready to go! To use your iCloud settings you’ll need to get hold of the default NSUbiquitousKeyValueStore (this code is take from the Preferences and Settings Programming Guide).

1
2
3
4
5
6
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
          selector:@selector(updateKVStoreItems:)
          name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
          object:store];
[store synchronize];

This code gets hold of the default store and registers us to get updates when something changes in the store (e.g. another device modifies the values). We then synchronize the store with the cloud.

You should run this code as soon as possible when your app launches so that you get any changes immediately – you respond to these changes in the updateKVStoreItems notification:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
- (void)updateKVStoreItems:(NSNotification*)notification {
   // Get the list of keys that changed.
   NSDictionary* userInfo = [notification userInfo];
   NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];

   // If a reason could not be determined, do not update anything.
   if (!reasonForChange)
      return;

   // Update only for changes from the server.
   NSInteger reason = [reasonForChange integerValue];
   if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
         (reason == NSUbiquitousKeyValueStoreInitialSyncChange)) {
      // If something is changing externally, get the changes
      NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
      // do whatever you need to do when settings change
   }
}

There are three possible reasons for receiving a change notification:

  • NSUbiquitousKeyValueStoreServerChange – another device updated the values in iCloud.
  • NSUbiquitousKeyValueStoreInitialSyncChange – slightly more complicated, only happens under these circumstances:
    1. You start the app and call synchronize
    2. Before iOS has chance to pull down the latest values from iCloud you make some changes.
    3. iOS gets the changes from iCloud
  • NSUbiquitousKeyValueStoreQuotaViolationChange – you’re using it wrong and trying to store too much data.
  • NSUbiquitousKeyValueStoreAccountChange – the user has changed to a difference iCloud account. What you should probably do is clear down the user defaults to their default settings and then copy accross any new values from the cloud.

Storing values in iCloud

You can treat the NSUbiquitousKeyValueStore just as you would NSUserDefaults:

1
2
[[NSUbiquitousKeyValueStore defaultStore] setString:@"Mr Blobby" forKey:@"name"];
[[NSUbiquitousKeyValueStore defaultStore] synchronize];

As with NSUserDefaults it’s not neccessary to call synchronize every time you make a change (but calling synchronize will cause any changes to be sent to iCloud immediately).

Off line or No iCloud account

From my expeiments on my test devices if you are offline or if you have no iCloud account then values are still persisted – so it seems like these edge cases are not something to worry about.

Demo Project

I’ve put a very simple demo project up on github. Make sure you modifiy the app id to one that you have created on your own account and modified the code signing and provisioning profile settings.

Updating Sudoku Grab

I’ve got a little bit of spare time on my hands at the moment and have started to go back through the apps I’ve built and think about updating them. A big one on my list is Sudoku Grab – the first app I ever wrote, published back in 2009!

The first step in this was to actually get the code building with the latest version of Xcode. After a bit of a cull of some very old) libraries it actually built and ran!

The next step was to upgrade to ARC – this was relatively painless except for some usages of NSInvocation which I’d used for building some dynamic menus.

NSInvocation doesn’t seem to be recommended anymore, blocks are the new way of doing the kind of thing I was doing, so I was resigned to rewriting this bit of code until I realised that the only place it was being used was in the grab from camera menu. Since I’m going to be reworking the UI anyway I decided to leave in place until later.

I’m planning on simplifying the menu system quite a bit – my current plan is to replace the front screen with it’s large icons with a simpler menu structure which will combine several levels into one menu.

My current plan is to do that first and get it working nicely with iOS7 and 4 inch display sizes. And then go through each of the old screens making sure they work with both screen sizes and iOS7.

I think for this initial update that should be enough. Taking a brief look at the way the puzzles are persisted I think that another update could add synching over iCloud so that puzzles are available on all your devices. This does not look too hard – but I think the main thing to do first will be to get the UI modernised and updated.

One bonus of doing this update – I can definitely justify the need to get a iPhone5s now!

Grunt Liquibase Plugin

After my work yesterday I sat down and worked out how to create a proper grunt plugin. Once I realised that a node module is purely a node application with a package.json this proved to be really simple. One thing that is really great is learning the npm is not just for pulling down packages from the repository but is also great for local development.

I also borrowed quite a bit from (grunt-shell)[https://npmjs.org/package/grunt-shell] to work out the best way to call the child process to run liquibase.

You can find my new grunt plugin here: grunt-liquibase

Usage is pretty straightforward:

1
npm install grunt-liquibase --save-dev

Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript:

1
grunt.loadNpmTasks('grunt-liquibase');

And add a section named liquibase to the data object passed into grunt.initConfig().

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
grunt.initConfig({
  liquibase : {
      update: {
          options: {
              username : 'DB_USERNAME',
              password : 'DB_PASSWORD',
              url : 'jdbc:postgresql://DB_HOST:DB_PORT/DB_NAME'
          },
          command: 'update'
      },
      version : {
          command: 'version'
      }
  },
});

You can now run grunt liquibase:update and your database will be updated!

Full detail of the plugin can be found here: grunt-liquibase or at the git project page here.

Grunt and Liquibase

I’ve been playing around with node.js quite a lot recently and learning about the tools ecosystem. One tool that seems to be getting a lot of attention recently is Grunt – which is billed as “The JavaScript Task Runner”. This seems to be the tool that people are suggesting should be used for automating node.js builds and other tasks.

In the little project that I’ve been playing around with I want to have a database. One of the things that we’ve found really useful developing Vollow.me has been liquibase. Liquibase has proved invaluable as we’ve made modifications and updates to our database schema and we’ve integrated it into our continuous build and deployment system.

This experience makes me want to integrate liquibase into my node project and to do that I think I need to get it working with grunt.

There currently isn’t a grunt plugin or a node module for calling liquibase – at some point I’ll probably get round to creating one – but for now I just want to get it up and running with grunt.

Reading through what plugins are available for grunt, the simplest integration I can see is to use grunt-shell to call out to liquibase jar and run my changelog file.

First off we need grunt-shell installed:

1
npm install --save-dev grunt-shell

And now we can create a Gruntfile with a task definition to call liquibase:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
module.exports = function(grunt) {
  grunt.initConfig({
          shell: {
          liquibase: {
              options: {
                  stdout: true,
                  stderr : true
              },
              command: 'java -jar liquibase.jar ' +
                       '--changeLogFile changelog.xml '+
                       '--username DB_USERNAME ' +
                       '--password DB_PASSWORD ' +
                       '--url jdbc:postgresql://DB_HOST:DB_PORT/DB_NAME ' +
                       '--driver org.postgresql.Driver '+
                       '--classpath postgresql-9.3-1100.jdbc41.jar ' +
                       'update'
          }
      }
  });
  grunt.loadNpmTasks('grunt-shell');
};

I’ve downloaded the liquibase.jar and postgresql jdbc driver already.

Now we can run grunt and apply our changeset to our database:

1
2
3
4
5
$ grunt shell:liquibase
Running "shell:liquibase" (shell) task
Liquibase Update Successful

Done, without errors.

Next step will be to try and package up liquibase in a proper grunt task. We should also probably be reading our database settings from somewhere as well (I’ve hard coded mine for now). But that’s a job for tomorrow…

iOScon 2014

I’m helping to organise a conference with SkillsMatter – iOScon 2014.

Early bird tickets are on sale for just £95!

https://skillsmatter.com/conferences/1984-ios-exchange-2014

“Are you an iOS developer or designer who wants to keep ahead of the curve and hear from some of the best minds in the field? Interested in meeting other developers and exploring new techniques and technologies? Keen to collaborate and create something new from scratch at a one-off iOS hacking weekend? Then join us at the first ever iOScon!”

Welcome to Jekyll!

I’ve moved the blog over to github pages and jekyll.

Reading Barcodes in iOS7

One new feature in iOS7 which didn’t get announced anywhere (as far as I can tell anyway) if the ability to read barcodes.

There’s a new output for AVCapture called AVCaptureMetadataOutput – this supports a large number of supported formats including 1D and 2D barcodes.

Getting it up and running is pretty simple. Do the normal code for capturing output from the camera and then for the output do the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]
                                           init];
[self.session addOutput:output];

// see what types are supported (do this after adding otherwise the output reports nothing supported
NSSet *potentialDataTypes = [NSSet setWithArray:@[
                  AVMetadataObjectTypeAztecCode,
                  AVMetadataObjectTypeCode128Code,
                  AVMetadataObjectTypeCode39Code,
                  AVMetadataObjectTypeCode39Mod43Code,
                  AVMetadataObjectTypeCode93Code,
                  AVMetadataObjectTypeEAN13Code,
                  AVMetadataObjectTypeEAN8Code,
                  AVMetadataObjectTypePDF417Code,
                  AVMetadataObjectTypeQRCode,
                  AVMetadataObjectTypeUPCECode]];

NSMutableArray *supportedMetaDataTypes =
                            [NSMutableArray array];
for(NSString *availableMetadataObject in
                  output.availableMetadataObjectTypes) {
    if([potentialDataTypes
             containsObject:availableMetadataObject]) {
        [supportedMetaDataTypes
                addObject:availableMetadataObject];
    }
}

[output setMetadataObjectTypes:supportedMetaDataTypes];

// Get called back everytime something is recognised
[output setMetadataObjectsDelegate:self
                             queue:dispatch_get_main_queue()];

You’ll then get called back on this method with array of AVMetadataMachineReadableCodeObject

1
2
3
4
5
6
7
- (void)   captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
          fromConnection:(AVCaptureConnection *)connection {
    for(AVMetadataMachineReadableCodeObject *recognizedObject in metadataObjects) {
        NSLog(@"%@", recognizedObject.stringValue);
    }
}

I’ve created a quick demo project available here: https://github.com/cgreening/BarCodeExample.

Sorry for the Long Silence…

Apologies for the absence of any interesting posts. I’ve recently been working for a startup building this - www.vollow.me - this has involved a lot of server side work, so I’ve been brushing off my old java and database skills.


Now working on the app to accomany the website, so expect some more interesting iPhone posts!

Cropping the Results From UIImagePickerController

I recently had to write some code to use the UIImagePickerControllerCropRect when picking or taking a photo. Looking around the web there were some pretty crazy coding examples that seemed to be unnecessarily complicated so I knocked up my own quick solution.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// ger the original image along with it's size
UIImage *image = [info objectForKey:UIImagePickerControllerOriginalImage];
CGSize size = image.size;

// crop the crop rect that the user selected
CGRect cropRect = [[info objectForKey:UIImagePickerControllerCropRect]
                  CGRectValue];

// create a graphics context of the correct size    
UIGraphicsBeginImageContext(cropRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();

// correct for image orientation    
UIImageOrientation orientation = [image imageOrientation];
if(orientation == UIImageOrientationUp) {
  CGContextTranslateCTM(context, 0, size.height);
  CGContextScaleCTM(context, 1, -1);
  cropRect = CGRectMake(cropRect.origin.x,
                        -cropRect.origin.y,
                        cropRect.size.width,
                        cropRect.size.height);
} else if(orientation == UIImageOrientationRight) {
  CGContextScaleCTM(context, 1.0, -1.0);
  CGContextRotateCTM(context, -M_PI/2);
  size = CGSizeMake(size.height, size.width);
  cropRect = CGRectMake(cropRect.origin.y,
                        cropRect.origin.x,
                        cropRect.size.height,
                        cropRect.size.width);
} else if(orientation == UIImageOrientationDown) {
  CGContextTranslateCTM(context, size.width, 0);
  CGContextScaleCTM(context, -1, 1);
  cropRect = CGRectMake(-cropRect.origin.x,
                        cropRect.origin.y,
                        cropRect.size.width,
                        cropRect.size.height);
}
// draw the image in the correct place
CGContextTranslateCTM(context, -cropRect.origin.x, -cropRect.origin.y);
CGContextDrawImage(context,
                   CGRectMake(0,0, size.width, size.height),
                   image.CGImage);
// and pull out the cropped image
UIImage *croppedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

GameKit and GameCenter

Had lots of fun at iOSDevUK. Excellent conference with some really interesting talks. And I even managed to give a talk! My slides along with notes can be downloaded from here. It’s all about making real time multiplayer games using GameKit and GameCenter.