Binding JSON to Data Objects using KVC

Working with JSON data in iOS is pretty straightforward. For example, suppose you're writing an application that retrieves some simple statistical information from a web service:

{"count": 3, "sum": 9.0, "average": 3.0}

You might load the data from the server, call the JSONObjectWithData:options:error: method of the NSJSONSerialization class, and retrieve the values from the deserialized dictionary as follows:

NSDictionary *dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:&error];

NSLog(@"%d", [[dictionary objectForKey:@"count"] intValue]); // prints 3
NSLog(@"%.1f", [[dictionary objectForKey:@"sum"] doubleValue]); // prints 9.0
NSLog(@"%.1f", [[dictionary objectForKey:@"average"] doubleValue]); // prints 3.0

However, sometimes it is preferable to work with more strongly typed data objects in an application. For example, you might create a Statistics class to represent the data returned by the web service:

@interface Statistics : NSObject

@property (nonatomic) int count;
@property (nonatomic) double sum;
@property (nonatomic) double average;

@end

You could then populate your object by extracting the values from the dictionary as follows:

Statistics *statistics = [[Statistics alloc] init];

statistics.count = [[dictionary objectForKey:@"count"] intValue];
statistics.sum = [[dictionary objectForKey:@"sum"] doubleValue];
statistics.average = [[dictionary objectForKey:@"average"] doubleValue];

To make things easier and avoid code duplication, you might put this code in an initializer for the Statistics class:

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
    self = [super init];

    if (self) {
        self.count = [[dictionary objectForKey:@"count"] intValue];
        self.sum = [[dictionary objectForKey:@"sum"] doubleValue];
        self.average = [[dictionary objectForKey:@"average"] doubleValue];
    }

    return self;
}

Your code for binding the JSON response to the Statistics instance would then be reduced to this:

Statistics *statistics = [[Statistics alloc] initWithDictionary:dictionary];

In either case, you could then access the data returned from the server using the strongly typed properties of your data object:

NSLog(@"%d", statistics.count); // prints 3
NSLog(@"%.1f", statistics.sum); // prints 9.0
NSLog(@"%.1f", statistics.average); // prints 3.0

This works reasonably well, and it is undoubtedly a common approach to mapping JSON content to strongly typed data objects.

However, there is an even simpler solution: key-value coding (KVC). The setValuesForKeysWithDictionary: method of NSObject can be used to automatically apply all values in a given dictionary to an object's properties. Using this approach, the initWithDictionary: method can be reduced to the following:

- (instancetype)initWithDictionary:(NSDictionary *)dictionary {
    self = [super init];

    if (self) {
        [self setValuesForKeysWithDictionary:dictionary];
    }

    return self;
}

No manual mapping of dictionary entries to property values is required. Simply declaring the properties using the appropriate name and type is sufficent. It also works in Swift:

class Statistics: NSObject {
    var count: Int = 0
    var sum: Double = 0
    var average: Double = 0

    init(dictionary: [String: AnyObject]) {
        super.init()

        setValuesForKeys(dictionary);
    }
}

Further, if you need to customize the assignment of any property names or values, you can do so easily by overriding setValue:forKey:. For example, suppose the server refers to the average property by a different name (e.g. "mean"):

{"count": 3, "sum": 9.0, "mean": 3.0}

You could override setValue:forKey: to ensure that this value is mapped to the correct property:

- (void)setValue:(id)value forKey:(NSString *)key {
    if ([key isEqual:@"mean"]) {
        key = @"average";
    }

    [super setValue:value forKey:key];
}

Finally, you can use KVC to easily ignore values. For example, suppose the server response additionally contains a "median" property:

{"count": 3, "sum": 9.0, "average": 3.0, "median": 3.0}

Since the Statistics class does not define a median property, setValuesForKeysWithDictionary: will throw an NSUnknownKeyException. You can avoid this exception by simply overriding the setValue:forUndefinedKey: method:

- (void)setValue:(id)value forUndefinedKey:(NSString *)key {
    // No-op
}

For more ways to simplify iOS app development, please see my projects on GitHub:

  • MarkupKit – Declarative UI for iOS and tvOS
  • HTTP-RPC – Lightweight multi-platform REST

Obtaining Results from a Presented Controller in iOS

The first time I wrote code to present a modal view controller in iOS, I naively assumed that the final argument to the presentViewController:animated:completion: method was a callback that would be invoked after the user had finished interacting with the presented controller's view (i.e. when the controller was dismissed). I was surprised to find my completion block being called as soon as the controller's view appeared on screen, rather than when it when it disappeared. Of course, after reading the documentation more carefully, I realized that this was the intended behavior:

completion – The block to execute after the presentation finishes.

Unfortunately, that's not what I needed. After looking in vain for a means to provide the controller with a callback to invoke on dismissal, I finally came across this information in Apple's View Controller Programming Guide for iOS:

If the presented view controller must return data to the presenting view controller, use the delegation design pattern to facilitate the transfer…
With delegation, the presented view controller stores a reference to a delegate object that implements methods from a formal protocol. As it gathers results, the presented view controller calls those methods on its delegate. In a typical implementation, the presenting view controller makes itself the delegate of its presented view controller.

That was all the document had to say – no code samples or other, more specific, recommendations were provided. Fortunately, after some digging, I was able to identify a couple existing UIKit classes that I could use as a model. UIImagePickerController, which allows a user to select a photo or video to use in an application, defines a UIImagePickerControllerDelegate protocol that is used to notify the presenter when the user either makes a selection or cancels the operation:

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
...

CNContactPickerViewController takes a similar approach with CNContactPickerDelegate:

- (void)contactPicker:(CNContactPickerViewController *)picker didSelectContact:(CNContact *)contact
- (void)contactPickerDidCancel:(CNContactPickerViewController *)picker
...

With these classes as a guide, I was able to easily define and implement my own custom controller delegate, which notifies a presenting controller that a user registration controller has either successfully registered a new user or has been cancelled:

// Forward reference
@protocol RegistrationViewControllerDelegate;

/**
 * Registration view controller.
 */
@interface RegistrationViewController : UITableViewController

@property (weak, nonatomic) id delegate;

@end

/**
 * Registration view controller delegate.
 */
@protocol RegistrationViewControllerDelegate

- (void)registrationViewController:(RegistrationViewController *)registrationViewController didRegisterUser:(User *)user;
- (void)registrationViewControllerDidCancel:(RegistrationViewController *)registrationViewController;

@end

Using a Static UITableView as a Layout Device: Take Two

While layout constraints and the UIStackView class have made it much easier to create applications that automatically respond to device size and orientation changes, another option for implementing autolayout sometimes gets overlooked: UITableView. Since iOS 8, table views have supported "self-sizing" cells, allowing developers to create complex table-based user interfaces without needing to compute cell heights manually.

As table views often make up a large part of an iOS application's user interface, taking advantage of self-sizing cells can significantly reduce overall development time. Additionally, this capabilitity extends to custom cells – as long as the cell defines constraints that unambiguously define the position of each subview, the cell's contents will be automatically laid out as needed by the table view. No manual size calculation or layout code is required.

A good example of using self-sizing table view cells can be found here. This example, written by Oliver Foggin, uses two table views. The first table view presents a list of animals to the user. Tapping a row in this view presents the second table view, which displays detailed information about the selected animal, including name, Latin name, image, and description, as well an author's name and image.

The detail view uses self-sizing static table view cells along with layout constraints to automatically arrange its contents. The structure of the detail view is defined in a storyboard. I was inspired to try to replicate this layout using MarkupKit. The results are shown below:

(after scrolling)

The markup used to create the layout (defined in a file I named AnimalDetailTableView.xml) is as follows:

<?xml version="1.0" encoding="UTF-8"?>

<LMTableView separator backgroundColor="#ffffff">
    <!-- Name and Latin name -->
    <LMTableViewCell>
        <LMColumnView>
            <UILabel id="titleLabel" font="headline"/>
            <UILabel id="dateLabel" font="footnote"/>
        </LMColumnView>
    </LMTableViewCell>

    <!-- Photo -->
    <LMTableViewCell>
        <UIImageView id="articleImageView" height="200" contentMode="scaleAspectFit"/>
    </LMTableViewCell>

    <!-- Text -->
    <LMTableViewCell>
        <UILabel id="articleTextView" numberOfLines="0"/>
    </LMTableViewCell>

    <!-- Author details -->
    <LMTableViewCell>
        <LMColumnView>
            <LMRowView layoutMarginTop="10" layoutMarginLeft="40" layoutMarginBottom="15" layoutMarginRight="40">
                <UIView height="2" backgroundColor="#666666"/>
            </LMRowView>

            <UIImageView id="authorImageView" height="92" contentMode="scaleAspectFit"/>
            <UILabel id="authorNameLabel" font="footnote" textAlignment="center"/>
        </LMColumnView>
    </LMTableViewCell>
</LMTableView>

LMTableView is a subclass of UITableView that acts as its own data source and delegate, serving cells from a statically-defined collection of table view sections. LMTableViewCell is a subclass of UITableViewCell that provides a vehicle for custom cell content. It automatically applies constraints to its content to enable self-sizing behavior.

LMRowView and LMColumnView are layout views that arrange their subviews in a horizontal or vertical line, respectively. They are similar to UIStackView but have some unique properties, such as the ability to distribute subviews by weight as well as specify a background color. They also work in iOS 8. See this article for more information.

The first cell uses a column view to arrange the name and Latin name labels in a vertical line within the cell. The "id" attribute defines outlets for the labels, using the same outlet names that were used in the original storyboard ("titleLabel" and "dateLabel"). As in the original, the labels use the "headline" and "footnote" dynamic text styles:

<!-- Name and Latin name -->
<LMTableViewCell>
    <LMColumnView>
        <UILabel id="titleLabel" font="headline"/>
        <UILabel id="dateLabel" font="footnote"/>
    </LMColumnView>
</LMTableViewCell>

The second cell contains an image view named "articleImageView" that displays a picture of the animal. As in the original storyboard, the height of the image view is constrained to 200 pixels. Similarly, the third cell contains a label named "articleTextView" that displays a description of the animal:

<!-- Photo -->
<LMTableViewCell>
    <UIImageView id="articleImageView" height="200" contentMode="scaleAspectFit"/>
</LMTableViewCell>

<!-- Text -->
<LMTableViewCell>
    <UILabel id="articleTextView" numberOfLines="0"/>
</LMTableViewCell>

The last cell contains a column view that displays the author's name and picture. The picture's height is constrained to 92 pixels, and the name label uses the "footnote" style, as in the original. The column view also contains an empty UIView that is used to create the 2-pixel wide separator found in the original version. A row view is used to establish the margins around the separator view:

<!-- Author details -->
<LMTableViewCell>
    <LMColumnView>
        <LMRowView layoutMarginTop="10" layoutMarginLeft="40" layoutMarginBottom="15" layoutMarginRight="40">
            <UIView height="2" backgroundColor="#666666"/>
        </LMRowView>

        <UIImageView id="authorImageView" height="92" contentMode="scaleAspectFit"/>
        <UILabel id="authorNameLabel" font="footnote" textAlignment="center"/>
    </LMColumnView>
</LMTableViewCell>

Adding the following method to the AnimalDetailTableViewController class to load the view from markup was all I needed to replace the storyboard-based layout with the MarkupKit version:

- (void)loadView
{
    self.view = [LMViewBuilder viewWithName:@"AnimalDetailTableView" owner:self root:nil];
}

No other changes were required! In fact, the tableView:heightForRowAtIndexPath: method of AnimalDetailTableViewController and the detail view scene in the storyboard can actually be removed, since they are not needed by the markup version.

For more information about MarkupKit, including additional examples, see the project README.