- •Contents at a Glance
- •About the Authors
- •About the Technical Reviewer
- •Acknowledgments
- •Preface
- •What This Book Is
- •What You Need
- •Developer Options
- •What You Need to Know
- •What’s Different About Coding for iOS?
- •Only One Active Application
- •Only One Window
- •Limited Access
- •Limited Response Time
- •Limited Screen Size
- •Limited System Resources
- •No Garbage Collection, but…
- •Some New Stuff
- •A Different Approach
- •What’s in This Book
- •What’s New in This Update?
- •Are You Ready?
- •Setting Up Your Project in Xcode
- •The Xcode Workspace Window
- •The Toolbar
- •The Navigator View
- •The Jump Bar
- •The Utility Pane
- •Interface Builder
- •New Compiler and Debugger
- •A Closer Look at Our Project
- •Introducing Xcode’s Interface Builder
- •What’s in the Nib File?
- •The Library
- •Adding a Label to the View
- •Changing Attributes
- •Some iPhone Polish—Finishing Touches
- •Bring It on Home
- •The Model-View-Controller Paradigm
- •Creating Our Project
- •Looking at the View Controller
- •Understanding Outlets and Actions
- •Outlets
- •Actions
- •Cleaning Up the View Controller
- •Designing the User Interface
- •Adding the Buttons and Action Method
- •Adding the Label and Outlet
- •Writing the Action Method
- •Trying It Out
- •Looking at the Application Delegate
- •Bring It on Home
- •A Screen Full of Controls
- •Active, Static, and Passive Controls
- •Creating the Application
- •Implementing the Image View and Text Fields
- •Adding the Image View
- •Resizing the Image View
- •Setting View Attributes
- •The Mode Attribute
- •Interaction Checkboxes
- •The Alpha Value
- •Background
- •Drawing Checkboxes
- •Stretching
- •Adding the Text Fields
- •Text Field Inspector Settings
- •Setting the Attributes for the Second Text Field
- •Creating and Connecting Outlets
- •Closing the Keyboard
- •Closing the Keyboard When Done Is Tapped
- •Touching the Background to Close the Keyboard
- •Adding the Slider and Label
- •Creating and Connecting the Actions and Outlets
- •Implementing the Action Method
- •Adding Two Labeled Switches
- •Connecting and Creating Outlets and Actions
- •Implementing the Switch Actions
- •Adding the Button
- •Connecting and Creating the Button Outlets and Actions
- •Implementing the Segmented Control Action
- •Implementing the Action Sheet and Alert
- •Conforming to the Action Sheet Delegate Method
- •Showing the Action Sheet
- •Spiffing Up the Button
- •Using the viewDidLoad Method
- •Control States
- •Stretchable Images
- •Crossing the Finish Line
- •The Mechanics of Autorotation
- •Points, Pixels, and the Retina Display
- •Autorotation Approaches
- •Handling Rotation Using Autosize Attributes
- •Configuring Supported Orientations
- •Specifying Rotation Support
- •Designing an Interface with Autosize Attributes
- •Using the Size Inspector’s Autosize Attributes
- •Setting the Buttons’ Autosize Attributes
- •Restructuring a View When Rotated
- •Creating and Connecting Outlets
- •Moving the Buttons on Rotation
- •Swapping Views
- •Designing the Two Views
- •Implementing the Swap
- •Changing Outlet Collections
- •Rotating Out of Here
- •Common Types of Multiview Apps
- •The Architecture of a Multiview Application
- •The Root Controller
- •Anatomy of a Content View
- •Building View Switcher
- •Creating Our View Controller and Nib Files
- •Modifying the App Delegate
- •Modifying BIDSwitchViewController.h
- •Adding a View Controller
- •Building a View with a Toolbar
- •Writing the Root View Controller
- •Implementing the Content Views
- •Animating the Transition
- •Switching Off
- •The Pickers Application
- •Delegates and Data Sources
- •Setting Up the Tab Bar Framework
- •Creating the Files
- •Adding the Root View Controller
- •Creating TabBarController.xib
- •The Initial Test Run
- •Implementing the Date Picker
- •Implementing the Single-Component Picker
- •Declaring Outlets and Actions
- •Building the View
- •Implementing the Controller As a Data Source and Delegate
- •Implementing a Multicomponent Picker
- •Declaring Outlets and Actions
- •Building the View
- •Implementing the Controller
- •Implementing Dependent Components
- •Creating a Simple Game with a Custom Picker
- •Writing the Controller Header File
- •Building the View
- •Adding Image Resources
- •Implementing the Controller
- •The spin Method
- •The viewDidLoad Method
- •Final Details
- •Linking in the Audio Toolbox Framework
- •Final Spin
- •Table View Basics
- •Table Views and Table View Cells
- •Grouped and Plain Tables
- •Implementing a Simple Table
- •Designing the View
- •Writing the Controller
- •Adding an Image
- •Using Table View Cell Styles
- •Setting the Indent Level
- •Handling Row Selection
- •Changing the Font Size and Row Height
- •Customizing Table View Cells
- •Adding Subviews to the Table View Cell
- •Creating a UITableViewCell Subclass
- •Adding New Cells
- •Implementing the Controller’s Code
- •Loading a UITableViewCell from a Nib
- •Designing the Table View Cell in Interface Builder
- •Using the New Table View Cell
- •Grouped and Indexed Sections
- •Building the View
- •Importing the Data
- •Implementing the Controller
- •Adding an Index
- •Implementing a Search Bar
- •Rethinking the Design
- •A Deep Mutable Copy
- •Updating the Controller Header File
- •Modifying the View
- •Modifying the Controller Implementation
- •Copying Data from allNames
- •Implementing the Search
- •Changes to viewDidLoad
- •Changes to Data Source Methods
- •Adding a Table View Delegate Method
- •Adding Search Bar Delegate Methods
- •Adding a Magnifying Glass to the Index
- •Adding the Special Value to the Keys Array
- •Suppressing the Section Header
- •Telling the Table View What to Do
- •Putting It All on the Table
- •Navigation Controller Basics
- •Stacky Goodness
- •A Stack of Controllers
- •Nav, a Hierarchical Application in Six Parts
- •Meet the Subcontrollers
- •The Disclosure Button View
- •The Checklist View
- •The Rows Control View
- •The Movable Rows View
- •The Deletable Rows View
- •The Editable Detail View
- •The Nav Application’s Skeleton
- •Creating the Top-Level View Controller
- •Setting Up the Navigation Controller
- •Adding the Images to the Project
- •First Subcontroller: The Disclosure Button View
- •Creating the Detail View
- •Modifying the Disclosure Button Controller
- •Adding a Disclosure Button Controller Instance
- •Second Subcontroller: The Checklist
- •Creating the Checklist View
- •Adding a Checklist Controller Instance
- •Third Subcontroller: Controls on Table Rows
- •Creating the Row Controls View
- •Adding a Rows Control Controller Instance
- •Fourth Subcontroller: Movable Rows
- •Creating the Movable Row View
- •Adding a Move Me Controller Instance
- •Fifth Subcontroller: Deletable Rows
- •Creating the Deletable Rows View
- •Adding a Delete Me Controller Instance
- •Sixth Subcontroller: An Editable Detail Pane
- •Creating the Data Model Object
- •Creating the Detail View List Controller
- •Creating the Detail View Controller
- •Adding an Editable Detail View Controller Instance
- •But There’s One More Thing. . .
- •Breaking the Tape
- •Creating a Simple Storyboard
- •Dynamic Prototype Cells
- •Dynamic Table Content, Storyboard-Style
- •Editing Prototype Cells
- •Good Old Table View Data Source
- •Will It Load?
- •Static Cells
- •Going Static
- •So Long, Good Old Table View Data Source
- •You Say Segue, I Say Segue
- •Creating Segue Navigator
- •Filling the Blank Slate
- •First Transition
- •A Slightly More Useful Task List
- •Viewing Task Details
- •Make More Segues, Please
- •Passing a Task from the List
- •Handling Task Details
- •Passing Back Details
- •Making the List Receive the Details
- •If Only We Could End with a Smooth Transition
- •Split Views and Popovers
- •Creating a SplitView Project
- •The Storyboard Defines the Structure
- •The Code Defines the Functionality
- •The App Delegate
- •The Master View Controller
- •The Detail View Controller
- •Here Come the Presidents
- •Creating Your Own Popover
- •iPad Wrap-Up
- •Getting to Know Your Settings Bundle
- •The AppSettings Application
- •Creating the Project
- •Working with the Settings Bundle
- •Adding a Settings Bundle to Our Project
- •Setting Up the Property List
- •Adding a Text Field Setting
- •Adding an Application Icon
- •Adding a Secure Text Field Setting
- •Adding a Multivalue Field
- •Adding a Toggle Switch Setting
- •Adding the Slider Setting
- •Adding Icons to the Settings Bundle
- •Adding a Child Settings View
- •Reading Settings in Our Application
- •Retrieving User Settings
- •Creating the Main View
- •Updating the Main View Controller
- •Registering Default Values
- •Changing Defaults from Our Application
- •Keeping It Real
- •Beam Me Up, Scotty
- •Your Application’s Sandbox
- •Getting the Documents Directory
- •Getting the tmp Directory
- •File-Saving Strategies
- •Single-File Persistence
- •Multiple-File Persistence
- •Using Property Lists
- •Property List Serialization
- •The First Version of the Persistence Application
- •Creating the Persistence Project
- •Designing the Persistence Application View
- •Editing the Persistence Classes
- •Archiving Model Objects
- •Conforming to NSCoding
- •Implementing NSCopying
- •Archiving and Unarchiving Data Objects
- •The Archiving Application
- •Implementing the BIDFourLines Class
- •Implementing the BIDViewController Class
- •Using iOS’s Embedded SQLite3
- •Creating or Opening the Database
- •Using Bind Variables
- •The SQLite3 Application
- •Linking to the SQLite3 Library
- •Modifying the Persistence View Controller
- •Using Core Data
- •Entities and Managed Objects
- •Key-Value Coding
- •Putting It All in Context
- •Creating New Managed Objects
- •Retrieving Managed Objects
- •The Core Data Application
- •Designing the Data Model
- •Creating the Persistence View and Controller
- •Persistence Rewarded
- •Managing Document Storage with UIDocument
- •Building TinyPix
- •Creating BIDTinyPixDocument
- •Code Master
- •Initial Storyboarding
- •Creating BIDTinyPixView
- •Storyboard Detailing
- •Adding iCloud Support
- •Creating a Provisioning Profile
- •Enabling iCloud Entitlements
- •How to Query
- •Save Where?
- •Storing Preferences on iCloud
- •What We Didn’t Cover
- •Grand Central Dispatch
- •Introducing SlowWorker
- •Threading Basics
- •Units of Work
- •GCD: Low-Level Queueing
- •Becoming a Blockhead
- •Improving SlowWorker
- •Don’t Forget That Main Thread
- •Giving Some Feedback
- •Concurrent Blocks
- •Background Processing
- •Application Life Cycle
- •State-Change Notifications
- •Creating State Lab
- •Exploring Execution States
- •Making Use of Execution State Changes
- •Handling the Inactive State
- •Handling the Background State
- •Removing Resources When Entering the Background
- •Saving State When Entering the Background
- •A Brief Journey to Yesteryear
- •Back to the Background
- •Requesting More Backgrounding Time
- •Grand Central Dispatch, Over and Out
- •Two Views of a Graphical World
- •The Quartz 2D Approach to Drawing
- •Quartz 2D’s Graphics Contexts
- •The Coordinate System
- •Specifying Colors
- •A Bit of Color Theory for Your iOS Device’s Display
- •Other Color Models
- •Color Convenience Methods
- •Drawing Images in Context
- •Drawing Shapes: Polygons, Lines, and Curves
- •The QuartzFun Application
- •Setting Up the QuartzFun Application
- •Creating a Random Color
- •Defining Application Constants
- •Implementing the QuartzFunView Skeleton
- •Creating and Connecting Outlets and Actions
- •Implementing the Action Methods
- •Adding Quartz 2D Drawing Code
- •Drawing the Line
- •Drawing the Rectangle and Ellipse
- •Drawing the Image
- •Optimizing the QuartzFun Application
- •The GLFun Application
- •Setting Up the GLFun Application
- •Creating BIDGLFunView
- •Updating BIDViewController
- •Updating the Nib
- •Finishing GLFun
- •Drawing to a Close
- •Multitouch Terminology
- •The Responder Chain
- •Responding to Events
- •Forwarding an Event: Keeping the Responder Chain Alive
- •The Multitouch Architecture
- •The Four Touch Notification Methods
- •The TouchExplorer Application
- •The Swipes Application
- •Automatic Gesture Recognition
- •Implementing Multiple Swipes
- •Detecting Multiple Taps
- •Detecting Pinches
- •Defining Custom Gestures
- •The CheckPlease Application
- •The CheckPlease Touch Methods
- •Garçon? Check, Please!
- •The Location Manager
- •Setting the Desired Accuracy
- •Setting the Distance Filter
- •Starting the Location Manager
- •Using the Location Manager Wisely
- •The Location Manager Delegate
- •Getting Location Updates
- •Getting Latitude and Longitude Using CLLocation
- •Error Notifications
- •Trying Out Core Location
- •Updating Location Manager
- •Determining Distance Traveled
- •Wherever You Go, There You Are
- •Accelerometer Physics
- •Don’t Forget Rotation
- •Core Motion and the Motion Manager
- •Event-Based Motion
- •Proactive Motion Access
- •Accelerometer Results
- •Detecting Shakes
- •Baked-In Shaking
- •Shake and Break
- •Accelerometer As Directional Controller
- •Rolling Marbles
- •Writing the Ball View
- •Calculating Ball Movement
- •Rolling On
- •Using the Image Picker and UIImagePickerController
- •Implementing the Image Picker Controller Delegate
- •Road Testing the Camera and Library
- •Designing the Interface
- •Implementing the Camera View Controller
- •It’s a Snap!
- •Localization Architecture
- •Strings Files
- •What’s in a Strings File?
- •The Localized String Macro
- •Real-World iOS: Localizing Your Application
- •Setting Up LocalizeMe
- •Trying Out LocalizeMe
- •Localizing the Nib
- •Localizing an Image
- •Generating and Localizing a Strings File
- •Localizing the App Display Name
- •Auf Wiedersehen
- •Apple’s Documentation
- •Mailing Lists
- •Discussion Forums
- •Web Sites
- •Blogs
- •Conferences
- •Follow the Authors
- •Farewell
- •Index
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