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

CHAPTER 14: Hey! You! Get onto iCloud!

499

Each of these methods allows you to do some things that we’re ignoring in this app. They both provide a typeName parameter, which you could use to distinguish between different types of data storage that your document can load from or save to. They also have an outError parameter, which you could use to specify that an error occurred while copying data to or from your document’s in-memory data structure. In our case, however, what we’re doing is so simple that these aren’t important concerns.

That’s all we need for our document class. Sticking to MVC principles, our document sits squarely in the model camp, knowing nothing about how it’s displayed. And thanks to the UIDocument superclass, the document is even shielded from most of the details about how it’s stored.

Code Master

Now that we have our document class ready to go, it’s time to address the first view that a user sees when running our app: the list of existing TinyPix documents, which is taken care of by the BIDMasterViewController class. We need to let this class know how to grab the list of available documents, create and name a new document, and let the user choose an existing document. When a document is created or chosen, it’s then passed along to the detail controller for display.

Start off by selecting BIDMasterViewController.h, where we’ll make a few changes. We’re going to use an alert panel later on to let the user name a new document, so we want to declare that this class implements the relevant delegate protocol.

We’ll also include a segmented control in our GUI, which will allow the user to choose the color that will be used to display the TinyPix pixels. Though this is not a particularly useful feature in and of itself, it will help demonstrate the iCloud mechanism, as the highlight color setting makes its way from the device on which you set it to another of your connected devices running the same app. The first version of the app will use the color as a per-device setting. Later in the chapter, we’ll add the code to make the color setting propagate through iCloud to the user’s other devices.

To implement the color segmented control, we’ll add an outlet and an action to our code as well. Make these changes to BIDMasterViewController.h:

#import <UIKit/UIKit.h>

@interface BIDMasterViewController : UITableViewController

@property (weak, nonatomic) IBOutlet UISegmentedControl *colorControl; - (IBAction)chooseColor:(id)sender;

@end

Now, switch over to BIDMasterViewController.m. We’re going to start by importing the header for our document class, adding some private properties and methods (for later use) in a class extension, and synthesizing accessors for the new properties we just added.

#import "BIDMasterViewController.h"

#import "BIDTinyPixDocument.h"

www.it-ebooks.info

500CHAPTER 14: Hey! You! Get onto iCloud!

@interface BIDMasterViewController () <UIAlertViewDelegate> @property (strong, nonatomic) NSArray *documentFilenames; @property (strong, nonatomic) BIDTinyPixDocument *chosenDocument;

-(NSURL *)urlForFilename:(NSString *)filename;

-(void)reloadFiles;

@end

@implementation BIDMasterViewController

@synthesize colorControl; @synthesize documentFilenames; @synthesize chosenDocument;

.

.

.

Let’s take care of those private methods right away. The first of these takes a file name, combines it with the file path of the app’s Documents directory, and returns a URL pointing to that specific file. The Documents directory is a special location that iOS sets aside, one for each app installed on an iOS device. You can use it to store documents created by your app, and rest assured that those documents will be automatically included whenever users back up their iOS device, whether it’s to iTunes or iCloud.

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

- (NSURL *)urlForFilename:(NSString *)filename {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *documentDirectory = [paths objectAtIndex:0];

NSString *filePath = [documentDirectory stringByAppendingPathComponent:filename]; NSURL *url = [NSURL fileURLWithPath:filePath];

return url;

}

The second private method is a bit longer. It also uses the Documents directory, this time to search for files representing existing documents. The method takes the files it finds and sorts them by creation date, so that the user will see the list of documents sorted “blog-style,”with the newest items first. The document file names are stashed away in the documentFilenames property, and then the table view (which we admittedly haven’t yet dealt with) is reloaded. Add this method above the @end:

- (void)reloadFiles {

NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);

NSString *path = [paths objectAtIndex:0]; NSFileManager *fm = [NSFileManager defaultManager];

NSError *dirError;

NSArray *files = [fm contentsOfDirectoryAtPath:path error:&dirError]; if (!files) {

NSLog(@"Encountered error while trying to list files in directory %@: %@", path, dirError);

}

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

501

NSLog(@"found files: %@", files);

files = [files sortedArrayUsingComparator: ^NSComparisonResult(id filename1, id filename2) {

NSDictionary *attr1 = [fm attributesOfItemAtPath:

[path stringByAppendingPathComponent:filename1] error:nil];

NSDictionary *attr2 = [fm attributesOfItemAtPath:

[path stringByAppendingPathComponent:filename2] error:nil];

return [[attr2 objectForKey:NSFileCreationDate] compare: [attr1 objectForKey:NSFileCreationDate]];

}];

self.documentFilenames = files; [self.tableView reloadData];

}

Now, let’s deal with our dear old friends, the table view data source methods. These should be pretty familiar to you by now. Add the following three methods above the

@end:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return 1;

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {

return [self.documentFilenames count];

}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"FileCell"];

NSString *path = [self.documentFilenames objectAtIndex:indexPath.row]; cell.textLabel.text = path.lastPathComponent.stringByDeletingPathExtension; return cell;

}

These methods are based on the contents of the array stored in the documentFilenames property. The tableView:cellForForAtIndexPath: method relies on the existence of a cell attached to the table view with "FileCell" set as its identifier, so we must be sure to set that up in the storyboard a little later.

If not for the fact that we haven’t touched our storyboard yet, the code we have now would almost be something we could run and see in action, but with no preexisting TinyPix documents, we would have nothing to display in our table view. And so far, we don’t have any way to create new documents either. Also, we have not yet dealt with the color-selection control we’re going to add. So, let’s do a bit more work before we try to run our app.

The user’s choice of highlight color will be stored in NSUserDefaults for later retrieval. Here’s the action method that will do that by passing along the segmented control’s chosen index. Add this method above the @end:

www.it-ebooks.info

502 CHAPTER 14: Hey! You! Get onto iCloud!

- (IBAction)chooseColor:(id)sender {

NSInteger selectedColorIndex = [(UISegmentedControl *)sender selectedSegmentIndex]; NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

[prefs setInteger:selectedColorIndex forKey:@"selectedColorIndex"];

}

We realize that we haven’t yet set this up in the storyboard, but we’ll get there!

We also need to add the following few lines to the viewWillAppear: method, to make sure that the segmented control in our app’s GUI will show the current value from NSUserDefaults as soon as it’s about to be displayed:

- (void)viewWillAppear:(BOOL)animated

{

[super viewWillAppear:animated];

NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];

NSInteger selectedColorIndex = [prefs integerForKey:@"selectedColorIndex"]; self.colorControl.selectedSegmentIndex = selectedColorIndex;

}

Now, let’s set up a few things in our viewDidLoad method. We’ll start off by adding a button to the right side of the navigation bar. The user will press this button to create a new TinyPix document. We finish off by calling the reloadFiles method that we implemented earlier. Make this change to viewDidLoad:

- (void)viewDidLoad

{

[super viewDidLoad];

// Do any additional setup after loading the view, typically from a nib.

UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self

action:@selector(insertNewObject)]; self.navigationItem.rightBarButtonItem = addButton;

[self reloadFiles];

}

You may have noticed that when we created the UIBarButtonItem in this method, we told it to call the insertNewObject method when it’s pressed. We haven’t written that method yet, so let’s do so now. Add this method above the @end:

-(void)insertNewObject {

//get the name UIAlertView *alert =

[[UIAlertView alloc] initWithTitle:@"Filename"

message:@"Enter a name for your new TinyPix document." delegate:self

cancelButtonTitle:@"Cancel" otherButtonTitles:@"Create", nil];

alert.alertViewStyle = UIAlertViewStylePlainTextInput; [alert show];

}

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

503

This method creates an alert panel that includes a text-input field and displays it. The responsibility of creating a new item instead falls to the delegate method that the alert view calls when it’s finished, which we’ll also address now. Add this method above the

@end:

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {

if (buttonIndex == 1) {

NSString *filename = [NSString stringWithFormat:@"%@.tinypix", [alertView textFieldAtIndex:0].text];

NSURL *saveUrl = [self urlForFilename:filename];

self.chosenDocument = [[BIDTinyPixDocument alloc] initWithFileURL:saveUrl]; [chosenDocument saveToURL:saveUrl

forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) {

if (success) { NSLog(@"save OK"); [self reloadFiles];

[self performSegueWithIdentifier:@"masterToDetail" sender:self]; } else {

NSLog(@"failed to save!");

}

}];

}

}

This method starts out simply enough. It checks the value of buttonIndex to see which button was pressed (a 0 indicates that the user pressed the Cancel button). It then creates a file name based on the user’s entry, a URL based on that file name (using the urlForFilename: method we wrote earlier), and a new BIDTinyPixDocument instance using that URL.

What comes next is a little more subtle. It’s important to understand here that just creating a new document with a given URL doesn’t create the file. In fact, at the time that the initWithFileURL: is called, the document doesn’t yet know if the given URL refers to an existing file or to a new file that needs to be created. We need to tell it what to do. In this case, we tell it to save a new file at the given URL with this code:

[chosenDocument saveToURL:saveUrl forSaveOperation:UIDocumentSaveForCreating

completionHandler:^(BOOL success) {

.

.

.

}];

Of interest is the purpose and usage of the block that is passed in as the last argument. This method, which we’re calling saveToURL:forSaveOperation:completionHandler:, doesn’t have a return value to tell us how it all worked out. In fact, the method returns immediately after it’s called, long before the file is actually saved. Instead, it starts the file-saving work, and later, when it’s done, calls the block that we gave it, using the success parameter to let us know whether it succeeded. To make it all work as smoothly as possible, the file-saving work is actually performed on a background thread. The

www.it-ebooks.info

504

CHAPTER 14: Hey! You! Get onto iCloud!

block we pass in, however, is called on the main thread, so we can safely use any facilities that require the main thread, such as UIKit. With that in mind, take a look again at what happens inside that block:

if (success) { NSLog(@"save OK"); [self reloadFiles];

[self performSegueWithIdentifier:@"masterToDetail" sender:self]; } else {

NSLog(@"failed to save!");

}

This is the content of the block we passed in to the file-saving method, and it’s called later after the file operation is completed. We check to see if it succeeded; if so, we do an immediate file reload, and then initiate a segue to another view controller. This is an aspect of segues that we didn’t cover in Chapter 10, but it’s pretty straightforward.

The idea is that a segue in a storyboard file can have an identifier, just like a table view cell, and you can use that identifier to trigger a segue programmatically. In this case, we’ll just need to remember to configure that segue in the storyboard when we get to it. But before we do that, let’s add the last method this class needs, to take care of that segue. Insert this method above the @end:

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if (sender == self) {

//if sender == self, a new document has just been created,

//and chosenDocument is already set.

UIViewController *destination = segue.destinationViewController; if ([destination respondsToSelector:@selector(setDetailItem:)]) {

[destination setValue:self.chosenDocument forKey:@"detailItem"];

}

}else {

//find the chosen document from the tableview

NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow]; NSString *filename = [documentFilenames objectAtIndex:indexPath.row]; NSURL *docUrl = [self urlForFilename:filename];

self.chosenDocument = [[BIDTinyPixDocument alloc] initWithFileURL:docUrl]; [self.chosenDocument openWithCompletionHandler:^(BOOL success) {

if (success) { NSLog(@"load OK");

UIViewController *destination = segue.destinationViewController; if ([destination respondsToSelector:@selector(setDetailItem:)]) {

[destination setValue:self.chosenDocument forKey:@"detailItem"];

}

} else {

NSLog(@"failed to load!");

}

}];

}

}

This method has two clear paths of execution, determined by the condition at the top. Remember from our discussion of storyboards in Chapter 10 that this method is called

www.it-ebooks.info

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