Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Beginning iOS5 Development.pdf
Скачиваний:
7
Добавлен:
09.05.2015
Размер:
15.6 Mб
Скачать

494

CHAPTER 14: Hey! You! Get onto iCloud!

a paid iOS developer account so that you can install on devices, because apps running in the simulator don’t have access to iCloud services.

Managing Document Storage with UIDocument

Anyone who has used a desktop computer for anything besides just surfing the Web has probably worked with a document-based application. From TextEdit to Microsoft Word to GarageBand to Xcode, any piece of software that lets you deal with multiple collections of data, saving each collection to a separate file, could be considered a document-based application. Often, there’s a one-to-one correspondence between an on-screen window and the document it contains, but sometimes (such as in Xcode), a single window can display multiple files that are all related in some way.

On iOS devices, we don’t have the luxury of multiple windows, but plenty of apps can still benefit from a document-based approach. Now iOS developers have a little boost in making it work thanks to the UIDocument class, which takes care of the most common aspects of document file storage. You won’t need to deal with files directly (just URLs), and all the necessary reading and writing happen on a background thread, so your app can remain responsive even while file access is occurring. It also automatically saves edited documents periodically and whenever the app is suspended (such as when the device is shut down, the home button is pressed, and so on), so there’s no need for any sort of save button. All of this helps make your apps behave the way users expect their iOS apps to behave.

Building TinyPix

We’re going to build an app called TinyPix that lets you edit simple 8 × 8 images, in glorious 1-bit color (see Figure 14–1)! For the user’s convenience, each picture is blown up to the full-screen size for editing. And, of course, we’ll be using UIDocument to represent the data for each image.

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

495

Figure 14–1. Editing an extremely low-resolution icon in TinyPix

Start off by creating a new project in Xcode. From the iOS Application section, select the Master-Detail Application template, and then click Next. Name this new app TinyPix, set the Device Family popup to iPhone, and make sure the Use Storyboard checkbox is checked. Then click Next again, and choose the location to save your project.

In Xcode’s project navigator, you’ll see that your project contains files for

BIDAppDelegate, BIDMasterViewController, and BIDDetailViewController, as well as the MainStoryboard.storyboard file. We’ll make changes to most of these files to some extent, and we will create a few new classes along the way as well.

Creating BIDTinyPixDocument

The first new class we’re going to create is the document class that will contain the data for each TinyPix image that’s loaded from file storage. Select the TinyPix folder in Xcode, and press N to create a new file. From the iOS Cocoa Touch section, select

Objective-C class, and click Next. Enter BIDTinyPixDocument in the Class field, enter

UIDocument in the Subclass of field, and click Next. Then click Create to create the files.

Let’s think about the public API of this class before we get into its implementation details. This class is going to represent an 8 × 8 grid of pixels, where each pixel consists of a single on or off value. So, let’s give it a method that takes a pair of grid and column

www.it-ebooks.info

496

CHAPTER 14: Hey! You! Get onto iCloud!

indexes and returns a BOOL value. Let’s also provide a method to set a specific state at a specified grid and column and, as a convenience, another method that simply toggles the state at a particular place.

Select BIDTinyPixDocument.h to edit the new class’s header. Add the following bold lines:

#import <UIKit/UIKit.h>

@interface BIDTinyPixDocument : UIDocument

// row and column range from 0 to 7

-(BOOL)stateAtRow:(NSUInteger)row column:(NSUInteger)column;

-(void)setState:(BOOL)state atRow:(NSUInteger)row column:(NSUInteger)column;

-(void)toggleStateAtRow:(NSUInteger)row column:(NSUInteger)column;

@end

Now switch over to BIDTinyPixDocument.m, where we’ll implement storage for our 8 × 8 grid, the methods defined in our public API, and the required UIDocument methods that will enable loading and saving our documents.

Let’s start by defining the storage for our 8 × 8 bitmap data. We’ll hold this data in an instance of NSMutableData, which lets us work directly with an array of byte data that is still contained inside an object, so that the usual Cocoa memory management will take care of freeing the memory when we’re finished with it. Add this class extension and property synthesis to make it happen:

#import "BIDTinyPixDocument.h"

@interface BIDTinyPixDocument ()

@property (strong, nonatomic) NSMutableData *bitmap; @end

@implementation BIDTinyPixDocument

@synthesize bitmap;

@end

The UIDocument class has a designated initializer that all subclasses should use. This is where we’ll create our initial bitmap. In true bitmap style, we’re going to minimize memory usage by using a single byte to contain each row. Each bit in the byte represents the on/off value of a column index within that row. In total, our document contains just 8 bytes.

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

497

NOTE: This section contains a small amount of bitwise operations, as well as some C pointer and array manipulation. This is all pretty mundane for C developers, but if you don’t have much C experience, it may seem puzzling or even impenetrable. In that case, feel free to simply copy and

use the code provided (it works just fine). If you want to really understand what’s going on, you may want to dig deeper into C itself, perhaps by adding a copy of Learn C on the Mac by Dave

Mark (Apress, 2009) to your bookshelf.

Add this method to our document’s implementation, placing it directly above the @end at the bottom of the file:

- (id)initWithFileURL:(NSURL *)url { self = [super initWithFileURL:url]; if (self) {

unsigned char startPattern[] = { 0x01,

0x02,

0x04,

0x08,

0x10,

0x20,

0x40,

0x80

};

self.bitmap = [NSMutableData dataWithBytes:startPattern length:8];

}

return self;

}

This starts off each bitmap with a simple diagonal pattern stretching from one corner to another.

Now, it’s time to implement the methods that make up the public API we defined in the header. Let’s tackle the method for reading the state of a single bit first. This simply grabs the relevant byte from our array of bytes, then does a bit shift and an AND operation to determine if the specified bit was set, and returns YES or NO accordingly. Add this method above the @end:

- (BOOL)stateAtRow:(NSUInteger)row column:(NSUInteger)column { const char *bitmapBytes = [bitmap bytes];

char rowByte = bitmapBytes[row];

char result = (1 << column) & rowByte; if (result != 0)

return YES;

else

return NO;

}

Next comes the inverse: a method that sets the value specified at a given row and column. Here, we once again grab a pointer to the relevant byte for the specified row

www.it-ebooks.info

498

CHAPTER 14: Hey! You! Get onto iCloud!

and do a bit shift. But this time, instead of using the shifted bit to examine the contents of the row, we use it to either set or unset a bit in the row. Add this method above the

@end:

- (void)setState:(BOOL)state atRow:(NSUInteger)row column:(NSUInteger)column { char *bitmapBytes = [bitmap mutableBytes];

char *rowByte = &bitmapBytes[row];

if (state)

*rowByte = *rowByte | (1 << column);

else

*rowByte = *rowByte & ~(1 << column);

}

Now, let’s add the convenience method, which lets outside code simply toggle a single cell. Add this method above the @end:

- (void)toggleStateAtRow:(NSUInteger)row column:(NSUInteger)column { BOOL state = [self stateAtRow:row column:column];

[self setState:!state atRow:row column:column];

}

Our document class requires two final pieces before it fits into the puzzle of a document-based app: methods for reading and writing. As we mentioned earlier, you don’t need to deal with files directly. You don’t even need to worry about the URL that was passed into the initWithFileURL: method earlier. All that’you need to do is implement one method that transforms the document’s data structure into an NSData object, ready for saving, and another that takes a freshly loaded NSData object and pulls the object’s data structure out of it. Since our document’s internal structure is already contained in an NSMutableData object, which is a subclass of NSData, these implementations are pleasingly simple. Add these two methods above the @end:

- (id)contentsForType:(NSString *)typeName error:(NSError **)outError { NSLog(@"saving document to URL %@", self.fileURL);

return [bitmap copy];

}

- (BOOL)loadFromContents:(id)contents ofType:(NSString *)typeName error:(NSError **)outError {

NSLog(@"loading document from URL %@", self.fileURL); self.bitmap = [contents mutableCopy];

return true;

}

The first of these methods, contentsForType:error:, is called whenever our document is about to be saved to storage. It simply returns an immutable copy of our bitmap data, which the system will take care of storing later.

The second method, loadFromContents:ofType:error:, is called whenever the system has just loaded data from storage and wants to provide this data to an instance of our document class. Here, we just grab a mutable copy of the data that has been passed in. We’ve included some logging statements, just so you can see what’s happening in the Xcode log later on.

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]