Amy Worrall
  1. Thoughts on Apple Watch

    17th September 2014

    I wear a Pebble. I’m part of the Smartwatch Revolution™, as it were. I find my Pebble really useful for a few things: not missing phone calls when my phone is on silent and in my bag; receiving certain notifications (SMS and Facebook Messenger, but never email) on my wrist; sometimes controlling my music. I plan to use some fitness features (mainly tracking work done at the gym) in the future. I don’t actually use many of its features, but not missing phone calls is almost worth it in itself.

    Compared to other smart watches, I love how the Pebble only requires charging once a week or less. However, there are things I dislike. It manages to partially disconnect from my phone quite often, so that phone calls still come through, but notifications don’t. That’s annoying, and it doesn’t warn you when it’s happened. Also, I wish there were an easy way to do outgoing communications on it: possibly selecting from a few pre-defined messages (maybe emoji) or some other simple interface.

    You’d think the Apple Watch suits my needs. Surely Apple will not have Bluetooth issues like Pebble. They showed off some very good outgoing communication modes (even if I think the animated emoji are really cheesy). There are things I don’t like: battery life, and the fact that the screen turns off when not in use — my Pebble is always on for a glance at the time. But I could probably live with the limitations.

    There’s a bigger problem though. John Gruber referred to Apple’s September event as “nearly flawless” (apart from the U2 segment). I disagree. The iPhone 6 and Apple Pay parts were, but the introduction to the watch was lacking. The thing it was lacking was something telling us “Here’s why you need Apple Watch”. There was a big list of things the watch could do, but nowhere did they tell us how much better our life would be for being able to do those things. I bought the Pebble (which was less than half the price of the lowest end Apple Watch) because I was interested in what the future might bring for smart watches. Do I buy the Apple Watch for the same reason, or do they have a unique selling point yet?

    I’m nervous about the more expensive editions of the watch too. If the gold watch is a four figure sum as is rumoured, how is that reconcilable with the fact that the technology will go out of date? Even aside from the issue of tying your watch to your preferred brand of phone, there will be better Apple watches out in the future. Will the first generation Apple watch even be supported by the iPhone 9? If not, what happens to your $5k investment? In other words, are Apple suggesting that expensive watches should be replaced every few years (i.e. changing what a watch is), or are they suggesting that their consumer tech device will last longer than the rest of the market (i.e. changing what a consumer tech device is)? The latter could be accomplished by making them upgradable, but that doesn’t seem very Apple-like.

    When the iPod came out, it was a device that did one thing really well. When the iPhone came out, it did three things really well. Both of those devices gained more functionality later, but their starting point fixed the tone for the product. The watch seems to do loads of things, some of them fairly well (e.g. fitness), some of them less well than dedicated devices (music, messaging, maps, photos). Is the selling point the fact that you don’t need dedicated devices, and can therefore put up with poorer usability? But then, most features require you to carry a phone, and it will always do certain things better than a watch. I doubt anyone will show photos on their watch screen, because a phone screen is just better at doing that.

    I think the biggest advantage of the Apple Watch might be third party apps. Not dedicated watch apps, but developers of iPhone apps. Since the Apple Watch will have a higher adoption rate than any other smart watch for Apple users, more apps will integrate with it. Suddenly it’ll be an additional screen that works with your existing apps, which is more valuable than (e.g.) the Pebble, which has apps of its own but most iOS apps don’t integrate with it.

    I’ll wait and see. If I get one, it’ll be a cheap model.

  2. An introduction to Cocoa Bindings

    12th September 2014

    After learning about Key Value Observing, you might be wondering if there is any way of automatically keep two properties synchronised. Cocoa Bindings, a Mac-only technology, provides this missing link, although it is not as simple as just updating one property whenever the other changes. Bindings are specifically designed to work with views and controllers in AppKit, and contain many features to make them more useful when responding to user interaction.

    Most tutorials teach you how to use bindings with Interface Builder. While this is by far the most common way to use bindings, Interface Builder can leave the impression that bindings are a kind of magic, and developers who never dive deeper and gain a full understanding of how they work can often struggle to solve certain problems, especially when trying to debug some unexpected behaviour.

    Setting up a binding

    Typically, a view is bound to a controller, with a key path that refers to a property on a model object. 

    Setting up a binding may look like this:

    	[self.textField bind:@"value"
    				toObject:self.objectController
    			 withKeyPath:@"selection.firstName"
    				 options:nil];

    We can see that there are two objects in play, each with an additional piece of information. The object being bound (usually a view object, in this case an NSTextField) is given a binding name (in this case, “value”). AppKit controllers also expose bindings, so can fulfil this role, but to simplify this introduction, I’ll keep referring to the “view” side of the binding. 

    The object it is being bound to (usually a controller object, in this case an NSObjectController) is given a key path (selection.firstName) that goes from the controller object to the relevant property on the model object. The controller object and key path together form the “model” side of the binding. 

    The astute among you will have noticed that when talking about the view, I referred to a binding name rather than a key path. This is another sign that bindings are not symmetrical. On the view side, you bind to a named binding. For any AppKit view or controller, you can find the supported list of bindings in the Cocoa Bindings Reference.

    After running the above code, the text field is bound to the model. Whenever the model object’s firstName property changes, or if the model object is switched with another model object inside the object controller, the text field will update itself. If the user makes any changes to the text field, once she has committed editing (by pressing Return, switching focus to another text field, etc), the model’s firstName property will be updated.

    Obligations of the model

    The model has to support Key Value Observing (KVO), which in turn requires that it supports Key Value Coding (KVC). 

    There are benefits if the object being bound to implements NSEditorRegistration. This is one reason why it’s a good idea to bind to controller objects rather than binding directly to the model. NSEditorRegistration lets the binding tell the controller that its content is in the process of being edited. The controller keeps track of which views are currently editing the controller’s content. If the user closes the window, for example, every controller associated with that window can tell all such views to immediately commit their pending edits, and thus the user will not lose any data.

    Apple supply some generic controller objects (NSObjectController, NSArrayController, NSTreeController) that can be used to wrap your model objects, providing the editor registration functionality. Using a controller also has the advantage that the bindings system isn’t directly observing your model object — so if you replace your model object with a new one (such as in a detail view where the user has changed the record that is being inspected), you can just replace the model object inside the controller, KVO notices and the binding updates.

    AppKit’s controller objects can also return placeholder values if they have no model object, or (for NSArrayController and NSTreeController) if multiple objects are selected.

    Obligations of the view

    In bindings, the view does all the work. It is responsible for observing the model, pushing its own changes back to the model (using Key Value Coding) at an appropriate point, and keeping track of all the information about the binding. If you’re binding to Apple-supplied views (or Apple-supplied controllers that expose bindings) all this is taken care of, but it becomes important if you are creating your own bindable views. Luckily, NSObject has some tricks to help you.

    NSObject implements the bind:toObject:withKeyPath:options: method, allowing you to bind any two arbitrary object, but with the following caveats:

    1. The binding is unidirectional: the model will update the view, but not vice versa.
    2. NSObject assumes that the binding name is the same as a key path on the view object. 

    In order to support full bi-directional bindings on a custom view, you have to do some extra work. Apple’s recommended solution is to manually implement bind:toObject:withKeyPath:options:, doing the following:

    • Determine which binding is being set.
    • Record what object the view is being bound to, using what keypath and with what options. Keep references to all these things, as they’ll be needed later.
    • Register as an observer of the keypath of the object to which it is bound so that it receives notification of changes.

    When storing references to the model object, make sure to use a weak reference. Before weak references were added to Objective-C, views had to retain their bound model object (in order to avoid a crash trying to access a released object). This led to a high likelihood of retain cycles, and workarounds such as unbinding all the UI before closing the window were common.

    Then, in the KVO callback method observeValueForKeyPath:ofObject:change:context:, work out which binding has caused the update (for example, by using the context pointer), and update your view’s internal state based on this new information.

    To handle changes going from the view to the model, it’s important to consider when they should take place. It may be more appropriate to send the changes when your view loses focus, rather than sending every change as it happens. (Some of AppKit’s built in bindings have an option to choose when changes are sent. Look at NSContinuouslyUpdatesValueBindingOption on NSTextField’s “value” binding, for example. Your custom view could also allow such an option.)

    At the appropriate point, simply use KVC to update the model: use setValue:forKey: using the object and keypath that you stored when the binding was registered. Be aware that your view may immediately receive a KVO update message for a change to the model when you do this: be prepared to handle it gracefully. It is unlikely you’ll get into an infinite loop at this point, since in the absence of a user-initiated action your view won’t be sending information back down the binding, but it’s important to be aware that this may happen.

    Supporting NSEditor/NSEditorRegistration will require work on your part as well. Your view will need to implement the NSEditor protocol. In addition, when your view gains focus, it will need to introspect the bound object for each of its bindings, and check whether that object implements the NSEditorRegistration protocol. If it does, then send an objectDidBeginEditing: message to it. When your view loses focus, do the same but with an objectDidEndEditing: message.

    Building atop NSObject’s bind:toObject:withKeyPath:options: implementation

    When implementing bindings support on a custom view, an alternative approach (see here) is to use NSObject’s default functionality instead of overriding the bind:toObject:withKeyPath:options: method. As mentioned above, this provides a unidirectional binding. But NSObject kindly stores enough information for your view to use to send data in the other direction, and it’s only a little extra work to take advantage of it.

    When using this technique, you are still limited to bindings with the same name as a key on your view. This is not typically a large problem, but it can limit the flexibility slightly. Additionally, since the model-to-view direction of the binding is outside your direct control, you are limited to whichever options NSObject supports. Luckily, NSValueTransformerBindingOption (one of the most common options) is supported.

    At the point where your view desires to propagate data through the binding to the model, you need to introspect the bindings that are currently set up on your view:

    NSDictionary* info = [self infoForBinding:@"value"];

    Inside this dictionary, you can use NSObservedObjectKey, NSObservedKeyPathKey and NSOptionsKey to obtain the object that your view is bound to, the key path on that object, and the options that were supplied when creating the binding. Simply call setValue:forKeyPath: on the observed object, using the key path from NSObservedKeyPathKey, and setting the value that your view wishes to propagate.

    If you want to support any options when propagating your view’s value to the model, you’ll have to implement the functionality manually. For example, if you want to support NSValueTransformerBindingOption, you’ll need to check if a value transformer was supplied (i.e. whether NSValueTransformerBindingOption is present in the NSOptionsKey dictionary). If so, ask the value transformer for reverseTransformedValue:, and use the result when sending data across the binding using setValue:forKeyPath:.

    Supporting NSEditor/NSEditorRegistration will still require manual coding, as described in the previous section.

    Unbinding

    If you implemented a bindable view from scratch, by overriding bind:toObject:withKeyPath:options:, you should also override unbind:, using this method to unregister from any KVO notifications and to remove the references to the object/keypath/options that you stored.

    You should also do this when your view is deallocated.

    If you are relying on NSObject’s bind:toObject:withKeyPath:options: implementation, then the framework will take care of unbinding in both of these situations.

    Exposing Bindings

    When reading the NSKeyValueBindingCreation Protocol Reference, you might come across the exposeBinding: method (and its counterpart, exposedBindings), and assume that you have to expose a binding before you can use it. This is not in fact the case: this method is a legacy from an older version of Interface Builder. Calling this method when running inside an Interface Builder plugin allowed your bindings to show up in Interface Builder’s inspector for your custom view.

    The current version of Interface Builder does not support plugins. Xcode 6 is bringing Live Rendering to Interface Builder, which allows developers to annotate certain properties and have them show up in Interface Builder’s inspector, but the support currently does not extend to custom bindings. If you desire such support, please file a duplicate of Radar 2281401.

    Conclusion

    This article has described the concepts involved in creating bindings in code, and creating both model and view objects that are bindable. Model objects simply require KVO support; view objects require a little more work in order to support all the features of bindings, but (given a firm understanding of KVO/KVC) there is no magic going on: bindings are merely a formalisation of how a few existing technologies can work together to synchronise your app’s model with its view.

    Many apps do not require anything more than using the bindings available built in to AppKit controls. These can be very powerful, for example for creating a master/detail interface using table views and an NSArrayController. The NSController objects also support fetching from Core Data, support editing/viewing multiple objects at once, and have a number of other advanced features. To learn more about AppKit’s bindings, look at the Cocoa Bindings Programming Guide (for an overview of how bindings work), and the Cocoa Bindings Reference (for an overview of what bindings are available for AppKit classes).

  3. Using auto-layout to calculate table cell height

    5th November 2013

    Table cell height is one of the tricky bits of UITableView. You have to calculate it manually, in advance of creating your cell. Here’s a method that uses auto-layout to help you calculate it.

    First of all, you’ll need a cell with its content laid out with auto-layout. The easiest way to make one is to use a xib file. Make a new xib file with only one object (a table cell) inside. Add whatever subviews you want to your table cell, and lay them out with auto-layout.

    image

    Now, I’m not an auto-layout master. I’ve seldom used it before. So it took a bit of trial and error to get the layout set up correctly. The crucial thing was to make sure there were no constraints limiting the height of my labels, since I want them to resize themselves to fit whatever text goes in them.

    I set up tags for my labels, so I can refer to them in code in order to populate them. I also made sure to set the labels to support multiple lines of text, by setting numberOfLines to 0 and lineBreakMode to word wrap.

    Next, in your UITableViewController subclass, you need to keep a prototype UITableViewCell:

    @property (nonatomic, strong) UITableViewCell *prototypeCell;

    In the UITableViewController subclass’s init method, I made sure to register my cell’s nib:

    self.cellNib = [UINib nibWithNibName:@"MyCustomCell" bundle:nil];
    [self.tableView registerNib:self.cellNib forCellReuseIdentifier:@"CustomCell"];

    In your heightForRowAtIndexPath method, create this cell if it doesn’t already exist, then populate it with data.

    - (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	 if (!self.prototypeCell)
    	 {
    		 self.prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];
    	 }
    	
    	[self configureCell:self.prototypeCell forIndexPath:indexPath isForOffscreenUse:YES];
    	
    	[self.prototypeCell layoutIfNeeded];
    	CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    	return size.height;
    }

    In this method, I’m creating a cell by dequeuing one from the table view, then calling a method to customise the cell. I do the same thing in cellForRowAtIndexPath:

    - (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    {
    	UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"MyCustomCell"];;
    	[self configureCell:cell forIndexPath:indexPath isForOffscreenUse:NO];
    	
    	return cell;
    }
    

    Looking back at heightForRowAtIndexPath:, what’s going on after we have a prototype cell?

    First we tell out cell to layoutIfNeeded. Then we ask it for systemLayoutSizeFittingSize:UILayoutFittingCompressedSize. This means we’re asking the layout system for the smallest size possible that will contain all the content.

    So what does this configureCell method do? Whatever you want: it’s the method that applies the data to the cell. The important thing is, for the most part it doesn’t matter to that method whether it’s configuring the prototype cell used for height calculation, or configuring an actual cell that will be put on screen. For the few occasions where it does matter, I’ve added a forOffscreenUse: flag. (The only time I used the flag is when setting up key-value observing: I never want to KVO the prototype cell.)

    There’s one more thing: if you have separator lines turned on, don’t forget to add 1 to the height you’ve derived! (Originally I described subclassing UILabel and adding 1 to the height returned: that was due to my misunderstanding the problem.)

    And that’s it: cell heights should configure themselves to fit your content.

    Note that this method isn’t that efficient. If you’re using a large table view, you’ll definitely want to implement tableView:estimatedHeightForRowAtIndexPath:. This method lets you provide a rough guess at a row height, without needing to be so accurate. You could even just return a constant number, based on the average height of your cells. If you don’t implement this method, then before it displays anything, UITableView will go through every row in your table, apply its data to the prototype cell and ask auto-layout to calculate a height, which can be very slow if there are hundreds of rows.

    For more reading, have a look at this StackOverflow question.

  4. Predicting an Apple event

    17th October 2013

    image

    It’s prediction time again: Apple have sent an invitation for their October event. I just read Nick Heer’s predictions for what we’ll see. The thing that struck me was how many of them there are. From my memory of Apple announcement, I didn’t think they’d announce so many things at once. So I went digging.

    Apple’s events usually have one headline hardware announcement: the one thing that they want you to remember afterwards. This may comprise a few products (iPhone 5S and 5C, for example), but they’ll be related products that together tell one story. Then there may be one unrelated minor announcement, an update regarding a new OS version (that has already been revealed), plus one or two software or service announcements, and some mention of new accessories. And that’s usually it, isn’t it?

    Let’s look at examples, starting with the iPhone 5 launch. The main announcement was of course the 5. The minor but unrelated hardware was the new Nano. The OS update was iOS 6, which had already been previewed at WWDC. The software/service was iTunes 11. Accessories were new earbuds and lightning adaptors. This fits the pattern.

    The iPhone 5S/5C event: the one we’ve just had. Headline was the new iPhones. OS update was iOS 7, which again we’d seen at WWDC. Service was iTunes Radio. There was no minor announcement (I guess it could be argued that the 5S was the headline and the 5C was the minor announcement). Accessories included 5C cases. This one also fits the pattern.

    The iPad mini event: the big news was the iPad mini, plus the related but less important announcement of the iPad 4. Minor but unrelated hardware? Well, we had the new thin iMacs, new retina Macbook Pro 13”, spec bumps for the Mac mini, and the Fusion Drive technology. Software-wise we got iBooks and iBooks Author updates. So this announcement doesn’t quite fit the pattern — the iPad mini was the take-home message, but the new thin iMacs were also a big deal.

    So what of the upcoming event? The headline feature would be iPads. We’ve had lots of leaks for the new full-size iPad, with its narrower bezel and smaller size, so I think that’s a cert. iPad mini is also due for an upgrade, although I couldn’t call whether it will go retina or not. The minor unrelated hardware would be Mac Pro — minor in that it’s been previewed already, so it’s not a new headliner. OS update will be Mavericks, which has also already been seen. So that’s all of Nick’s “Count on” section (apart from iOS 7 update: I doubt they’d take up any presentation time showing one, even if one does come out around then.)

    What about his sandwich, coffee or nickel sections? Well, we don’t have a software or service release yet, so iWork could go there. I reckon we’re more likely to see updated iLife though, especially with the leaked icons. Other apps: iBooks might get a mention, if the update is significant. A Find my Friends update might, mainly because it was the poster child for skeumorphism. Whether or not they’re talked about at the event, I’d expect updates for everything except maybe Remote to happen by the end of the year. (Remote, as far as I can tell, is someone’s pet project rather than something Apple is committed to.)

    Dropping iPad 2 isn’t an announcement thing — it may well happen, but they won’t mention it. Ditto dropping the iPod Classic, which I’m surprised hasn’t happened already. (I did notice one prominently on display in an Apple Store recently though, which surprised me.) Updated MacBooks, if they’re just a spec bump, could fit into this event even alongside the Mac Pro, based on the precedent set at the iPad mini event.

    The new thunderbolt display is an interesting one. All signs point to Apple attempting to build a super high resolution display: especially the new Thunderbolt 2, specifically stated as driving 4K displays. And also, if it’s not super high res, then why make a new one? But launching that, a very much pro feature and one that would grab headlines, in the same event as the consumer iPads, seems odd. I reckon if they have a new display, then both that and the Mac Pro release will get their own pro-focussed event, maybe also incorporating a Final Cut Pro update.

    I agree that the smartwatch is a “no chance”, and anything to do with TV is unlikely (much as I’d love an SDK for the Apple TV, to go with the new game controller support in iOS…).

    To conclude, it is entirely possible to get a long list of updated things into one Apple event. When making predictions, it’s important to think of which ones are the headline grabbers: if a smartwatch did come out, for example, then that would have to be the headline product and the iPads would be regulated to the “minor update” part of the presentation. But Apple do sometimes get a huge list of updates into one event, even if the audience are only intended to remember one or two of them.

  5. Slides for my iOSDevUK talk on Templateable apps

    10th September 2013

    At iOSDevUK last week, I gave a talk on making templateable apps — that is, apps with a single codebase but different content, theming or features. As promised, I’m sharing the slides:

    Download the PDF of my slides

    I’ll hopefully blog in more detail on this topic in the future (perhaps including sharing a reusable class for reading the config plists). If I haven’t done it by October, email me to remind me!

  6. Omni Frameworks part 3: saving some data

    25th August 2013

    This is a quickie, but I thought I’d write up a small stumbling block that I encountered. It probably came from my being relatively unacquainted with UIDocument, rather than an Omni-specific problem, but here goes.

    I was implementing saving some data in the app I’ve been building up over part 1 and part 2 of this series. I made a map view, and decided that I would make the app save the user’s position as they panned the map view, so that when you opened the document again, it would be where you left it. 

    After first being sure to link against MapKit, I then made a MapViewController. I gave it a mapView property and a document property, then put the following in loadView:

    - (void)loadView
    {
        MKMapView *mapView = [[MKMapView alloc] init];
        
    	self.view = mapView;
    	self.mapView = mapView;
    	mapView.delegate = self;
    }

    Then I needed to get it on the screen. In LocusDocumentViewController (my OUIDocumentViewController-conforming class), in loadView, I created my MapViewController and added it to the view hierarchy:

        self.mapViewController = [[MapViewController alloc] init];
        self.mapViewController.document = self.document;
        self.mapViewController.view.frame = self.view.bounds;
        [self addChildViewController:self.mapViewController];
        [self.view addSubview:self.mapViewController.view];
    

    Great, now we have a map on the screen when you open a document. So how do we go about saving the position? I added a property to LocusDocument (my OUIDocument subclass), of type MKCoordinateRegion, called mapRegion.

    Then, in readFromURL:error: and writeContents:toURL:forSaveOperation:originalContentsURL:error:, I needed to actually load and save the data. Here’s the code I used:

    - (BOOL)readFromURL:(NSURL *)url error:(NSError **)outError;
    {
        NSData *modelData = [NSData dataWithContentsOfURL:url];
    	
    	NSKeyedUnarchiver *archiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:modelData];
    	self.model = [archiver decodeObjectForKey:kModelKey];
        
        double lat = [archiver decodeDoubleForKey:kStopPickerRegionLat];
        double lon = [archiver decodeDoubleForKey:kStopPickerRegionLong];
        double latD = [archiver decodeDoubleForKey:kStopPickerRegionLatDelta];
        double lonD = [archiver decodeDoubleForKey:kStopPickerRegionLongDelta];
        self.mapRegion = MKCoordinateRegionMake(CLLocationCoordinate2DMake(lat, lon), MKCoordinateSpanMake(latD, lonD));
    	
    	return YES;
    }
    
    - (BOOL)writeContents:(id)contents toURL:(NSURL *)url forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL *)originalContentsURL error:(NSError **)outError;
    {
    	NSMutableData *newData = [NSMutableData new];
    	NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:newData];
    	[archiver encodeObject:self.model forKey:kModelKey];
        
        [archiver encodeDouble:self.mapRegion.center.latitude forKey:kStopPickerRegionLat];
        [archiver encodeDouble:self.mapRegion.center.longitude forKey:kStopPickerRegionLong];
        [archiver encodeDouble:self.mapRegion.span.latitudeDelta forKey:kStopPickerRegionLatDelta];
        [archiver encodeDouble:self.mapRegion.span.longitudeDelta forKey:kStopPickerRegionLongDelta];
        
    	[archiver finishEncoding];
    	
    	return [newData writeToURL:url atomically:NO];
    }
    

    The keys are all string constants that I defined at the top of the file. Essentially, to save an MKCoordinateRegion, I saved each of the four values that make it up.

    At this point, whenever the document gets loaded and saved, it should save the mapRegion property of the document, and correctly restore it. How do we set that property? Back to the MapViewController:

    I added a BOOL mapViewLoaded property, to make sure we only start saving the region once the map view has loaded. Then, in viewDidAppear, if the map view is not loaded I load the region out of the document. (I can get away with checking the latitude is not 0 because this app is targeted at users in Britain. A better design, and one which I might implement later, would be to ensure the document always has a mapRegion, by setting the default in LocusDocument’s initEmptyDocumentToBeSavedToURL:error: method.)

    - (void)viewDidAppear:(BOOL)animated
    {
        if (!self.mapViewLoaded)
        {
            if (self.document.mapRegion.center.latitude != 0)
            {
                [self.mapView setRegion:self.document.mapRegion animated:NO];
            }
            else
            {
                [self.mapView setRegion:MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(53.47719, -2.2325), 1000, 1000)];
            }
            [self performSelector:@selector(loadPins:) withObject:self afterDelay:0.1];
        }
    
        self.mapViewLoaded = YES;
    }

    Then, in mapView:regionDidChangeAnimated:, I save the region to the document object:

    - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
    {
        if (self.mapViewLoaded)
        {
            self.document.stopPickerMapRegion = mapView.region;
        }
    }
    

    That should be enough, right? It looks like we have all the pieces of the puzzle in place. At this point, I fired it up and… it didn’t work. Why not? It turned out that the saving code in LocusDocument did not seem to be getting called. 

    At this point I did some digging into how UIDocument works. It turns out that you need to inform the system that the document has unsaved changes. I did this by overriding the setter for mapRegion:

    - (void)setMapRegion:(MKCoordinateRegion)mapRegion
    {
        _MpRegion = mapRegion;
        [self updateChangeCount:UIDocumentChangeDone];
    }
    

    And that was it. Now each document in the app shows a map view, that remembers its position and zoom settings.

  7. Auto-boxing with performSelector:? Nope, but KVC works.

    20th August 2013

    While trying to debug some code I encountered, I came across this article by Marcus Zarra: Does Objective-C Perform Autoboxing on Primitives? 

    The article has been retracted. It initially stated that when calling performSelector:withObject: on a method that took a primitive argument, you could pass in an NSNumber and Cocoa would unbox it for you. However, it turns out that the trick does not in fact work, so the article comes with a big disclaimer at the top.

    If you’re ever in that situation, where you need to call a method that takes a primitive argument indirectly, what should you do? There are a number of ways, such as using NSInvocation, or working with the method’s IMP directly, both of which I may talk about further in another article. There’s one more way though: humble Key Value Coding.

    It turns out KVC does perform auto-unboxing for you. So if the method you are trying to call meets the criteria for a KVC method (e.g. it is in the form setSomething:,), you can do this:

    - (void)start;
    {
        [self setValue:@(1415) forKey:@"aThingy"];
    }
    
    - (void)setAThingy:(int)anInt;
    {
        NSLog(@"An int: %d", anInt);
    }

    This will work with some other primitive types too, including BOOLs, floats, and even structs wrapped in an NSValue. The complete list is found in Apple’s Key Value Coding documentation.

  8. Threads — an idea for an App.net client

    19th August 2013

    For those of you who don’t know, App.net is like Twitter but for money. No, wait, that’s not how we’re supposed to introduce it — it does other stuff too, like file hosting and chat rooms. And there are free accounts now, so it’s not only a paid service. Hmm, let’s try that again…

    App.net is a service where users can post short textual messages, which will show up on the stream of any user who follows them. A message can have some annotations — either standard ones, like location or the app they posted with, or custom ones defined by an app author. Messages can also be posted to channels, which are like private rooms for particular participants. So even if the starting point was something like Twitter, App.net is more extensible by developers and can do more things.

    One feature I like about App.net is how well it handles replies. On Twitter, a post can only count as a reply if it contains the @username of the person whose post you are replying to. Not so in App.net — you can even reply to a post without mentioning any usernames, and the service will track the whole conversation in order so that users can read it from the beginning.

    I’ve had a design in mind for an App.net client that I’ve been calling Threads. It’s based on web forums, so I guess it could be a web app. I’m posting about it here because in all seriousness, I’m not going to have the time to write it in the near future, so if someone else wants to take this idea and run with it, be my guest.

    The main view of the app shows your feed, but organised by thread. That is, it lists all the posts in your feed that are not replies to another post. They’re listed in the order of their latest reply (most recent at the top), so if someone replies to a conversation, the opening post for that conversation jumps to the top of the list. The number of replies in the conversation are prominently shown next to the post, as is the username of the most recent replier.

    Click on a post, and you get to view the thread. It shows you all the posts in the thread, in chronological order, oldest at the top, just like a web forum. I’d also imagine it would remember which posts in the thread you had already read, and make their background colour look faded or something, so when you come back to a thread later you can find your place.

    For each post in the thread, if it’s in reply to the post immediately above it, that’s fine and not confusing. But what if it’s in reply to a post from further back? In that situation, the parent post is quoted inline, in a smaller font, like how the Quote button works on most web forums. This happens automatically.

    Messages that are posted through Threads can have a bit of extra metadata. For example, if you start a new thread (i.e. make a post that is not a reply), it’ll let you type a subject as well as the body text. This subject will be saved in a private annotation, so that Threads (and any other client that cares to) can display that when displaying the list of threads, rather than having to display the text of the original post.

    A reply posted through Threads can let the user choose which bit of text (if any) to quote. Remember that when displaying normal messages, it quotes no text if the reply is to the last message in the thread, or it quotes the full text if it’s a reply to an earlier message? Well, if you reply through Threads, you can override it in either direction, or even quote a subset of the message. Again, the quote text is stored in an annotation.

    A user would be able to flag favourite threads in order to follow them, and there would be a page showing all the flagged threads. Another would list all the threads you posted in. Any settings, such as flagged threads, would be saved in your ADN account.

    When viewing the list of threads, you could choose to view just threads started by people you follow, or all threads participated in by people you follow (even if started by someone else). 

    That’s about it. I have a few more ideas, but this is the general gist of it. Again, if anyone wants to make this, feel free (just let me know!).

    Update: I’ve just been clued in to TreeView, which does a great job at displaying a thread. (It displays it hierarchically, rather than chronologically with quoting like I described, but that’s still not a bad way to view them.) If only they’d add the thread list page!

  9. Omni Frameworks Part 2: Using the Document Picker

    26th July 2013

    Here’s part two of a series of posts chronicling my experiences using the Omni Frameworks. As before, I’d like to add the disclaimer that I don’t know if what I’ve done is best practice: the Omni Frameworks are not documented and I’m figuring things out as I go along.

    Human readable Copyright

    Here’s one tip for you: the Omni Frameworks seem to parse the copyright string in Info.plist. I got a few crashes where an assert failed. (Strangely this didn’t happen every time.) Make sure you have a human readable copyright string. This one worked for me:

    image

    Document types

    We’re going to be building a document picker. This lets your users pick a document to edit, much like in Apple’s iWork apps. So we’re going to need to declare some document formats that the app supports. These settings are also in the Info tab. Here’s mine:

    image

    First I defined a document type. I gave it a name, and a UTI. Now, this UTI was for a custom document, so I made it up. If you wanted to support a standard document type like RTF or PDF, you’d need to use the UTI for that type.

    I added the CFBundleTypeRole and LSHandlerRank keys because Omni’s sample had them in. I’m not sure what they’re for.

    Next I had to declare the UTI. I put it in Exported UTIs because it’s one I made up. If it was an existing UTI, I’d put this information in Imported UTIs instead.

    I added the same Description and Identifier. Then I made a dictionary key, UTTypeTagSpecification, under the additional properties section. In it I put public.mime-type (which again I made up), public.filename-extension (which is obviously the desired filename extension), and com.apple.ostype, which is a HFS Type Code. Type codes are four character strings that represented file types on the old MacOS. Use upper and lower case letters — ones that are all lower case are reserved by Apple. Again, I made this one up.

    Creating new documents

    We need to do a couple of main things to get the document picker to work. We’ll need a document class, to represent the file on disk. We’ll need a view controller that’ll handle displaying that document. And we’ll need some modifications to the App Delegate to tell it what types of document to create.

    Start by making the two classes I mentioned. I made LocusDocument (inherits from OUIDocument), and LocusDocumentViewController (inherits from OUIViewController and conforms to OUIDocumentViewController).

    Now, in the App Delegate, we can tell it what to create:

    - (Class)documentClassForURL:(NSURL *)url;
    {
        return [LocusDocument class];
    }
    
    - (NSString *)documentStoreDocumentTypeForNewFiles:(OFSDocumentStore *)store;
    {
        return @"com.locusapp.locus";
    }

    In the LocusDocument class, we then need to tell it how to make its view controller:

    - (UIViewController *)makeViewController;
    {
        return [[LocusDocumentViewController alloc] init];
    }
    

    There are a couple of other methods to implement here. It needs to know how to read and write its contents to a file. For now, I’ll put stub methods in:

    - (BOOL)readFromURL:(NSURL *)url error:(NSError **)outError;
    {
        return YES;
    }
    
    - (BOOL)writeContents:(id)contents toURL:(NSURL *)url forSaveOperation:(UIDocumentSaveOperation)saveOperation originalContentsURL:(NSURL *)originalContentsURL error:(NSError **)outError;
    {
        NSData *newData = [NSData data];
    	return [newData writeToURL:url atomically:NO];
    }
    

    Next, there are a couple of methods related to previews (i.e. the little thumbnails displayed in the document picker). This is code I copied out of Omni’s sample app with only minor changes: I’ve put it in a GitHub gist to save pasting lots of code here.

    One more thing to mention: if you want to set up anything when a new document is created, override this method:

    - (id)initEmptyDocumentToBeSavedToURL:(NSURL *)url error:(NSError **)outError;

    The View Controller

    Here’s what I put in the header file for LocusDocumentViewController:

    @interface LocusDocumentViewController : OUIViewController<OUIDocumentViewController>
    
    @property(nonatomic) BOOL forPreviewGeneration;
    
    @property (nonatomic, weak) LocusDocument *locusDocument;
    @property (nonatomic, strong)  UIToolbar *toolbar;
    
    @end
    

    I gave it three properties. First, a BOOL to show whether this view controller was created just in order to generate a preview thumbnail. This is needed later when we come to add a toolbar. Secondly a weak property that refers back to the document. Thirdly, a UIToolbar.

    In the code for the view controller, we firstly need to deal with the fact that the OUIDocumentViewController protocol declares a property called document. We shouldn’t synthesize it though, just implement these methods:

    - (OUIDocument *)document
    {
        return self.locusDocument;
    }
    
    - (void)setDocument:(OUIDocument *)document
    {
    	self.locusDocument = (LocusDocument*)document;
    }

    All I’m doing there is assigning to and reading from the locusDocument property instead. The point of that is so that I can access it inside the class without having to cast all the time.

    We also need to make sure the view controller uses the same undo manager as the document:

    - (NSUndoManager *)undoManager;
    {
        return [self.locusDocument undoManager];
    }
    

    Now let’s make it display something:

    - (void)loadView
    {
        [super loadView];
    	self.view.backgroundColor = [UIColor redColor];
    } 

    Finally in your App Delegate, implement:

    - (UIView *)pickerAnimationViewForTarget:(OUIDocument *)document;
    {
        return ((LocusDocumentViewController *)document.viewController).view;
    }
    

    That should be enough to get something on the screen. At this point, I would expect that you can use the document picker’s Add button to create a new document, and that tapping that document will take you to a screen that is coloured in red.

    Of course, from that screen you can’t get back again. For that you need:

    Implementing the toolbar

    Back in your App Delegate, declare the following instance variable:

    @implementation AppDelegate
    {
        NSArray *_documentToolbarItems;
    }
    

    Then add this method to the App Delegate, which is used to create a set of toolbar items:

    - (NSArray *)toolbarItemsForDocument:(OUIDocument *)document;
    {
        if (!_documentToolbarItems) {
            NSMutableArray *items = [NSMutableArray array];
            
            [items addObject:self.closeDocumentBarButtonItem];
            
            [items addObject:self.undoBarButtonItem];
            
            [items addObject:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL] ];
            
            UIBarButtonItem *omniPresenceBarButtonItem = [self.document omniPresenceBarButtonItem];
            if (omniPresenceBarButtonItem != nil)
                [items addObject:omniPresenceBarButtonItem];
        	
            [items addObject:self.documentTitleToolbarItem];
            
            [items addObject:[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:NULL] ];
            
            _documentToolbarItems = [[NSArray alloc] initWithArray:items];
        }
        
        return _documentToolbarItems;
    }
    

    Now we need to actually create and hook up a toolbar. We do this in the LocusDocumentViewController class:

    - (void)loadView
    {
        [super loadView];
    	self.view.backgroundColor = [UIColor redColor];
    	
    	self.toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.bounds), 44.0)];
    	self.toolbar.autoresizesSubviews = YES;
    	self.toolbar.barStyle = UIBarStyleBlackOpaque;
    	self.toolbar.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin;
    }
    
    - (void)viewDidLoad;
    {
        [super viewDidLoad];
        OUIWithoutAnimating(^{
            // Don't steal the toolbar items from any possibly open document
            if (!self.forPreviewGeneration) {
                self.toolbar.items = [[OUIDocumentAppController controller] toolbarItemsForDocument:self.document];
                [self.toolbar layoutIfNeeded];
            }
        });
    }
    
    - (UIToolbar *)toolbarForMainViewController;
    {
        if (!self.toolbar)
    	{
    		[self view];
    	}
        return self.toolbar;
    }
    
    - (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration;
    {
        [self _updateTitleBarButtonItemSizeUsingInterfaceOrientation:toInterfaceOrientation];
        
        [super willRotateToInterfaceOrientation:toInterfaceOrientation duration:duration];
    }
    
    - (void)willMoveToParentViewController:(UIViewController *)parent;
    {
        if (parent) {
            [self _updateTitleBarButtonItemSizeUsingInterfaceOrientation:[[UIApplication sharedApplication] statusBarOrientation]];
        }
        
        [super willMoveToParentViewController:parent];
    }
    
    - (void)_updateTitleBarButtonItemSizeUsingInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation;
    {
        AppDelegate *controller = [AppDelegate controller];
        UIBarButtonItem *titleItem = [controller documentTitleToolbarItem];
        UIView *customView = titleItem.customView;
        
        CGFloat newWidth = UIInterfaceOrientationIsPortrait(interfaceOrientation) ? 400 : 550;
    	
        customView.frame = (CGRect){
            .origin.x = customView.frame.origin.x,
            .origin.y = customView.frame.origin.y,
            .size.width = newWidth,
            .size.height = customView.frame.size.height
        };
    }
    

    These were also taken mostly from Omni’s sample code. All they’re doing is creating a toolbar when the view is loaded, and putting it into place. Whenever something like a rotation event happens, the toolbar is repositioned.

    That’s all for now. You should be able to create new documents in the picker, view those documents (which just show up as a red screen), and return from viewing a document to the picker.

    There’s a bug in here somewhere which means the document title isn’t centred in the toolbar. I’m not sure what’s happening there, so if anyone knows why, please let me know!

    image

  10. Integrating the Omni frameworks into an app

    24th June 2013

    The OmniGroup, makers of apps such as OmniGraffle, have released a lot of their code as the open source Omni Frameworks. This is great: there are things like a document picker with support for OmniPresence, a rich text editor, classes for zoomable tiled views, and many many other things too.

    Unfortunately, the Omni Frameworks are barely documented. There is a sample app, called TextEditor, but despite the existence of this app it took me quite a while to work out how to get the frameworks to compile and to present a blank document picker on screen in a sample app. Here I’ll try to document some of what I did.

    A quick disclaimer: I’m still learning about the Omni Frameworks. I don’t know if I’m doing things right or not, nor can I answer any of your questions. This isn’t meant to be a tutorial that tells you every step, so if it doesn’t work for you, you may have to do some investigation on your own.

    One thing to note is that on iOS I prefer not to use Interface Builder, but rather to set everything up through code. Omni’s sample app uses IB, so that’s one difference I had to account for.

    Setting things up

    This was my first real foray into using Xcode 4’s Workspaces. I set things up this way because that’s how Omni’s sample project was set up. So start by making a workspace for your app, with your app’s project inside it.

    I first cloned the git repository into a directory within my project directory. Then I added some of the Omni Frameworks projects as subprojects to my project:

    image

    As shown in that screenshot, I added OmniBase, OmniFoundation, OmniQuartz, OmniAppKit, OmniFileStore, OmniFileExchange, OmniUI, OmniUIDocument and OmniUnzip as subprojects of my project. I also added FixStringsFile as another project within the same workspace.

    I also dragged all the Configurations files (the .xcconfig files, from in the Configurations directory within the OmniFrameworks root) into a new group in my app’s Xcode project.

    I added the Omni libraries as target dependencies for my app’s target:

    image

    I added a new Run Shell Script build phase, to run Omni’s CopyLibraryResources script:

    image

    (note that the path to the script is the path from your Workspace file. I’ve put the OmniFrameworks in a folder called OmniGroup, which is why that is the first path component.)

    I made sure to link my app with the Omni frameworks, and any frameworks they required:

    image

    I told Xcode to use the appropriate configuration files for the different targets and build types. This is also the first time I used configuration files.

    image

    Constructing the app

    Now to put the app together. As I said, I dislike using nib files on iOS, so the first thing I did was to remove the project’s main interface filename:

    image

    Now to do enough to get a document picker on screen. (Note that we’re not going to make it do anything yet, we’re just going to make it appear.)

    Make your app delegate class inherit from OUIDocumentAppController:

    #import <UIKit/UIKit.h>
    #import <OmniUIDocument/OUIDocumentAppController.h>
    
    @interface AppDelegate : OUIDocumentAppController
    
    @property (strong, nonatomic) UIWindow *window;
    
    @end
    
    

    Then, in AppDelegate.m, let’s get a window displaying with a document picker in it:

    #import "AppDelegate.h"
    #import <OmniUIDocument/OUIDocument.h>
    #import <OmniUIDocument/OUIDocumentPicker.h>
    #import <OmniUIDocument/OUIDocumentPickerDelegate.h>
    #import <OmniUIDocument/OUIMainViewController.h>
    
    @interface AppDelegate () 
    
    @end
    
    @implementation AppDelegate<OUIDocumentPickerDelegate>
    
    
    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        
    	self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    	[self.window makeKeyAndVisible];
    	
    	self.documentPicker = [[OUIDocumentPicker alloc] init];
    	self.documentPicker.delegate = self;
    	
    	self.mainViewController = [[OUIMainViewController alloc] init];
    	
    	self.window.rootViewController = self.mainViewController;
    	
    	return [super application:application didFinishLaunchingWithOptions:launchOptions];
    }
    @end

    Then build and run. You should get something like this:

    image

    That’s all for now. I haven’t explored how to use any of the Omni classes yet. I may post more once I do.