KCDKoala (Part 1)

By on 2014/11/23 in Design

There are few tasks as common to iOS development as setting up a table view. As an iOS developer you have probably done this a hundred billion times; and you’re going to do it one hundred billion more. If you’re like me, you’ve developed some tools over the years to simplify the setup and customization of your table views. KCDKoala is an open source re-implementation of my own solution to quickly setting up and working with table and collection views. It’s an object controller with UITableViewDataSource and UICollectionViewDataSource subclasses that exposes a common API for manipulating the underlying object collection. I thought I would take a few blog posts to discuss KCDKoala’s design: there are a lot of iOS programming blogs out there that will teach you how to do things on the micro level, but I’ve come across very few that take a step back and discuss software development from a wider perspective. KCDKoala’s underpinnings aren’t all that complicated: if you wrote a book about how it does the things that it does, you’d have a dozen concepts spread over half as many chapters. But you still wouldn’t know the why or the how that went into developing it. If you’re at all interested in writing useable code, the forks in the road, both taken and not, are more than details: they’re the raison d’être. But I’m getting ahead of myself. For today, all I’m going to do is give a high level overview of what KCDKoala does. Division of Responsibilities: UITableView, UITableViewDelegate, UITableViewDataSource No matter how you approach table views, you’re going to be dealing with three critical components: UITableView, UITableViewDataSource, and UITableViewDelegate. You might get a jump start with UITableViewController, a UIViewController subclass that uses a UITableView instance for its view and implements the basic methods declared in the table view’s delegate and data source protocols. And if all you’ve ever worked with are UITableViewController subclasses, you might not have stopped to appreciate the different responsibilities assigned to the table view’s delegate and data source. But the distinction between these two protocols matters: Apple did a wonderful job of dividing responsibilities. KCDKoala takes advantage of this division by providing a controller that works like a storage class, consuming the UITableView and UICollectionView data source protocols, and providing default, overridable, implementations of the corresponding delegate protocols. From the highest level, it works like this: Your objects adopt either the KCDTableViewObject or KCDCollectionViewObject protocol either directly, or via a category. You initialize your view controller class with a custom initialization method – e.g., initWithStyle:objects: You get the reference to your view controller’s koala instance and interact with it like you would a mutable array: KCDObjectController *koala = [self KCDObjectController]; id<KCDMutableSection>newSection1 = [koala insertSectionWithName:@"New Section 1" objects:myObjects1 animation:UITableViewRowAnimationFade]; id<KCDMutableSection>newSection2 = [koala insertSectionWithName:@"New Section 2" objects:myObjects2 animation:UITableViewRowAnimationFade]; [koala moveSectionAtIndex:1 toIndex:0]; [koala sortSectionAtIndex:0 withComparator:myComparator]; [koala moveObjectsAtIndexPaths:fromPaths toIndexPaths:toPaths]; [newSection1 enumerateObjectsUsingBlock:^(id<KCDObject> object, NSInteger idx, BOOL *stop) { [koala deleteObject:object animation:UITableViewRowAnimationMiddle]; }]; The corresponding view will animate to reflect the updates. You effectuate view updates by manipulating storage. Simple. And if you’ve worked with iOS for long, or done anything with Cocoa Bindings on the Mac, obvious, familiar, and yawn-inducing. But let’s rewind to step #1: protocol adoption. What’s that all about? Reassignment of Responsibilities In your typical UITableViewController and UICollectionViewController, you configure your views (i.e., cells) in your view controller. It’s the MVC norm. That’s how Apple does it, that’s how mom does it, that’s how George Washington did it. But kids these days are sticking all sorts of fancy stuff into table view cells – knobs, switches, sliders, clocks, radios, ponies – and the more stuff they stick in, the more work the view controller needs to do to manage the connection between the data a cell represents and the cell itself, and the more attenuated the connection between the underlying models and their views begins to feel. KCDKoala provides an alternative approach: the objects represented in the table or collection view configure their own cells. This responsibility is the core component of the KCDObject protocols: // KCDTableViewObject - (UITableViewCell *)cellForTableView:(UITableView *)tableView; - (CGFloat)heightForTableView:(UITableView *)tableView; // KCDCollectionViewObject - (UICollectionViewCell *)cellForCollectionView:(UICollectionView *)collectionView; - (CGSize)sizeForCollectionView:(UICollectionView *)collectionView; Once an object adopts the appropriate protocol, presentation is effortless. And this behavior is completely optional: if you, as the API consumer, would prefer to serve up cells the traditional way – e.g., tableView:cellForRowAtIndexPath:– you can do so simply by implementing that method. In fact, any of the UITableView or UICollectionView delegate behaviors that KCDKoala implements for can be overridden simply by implementing them yourself: the koala will always defer to custom implementations of any delegate methods. Finally, you can walk the line between the two behaviors by adopting the KCDTableViewDataSourceDelegate protocol, which provides you with the best of both worlds: - (UITableViewCell *)koala:(KCDObjectController *)kola tableView:(UITableView *)tableView cellForObject:(id<KCDTableViewObject>)object atIndexPath:(NSIndexPath *)indexPath; That’s all for now: next week I’ll discuss how asynchronous transactions are handled, the internal dispatch queue configuration, and how protocols are used to limit API availability to specific...

No Extensions Necessary: Determining Image Type from Data in Cocoa

By on 2014/08/29 in Development

Cocoa relies – almost exclusively – on file extensions to determine file types. Applications are expected to declare which file types they support, and both iOS and OS-X will use those declarations in deciding which applications can open what files. Cocoa will neither read or write file type metadata to the actual file itself, nor will it analyze the file’s raw byte’s to determine file type from the data’s header. Instead, file types are canonized into “uniform type identifiers”, or UTIs. These reverse domain format strings are used by the system to express what “type” a given file is, or may be. When you remove the path extension from a file name, you destroy this relationship, and therefore the system’s ability to distinguish a Microsoft Word document from a JPEG. Or do you? It turns out that there is an exception to the general rule: images. If you remove the file extension from a Windows Bitmap file, for example, a Mac will still recognize the image for what it is: a BMP. Obviously, when it comes to images, Cocoa is perfectly able (and willing) to determine file type by reference to the byte stream itself. So how do we take advantage of this? The ImageIO framework will allow us to inspect image data and make a decision about what “type” it actually is. This can be extremely useful when you’re working with data that lost their context (i.e., filename) when they were dumped from a database, were returned from an inane web service, or were transferred from an extension-hating Windows’ user’s machine. The CoreServices framework, or MobileCoreServices if you’re writing for iOS, is the repository of the system’s declared file type constants. You will need to import this framework to make intelligent use of the uniform type identifiers that ImageIO returns from data inspection. Here’s just enough sample code to get you started: @import ImageIO #if TARGET_OS_IPHONE @import MobileCoreServices; #else @import CoreServices; #endif We’ll need two methods: one to determine the image type from data that’s already been loaded into memory, and another for examining data on disk. Let’s keep things clean by keeping the common code in a share function: NSString * typeOfImage(CGDataProviderRef imgDataProvider, NSString *typeHint) { const void * keys[] = { kCGImageSourceShouldCache, kCGImageSourceTypeIdentifierHint }; const void * values[] = { kCFBooleanFalse, (__bridge CFStringRef)typeHint }; CFDictionaryRef options = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2, NULL, NULL); CGImageSourceRef imageSource = CGImageSourceCreateWithDataProvider(imgDataProvider, options); NSString *imageType = (__bridge NSString *)CGImageSourceGetType(imageSource); CFRelease(options); CFRelease(imageSource); return imageType; } Next, our convenience function for handling image data: NSString * typeOfImageData(NSData *imageData, NSString *typeHint) { NSString *imageType = nil; CGDataProviderRef provider = NULL; if ((provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)imageData))) { imageType = typeOfImage(provider, typeHint); CGDataProviderRelease(provider); } return imageType; } And, finally, our convenience function for handling images on disk: NSString * typeOfImageAtURL(NSURL *imageFileURL, NSString *typeHint) { if (![imageFileURL isFileURL]) { // Limit to local files. return nil; } NSString *imageType = nil; CGDataProviderRef provider = NULL; if ((provider = CGDataProviderCreateWithURL((__bridge CFURLRef)imageFileURL))) { imageType = typeOfImage(provider, typeHint); CGDataProviderRelease(provider); } return imageType; } Here’s a sample method to get you up and running. - (void)test_typeOfImage; { // Load our intentionally mislabeled JPEG image: "aMislabeledJPEGImage.tiff" NSURL *imageURL = [[NSBundle mainBundle] URLForResource:@"aMislabeledJPEGImage" withExtension:@"tiff"]; NSString *imageTypeIdentifier = typeOfImageAtURL(imageURL, nil); if ([imageTypeIdentifier isEqualToString:(__bridge NSString *)kUTTypeTIFF]) { NSLog(@"Image is of TIFF type: %@", imageTypeIdentifier); } if ([imageTypeIdentifier isEqualToString:(__bridge NSString *)kUTTypeJPEG]) { NSLog(@"Image is of JPEG type: %@", imageTypeIdentifier); } } That’s all there is to it,...