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

CHAPTER 14: Hey! You! Get onto iCloud!

505

on a view controller whenever a new controller is about to be pushed onto the navigation stack. The sender parameter points out the object that initiated the segue, and we use that to figure out just what to do here. If the segue is initiated by the programmatic method call we performed in the alert view delegate method, then sender will be equal to self. In that case, we know that the chosenDocument property is already set, and we simply pass its value off to the destination view controller.

Otherwise, we know we’re responding to the user touching a row in the table view, and that’s where things get a little more complicated. That’s the time to construct a URL (much as we did when creating a document), create a new instance of our document class, and try to open the file. You’ll see that the method we call to open the file, openWithCompletionHandler:, works similarly to the save method we used earlier. We pass it a block that it will save for later execution. Just as with the file-saving method, the loading occurs in the background, and this block will be executed on the main thread when it’s complete. At that point, if the loading succeeded, we pass the document along to the detail view controller.

Note that both of these methods use the key-value coding technique that we’ve used a few times before (such as in Chapter 10), letting us set the detailItem property of the segue’s destination controller, even though we don’t include its header. This will work out just fine for us, since BIDDetailViewController—the detail view controller class created as part of the Xcode project—happens to include a property called detailItem right out of the box.

With the amount of code we now have in place, it’s high time we configure the storyboard so that we can run our app and make something happen. Save your code and continue.

Initial Storyboarding

Select MainStoryboard.storyboard in the Xcode project navigator, and take a look at what’s already there. You’ll find scenes for the navigation controller, the master view controller, and the detail view controller (see Figure 14–2). You can ignore the navigation controller entirely, since all our work will be with the other two.

www.it-ebooks.info

506

CHAPTER 14: Hey! You! Get onto iCloud!

Figure 14–2. The TinyPix storyboard, showing the navigation controller, master view controller, and detail view controller

Let’s start by dealing with the master view controller scene. This is where the table view showing the list of all our TinyPix documents is configured. By default, this scene’s table view is configured to use static cells instead of dynamic cells (see Chapter 10 if you need a refresher on the difference between these two cell types). We want our table view to get its contents from the data source methods we implemented, so select the table view. You can do this in the dock by first finding the item named Master View Controller – Master, and then opening its disclosure triangle and selecting the Table View item just below it. With the table view selected, open the attributes inspector and set the Content popup to Dynamic Prototypes.

This change actually deletes the preexisting segue that connected a table view cell to the detail view controller. Re-create that segue by first selecting the Table View Cell within the Table View. Next, control-drag from the cell to the detail view controller, and select Push from the storyboard segues menu that pops up.

Now, select that same prototype table view cell you just dragged from, and use the attributes inspector to set its Style to Basic and its Identifier to FileCell. This will let the data source code we wrote earlier access the table view cell.

We also need to create the segue that we’re triggering in our code. Do this by controldragging from the master detail view controller’s icon (an orange circle at the bottom of its scene or the Master View Controller - Master icon in the dock) over to the detail view controller, and selecting Push from the storyboard segues menu.

You’ll now see two segues that seem to connect the two scenes. By selecting each of them, you can tell where they’re coming from. Selecting one segue highlights the whole master scene; selecting the second one highlights just the table view cell. Select the segue that highlights the whole scene, and use the attributes inspector to set its

Identifier to masterToDetail.

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

507

The final touch needed for the master view controller scene is to let the user pick which color will be used to represent an “on” point in the detail view. Instead of implementing some kind of comprehensive color picker, we’re just going to add a segmented control that will let the user pick from a set of predefined colors.

Find a Segmented Control in the object library, drag it out, and place it in the navigation bar at the top of the master view (see Figure 14–3).

Figure 14–3. The TinyPix storyboard, showing the master view controller with a segmented control being dropped on the controller’s navigation bar

Make sure the segmented control is selected, and open the attributes inspector. In the Segmented Control section at the top of the inspector, use the stepper control to change the number of Segments from 2 to 3. Then double-click the title of each segment in turn, changing them to Black, Red, and Green, respectively.

Next, control-drag from the segmented control to the icon representing the master controller (the orange circle below the controller or the dock icon labeled Master View Controller – Master), and select the chooseColor: method. Then control-drag from the master controller back to the segmented control, and select the colorControl outlet.

We’ve finally reached a point where we can run the app and see all our hard work brought to life! Run your app. You’ll see it start up and display an empty table view with a segmented control at the top and a plus button in the upper-right corner (see Figure 14–4).

Hit the plus button, and the app will ask you to name the new document. Give it a name, tap Create, and you’ll see the app transition to the detail display, which is, well, under construction right now. All the default implementation of the detail view controller does

www.it-ebooks.info

508

CHAPTER 14: Hey! You! Get onto iCloud!

is display the description of its detailItem in a label. Of course, there’s more information in the console pane. It’s not much, but it’s something!

Figure 14–4. The TinyPix app when it first appears. Click the plus icon to add a new document. You’ll be prompted to name your new TinyPix document. At the moment, all the detail view does is display the document name in a label.

Tap the back button to return to the master list, where you’ll see the item you added. Go ahead and create one or two more items to see that they’re correctly added to the list. Then head back to Xcode, because we’ve got more work to do!

Creating BIDTinyPixView

Our next order of business is the creation of a view class to display our grid and let the user edit it. Select the TinyPix folder in the project navigator, and press N to create a new file. In the iOS Cocoa Touch section, select Objective-C class, and click Next. Name the new class BIDTinyPixView, and choose UIView in the Subclass of popup. Click Next, verify that the save location is OK, and click Create.

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

509

NOTE: The implemention of our view class includes some drawing and touch handling that we haven’t covered yet. Rather than bog down this chapter with too many details about these topics, we’re just going to quickly show you the code. We’ll cover details about drawing in Chapter 16

and responding to touches and drags in Chapter 17.

Select BIDTinyPixView.h, and make the following changes:

#import <UIKit/UIKit.h>

#import "BIDTinyPixDocument.h"

@interface BIDTinyPixView : UIView

@property (strong, nonatomic) BIDTinyPixDocument *document; @property (strong, nonatomic) UIColor *highlightColor;

@end

All we’re doing here is adding a couple of properties so that the controller can pass along the document, as well as set the color used for the “on” squares in our grid.

Now, switch over to BIDTinyPixView.m, where we have some more substantial work ahead of us. Start by adding this class extension and synthesizing all our properties at the top of the file:

#import "BIDTinyPixView.h"

typedef struct { NSUInteger row; NSUInteger column;

} GridIndex;

@interface BIDTinyPixView ()

@property (assign, nonatomic) CGSize blockSize; @property (assign, nonatomic) CGSize gapSize;

@property (assign, nonatomic) GridIndex selectedBlockIndex;

-(void)initProperties;

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

-(GridIndex)touchedGridIndexFromTouches:(NSSet *)touches;

-(void)toggleSelectedBlock;

@end

@implementation BIDTinyPixView

@synthesize document; @synthesize highlightColor;

@synthesize blockSize; @synthesize gapSize; @synthesize selectedBlockIndex;

.

.

.

www.it-ebooks.info

510

CHAPTER 14: Hey! You! Get onto iCloud!

Here, we defined a C struct called GridIndex as a handy way to deal with row/column pairs. We also defined a class extension with some properties and private methods that we’ll need to use later, and synthesized all our properties inside the implementation.

The default empty UIView subclass contains an initWithFrame: method, which is really the default initializer for the UIView class. However, since this class is going to be loaded from a storyboard, it will instead be initialized using the initWithCoder: method. We’ll implement both of these, making each call a third method that initializes our properties. Make this change to initWithFrame: and add the code just below it:

- (id)initWithFrame:(CGRect)frame

{

self = [super initWithFrame:frame]; if (self) {

// Initialization code

[self initProperties];

}

return self;

}

- (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) {

[self initProperties];

}

return self;

}

- (void)initProperties {

blockSize = CGSizeMake(34, 34); gapSize = CGSizeMake(5, 5); selectedBlockIndex.row = NSNotFound;

selectedBlockIndex.column = NSNotFound; highlightColor = [UIColor blackColor];

}

The blockSize and gapSize values are specifically tuned to a view that’s 310 pixels across. If we wanted to be extra clever here, we could have defined them dynamically based on the view’s actual frame, but this is the simplest approach that works for our case, so we’re sticking with it!

Now, let’s take a look at the drawing routines. We override the standard UIView drawRect: method, and use that to simply walk through all the blocks in our grid, and call another method for each block. Add the following bold code, and don’t forget to remove the comment marks around the drawRect: method:

/*

//Only override drawRect: if you perform custom drawing.

//An empty implementation adversely affects performance during animation. - (void)drawRect:(CGRect)rect

{

//Drawing code

if (!document) return;

www.it-ebooks.info

CHAPTER 14: Hey! You! Get onto iCloud!

511

for (NSUInteger row = 0; row < 8; row++) {

for (NSUInteger column = 0; column < 8; column++) { [self drawBlockAtRow:row column:column];

}

}

}

*/

- (void)drawBlockAtRow:(NSUInteger)row column:(NSUInteger)column {

CGFloat startX = (blockSize.width + gapSize.width) * (7 - column) + 1; CGFloat startY = (blockSize.height + gapSize.height) * row + 1;

CGRect blockFrame = CGRectMake(startX, startY, blockSize.width, blockSize.height); UIColor *color = [document stateAtRow:row column:column] ?

self.highlightColor : [UIColor whiteColor]; [color setFill];

[[UIColor lightGrayColor] setStroke];

UIBezierPath *path = [UIBezierPath bezierPathWithRect:blockFrame]; [path fill];

[path stroke];

}

Finally, we add a set of methods that respond to touch events by the user. Both touchesBegan:withEvent: and touchesMoved:withEvent: are standard methods that every UIView subclass can implement in order to capture touch events that happen within the view’s frame. These two methods make use of other methods we defined in the class extension to calculate a grid location based on a touch location, and to toggle a specific value in the document. Add these four methods at the bottom of the file, just above the @end:

- (GridIndex)touchedGridIndexFromTouches:(NSSet *)touches { GridIndex result;

UITouch *touch = [touches anyObject];

CGPoint location = [touch locationInView:self];

result.column = 8 - (location.x * 8.0 / self.bounds.size.width); result.row = location.y * 8.0 / self.bounds.size.height;

return result;

}

- (void)toggleSelectedBlock {

[document toggleStateAtRow:selectedBlockIndex.row column:selectedBlockIndex.column]; [[document.undoManager prepareWithInvocationTarget:document]

toggleStateAtRow:selectedBlockIndex.row column:selectedBlockIndex.column]; [self setNeedsDisplay];

}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { self.selectedBlockIndex = [self touchedGridIndexFromTouches:touches]; [self toggleSelectedBlock];

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { GridIndex touched = [self touchedGridIndexFromTouches:touches]; if (touched.row != selectedBlockIndex.row

|| touched.column != selectedBlockIndex.column) {

www.it-ebooks.info

512

CHAPTER 14: Hey! You! Get onto iCloud!

selectedBlockIndex = touched; [self toggleSelectedBlock];

}

}

Sharp-eyed readers may have noticed that the toggleSelectedBlock method does something a bit special. After calling the document’s toggleStateAtRow:column: method, it does something more. Let’s take another look:

- (void)toggleSelectedBlock {

[document toggleStateAtRow:selectedBlockIndex.row column:selectedBlockIndex.column]; [[document.undoManager prepareWithInvocationTarget:document]

toggleStateAtRow:selectedBlockIndex.row column:selectedBlockIndex.column]; [self setNeedsDisplay];

}

The call to document.undoManager returns an instance of NSUndoManager. We haven’t dealt with this directly anywhere else in this book, but NSUndoManager is the structural underpinning for the undo/redo functionality in both iOS and Mac OS X. The idea is that anytime the user performs an action in the GUI, you use NSUndoManager to leave a sort of breadcrumb by “recording” a method call that will undo what the user just did. NSUndoManager will store that method call on a special undo stack, which can be used to backtrack through a document’s state whenever the user activates the system’s undo functionality.

The way it works is that the prepareWithInvocationTarget: method returns a special kind of proxy object that you can send any message to, and the message will be packed up with the target and pushed onto the undo stack. So, while it may look like you’re calling toggleStateAtRow:column: twice in a row, the second time it’s not being called, but instead is just being queued up for later potential use. This kind of spectacularly dynamic behavior is an area where Objective-C really stands out in comparison to static languages such as C++, where techniques such as letting one object act as a proxy to another or packing up a method invocation for later use have no language support and are nearly impossible (and therefore many tasks such as building undo support can be quite tedious).

So, why are we doing this? We haven’t been giving any thought to undo/redo issues up to this point, so why now? The reason is that registering an undoable action with the document’s undoManager marks the document as “dirty,” and ensures that it will be saved automatically at some point in the next few seconds. The fact that the user’s actions are also undoable is just icing on the cake, at least in this application. In an app with a more complex document structure, allowing document-wide undo support can be hugely beneficial.

Save your changes. Now that our view class is ready to go, let’s head back to the storyboard to configure the GUI for the detail view.

www.it-ebooks.info

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