The Core iOS 6 Developer's Cookbook (4th Edition) (Developer's Library)
Author: Sadun, Erica
Date: 2012-11-30
Publisher: Pearson Education
Rights: Copyright © 2013 by Erica Sadun

The Core iOS 6 Developer’s Cookbook

Erica Sadun

Upper Saddle River, NJ • Boston • Indianapolis • San Francisco
New York • Toronto • Montreal • London • Munich • Paris • Madrid
Cape Town • Sydney • Tokyo • Singapore • Mexico City

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals.

The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein.

The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact:

U.S. Corporate and Government Sales
1-800-382-3419
corpsales@pearsontechgroup.com

For sales outside of the U.S., please contact

International Sales
international@pearsoned.com

AirPlay, AirPort, AirPrint, AirTunes, App Store, Apple, the Apple logo, Apple TV, Aqua, Bonjour, the Bonjour logo, Cocoa, Cocoa Touch, Cover Flow, Dashcode, Finder, FireWire, iMac, Instruments, Interface Builder, iOS, iPad, iPhone, iPod, iPod touch, iTunes, the iTunes logo, Leopard, Mac, Mac logo, Macintosh, Multi-Touch, Objective-C, Quartz, QuickTime, QuickTime logo, Safari, Snow Leopard, Spotlight, and Xcode are trademarks of Apple, Inc., registered in the U.S. and other countries. OpenGL, or OpenGL Logo: OpenGL is a registered trademark of Silicon Graphics, Inc. The YouTube logo is a trademark of Google, Inc. Intel, Intel Core, and Xeon are trademarks of Intel Corp. in the United States and other countries.

Visit us on the Web: informit.com/aw

Copyright © 2013 Pearson Education, Inc. All rights reserved. Printed in the United States of America. This publication is protected by copyright, and permission must be obtained from the publisher prior to any prohibited reproduction, storage in a retrieval system, or transmission in any form or by any means, electronic, mechanical, photocopying, recording, or likewise. For information regarding permissions, write to:

Pearson Education, Inc.
Rights and Contracts Department
501 Boylston Street, Suite 900
Boston, MA 02116
Fax (617) 671-3447

ISBN-13: 978-0-321-88421-3
ISBN-10: 0-321-88421-3
Second Printing: May 2013

Editor-in-Chief
Mark Taub

Senior Acquisitions Editor
Trina MacDonald

Senior Development Editor
Chris Zahn

Managing Editor
Kristy Hart

Project Editor
Jovana San Nicolas-Shirley

Copy Editor
Keith Cline

Indexer
WordWise Publishing Services

Proofreader
Debbie Williams

Technical Editors
Duncan Champney
Oliver Drobnik
Rich Wardwell

Editorial Assistant
Olivia Basegio

Cover Designer
Chuti Prasertsith

Compositor
Nonie Ratcliff

I dedicate this book with love to my husband, Alberto, who has put up with too many gadgets and too many SDKs over the years while remaining both kind and patient at the end of the day.

Contents at a Glance

Preface

1 Gestures and Touches

2 Building and Using Controls

3 Alerting the User

4 Assembling Views and Animations

5 View Constraints

6 Text Entry

7 Working with View Controllers

8 Common Controllers

9 Accessibility

10 Creating and Managing Table Views

11 Collection Views

12 A Taste of Core Data

13 Networking Basics

Appendix Objective-C Literals

Index

Table of Contents

Preface

1 Gestures and Touches

Touches

Phases

Touches and Responder Methods

Touching Views

Multi-Touch

Gesture Recognizers

Recipe: Adding a Simple Direct Manipulation Interface

Recipe: Adding Pan Gesture Recognizers

Recipe: Using Multiple Gesture Recognizers Simultaneously

Resolving Gesture Conflicts

Recipe: Constraining Movement

Recipe: Testing Touches

Recipe: Testing Against a Bitmap

Recipe: Drawing Touches Onscreen

Recipe: Smoothing Drawings

Recipe: Using Multi-Touch Interaction

Recipe: Detecting Circles

Recipe: Creating a Custom Gesture Recognizer

Recipe: Dragging from a Scroll View

Recipe: Live Touch Feedback

Enabling Touch Feedback

Intercepting and Forwarding Touch Events

Implementing the TOUCHkit Overlay View

Recipe: Adding Menus to Views

Summary

2 Building and Using Controls

The UIControl Class

Target-Action

Kinds of Controls

Control Events

Buttons

Buttons in Interface Builder

Connecting Buttons to Actions

Recipe: Building Buttons

Multiline Button Text

Adding Animated Elements to Buttons

Adding Extra State to Buttons

Recipe: Animating Button Responses

Recipe: Adding a Slider with a Custom Thumb

Customizing UISlider

Adding Efficiency

Appearance Proxies

Recipe: Creating a Twice-Tappable Segmented Control

Second-Tap Feedback

Controls and Attributes

Working with Switches and Steppers

Recipe: Subclassing UIControl

Creating Controls

Tracking Touches

Dispatching Events

Recipe: Building a Star Slider

Recipe: Building a Touch Wheel

Recipe: Creating a Pull Control

Discoverability

Testing Touches

Recipe: Building a Custom Lock Control

Adding a Page Indicator Control

Recipe: Creating a Customizable Paged Scroller

Building Toolbars

Summary

3 Alerting the User

Talking Directly to Your User Through Alerts

Building Simple Alerts

Alert Delegates

Displaying the Alert

Kinds of Alerts

“Please Wait”: Showing Progress to Your User

Using UIActivityIndicatorView

Using UIProgressView

Recipe: No-Button Alerts

Building a Floating Progress Monitor

Recipe: Creating Modal Alerts with Run Loops

Recipe: Using Variadic Arguments with Alert Views

Presenting Lists of Options

Scrolling Menus

Displaying Text in Action Sheets

Recipe: Building Custom Overlays

Tappable Overlays

Recipe: Basic Popovers

Recipe: Local Notifications

Best Practices

Alert Indicators

Badging Applications

Recipe: Simple Audio Alerts

System Sounds

Vibration

Alerts

Delays

Disposing of System Sounds

Summary

4 Assembling Views and Animations

View Hierarchies

Recipe: Recovering a View Hierarchy Tree

Exploring XIB and Storyboard Views

Recipe: Querying Subviews

Managing Subviews

Adding Subviews

Reordering and Removing Subviews

View Callbacks

Tagging and Retrieving Views

Using Tags to Find Views

Recipe: Naming Views by Object Association

Naming Views in Interface Builder

View Geometry

Frames

Rectangle Utility Functions

Points and Sizes

Transforms

Coordinate Systems

Recipe: Working with View Frames

Adjusting Sizes

CGRects and Centers

Other Geometric Elements

Recipe: Retrieving Transform Information

Retrieving Transform Properties

Testing for View Intersection

Display and Interaction Traits

UIView Animations

Building Animations with Blocks

Recipe: Fading a View In and Out

Recipe: Swapping Views

Recipe: Flipping Views

Recipe: Using Core Animation Transitions

Recipe: Bouncing Views as They Appear

Recipe: Image View Animations

Summary

5 View Constraints

What Are Constraints?

Alignment Rectangles

Declaring Alignment Rectangles

Constraint Attributes

Constraint Math

The Laws of Constraints

Creating Constraints

Basic Constraint Declarations

Visual Format Constraints

Variable Bindings

Format Strings

Orientation

View Names

Connections

Predicates

Metrics

View-to-View Predicates

Priorities

Format String Summary

Storing and Updating Constraints

Recipe: Comparing Constraints

Recipe: Describing Constraints

Recipe: Creating Fixed-Size Constrained Views

Disabling Autosizing Constraints

Starting within View Bounds

Constraining Size

Recipe: Centering Views

Recipe: Setting Aspect Ratio

Aligning Views and Flexible Sizing

Why You Cannot Distribute Views

Recipe: Responding to Orientation Changes

Constraint Macros

Consistent Constraints

Sufficient Constraints

Macros

Debugging Your Constraints

Summary

6 Text Entry

Recipe: Dismissing a UITextField Keyboard

Preventing Keyboard Dismissal

Text Trait Properties

Other Text Field Properties

Recipe: Dismissing Text Views with Custom Accessory Views

Recipe: Adjusting Views Around Keyboards

Recipe: Adjusting Views Around Accessory Views

Testing for Hardware Keyboards

Recipe: Creating a Custom Input View

Recipe: Making Text-Input-Aware Views

Recipe: Adding Custom Input Views to Nontext Views

Adding Input Clicks

Recipe: Building a Better Text Editor (Part I)

Recipe: Building a Better Text Editor (Part II)

Enabling Attributed Text

Controlling Attributes

Other Responder Functionality

Recipe: Text-Entry Filtering

Recipe: Detecting Text Patterns

Rolling Your Own Expressions

Enumerating Regular Expressions

Data Detectors

Using Built-In Type Detectors

Useful Websites

Recipe: Detecting Misspelling in a UITextView

Searching for Text Strings

Summary

7 Working with View Controllers

View Controllers

The UIViewController Class

Navigation Controllers

Tab Bar Controllers

Split View Controllers

Page View Controller

Popover Controllers

Developing with Navigation Controllers and Split Views

Using Navigation Controllers and Stacks

Pushing and Popping View Controllers

Bar Buttons

Recipe: The Navigation Item Class

Titles and Back Buttons

Macros

Recipe: Modal Presentation

Presenting a Custom Modal Information View

Recipe: Building Split View Controllers

Recipe: Creating Universal Split View/Navigation Apps

Recipe: Tab Bars

Remembering Tab State

Recipe: Page View Controllers

Book Properties

Wrapping the Implementation

Exploring the Recipe

Building a Presentation Index

Recipe: Scrubbing Pages in a Page View Controller

Recipe: Custom Containers

Adding and Removing a Child View Controller

Transitioning Between View Controllers

Recipe: Segues

Segues, Interface Builder, and iOS 6

Summary

8 Common Controllers

Image Picker Controller

Image Sources

Presenting the Picker on iPhone and iPad

Recipe: Selecting Images

How To: Adding Photos to the Simulator

The Assets Library Framework

Presenting a Picker

Handling Delegate Callbacks

Recipe: Snapping Photos

Setting Up the Picker

Displaying Images

Saving Images to the Photo Album

Recipe: Recording Video

Creating the Video Recording Picker

Saving the Video

Recipe: Playing Video with Media Player

Recipe: Editing Video

AV Foundation and Core Media

Recipe: Picking and Editing Video

Recipe: E-Mailing Pictures

Creating Message Contents

Recipe: Sending a Text Message

Recipe: Posting Social Updates

Recipe: Activity View Controller

Creating and Presenting the Controller

Adding Services

Items and Services

Recipe: The Quick Look Preview Controller

Implementing Quick Look

Summary

9 Accessibility

Accessibility 101

Accessibility in Interface Builder

Enabling Accessibility

Traits

Labels

Hints

Testing with the Simulator

Broadcasting Updates

Testing Accessibility on the iPhone

Summary

10 Creating and Managing Table Views
iOS Tables

Delegation

Creating Tables

Table Styles

Laying Out the View

Assigning a Data Source

Serving Cells

Registering Cell Classes

Dequeueing Cells

Assigning a Delegate

Recipe: Implementing a Basic Table

Data Source Methods

Responding to User Touches

Table View Cells

Selection Color

Adding in Custom Selection Traits

Recipe: Creating Checked Table Cells

Working with Disclosure Accessories

Recipe: Table Edits

Adding Undo Support

Supporting Undo

Displaying Remove Controls

Handling Delete Requests

Swiping Cells

Reordering Cells

Adding Cells

Recipe: Working with Sections

Building Sections

Counting Sections and Rows

Returning Cells

Creating Header Titles

Customizing Headers and Footers

Creating a Section Index

Handling Section Mismatches

Delegation with Sections

Recipe: Searching Through a Table

Creating a Search Display Controller

Registering Cells for the Search Display Controller

Building the Searchable Data Source Methods

Delegate Methods

Using a Search-Aware Index

Recipe: Adding Pull-to-Refresh to Your Table

Recipe: Adding Action Rows

Coding a Custom Group Table

Creating Grouped Preferences Tables

Recipe: Building a Multiwheel Table

Creating the UIPickerView

Data Source and Delegate Methods

Using Views with Pickers

Using the UIDatePicker

Creating the Date Picker

Summary

11 Collection Views

Collection Views Versus Tables

Practical Implementation Differences

Establishing Collection Views

Controllers

Views

Data Sources and Delegates

Flow Layouts

Scroll Direction

Item Size and Line Spacing

Header and Footer Sizing

Insets

Recipe: Basic Collection View Flows

Recipe: Custom Cells

Recipe: Scrolling Horizontal Lists

Recipe: Introducing Interactive Layout Effects

Recipe: Scroll Snapping

Recipe: Creating a Circle Layout

Creation and Deletion Animation

Powering the Circle Layout

The Layout

Recipe: Adding Gestures to Layout

Recipe: Creating a True Grid Layout

Recipe: Custom Item Menus

Summary

12 A Taste of Core Data

Introducing Core Data

Entities and Models

Building a Model File

Attributes and Relationships

Building Object Classes

Creating Contexts

Adding Data

Examining the Data File

Querying the Database

Setting Up the Fetch Request

Performing the Fetch

Removing Objects

Recipe: Using Core Data for a Table Data Source

Index Path Access

Section Key Path

Section Groups

Index Titles

Table Readiness

Recipe: Search Tables and Core Data

Recipe: Adding Edits to Core Data Table Views

Adding Undo/Redo Support

Creating Undo Transactions

Rethinking Edits

Recipe: A Core Data-Powered Collection View

Summary

13 Networking Basics

Recipe: Checking Your Network Status

Scanning for Connectivity Changes

Recipe: Synchronous Downloads

Recipe: Asynchronous Downloads

One-Call No-Feedback Asynchronous Downloads

Recipe: Using JSON Serialization

Recipe: Converting XML into Trees

Trees

Building a Parse Tree

Summary

Appendix Objective-C Literals

Numbers

Boxing

Enums

Container Literals

Subscripting

Feature Tests

Index

Acknowledgments

This book would not exist without the efforts of Chuck Toporek, who was my editor and whipcracker for many years and multiple publishers. He is now at Apple and deeply missed. There’d be no Cookbook were it not for him. He balances two great skill sets: inspiring authors to do what they think they cannot, and wielding the large “reality trout” of whacking[1] to keep subject matter focused and in the real world. There’s nothing like being smacked repeatedly by a large virtual fish to bring a book in on deadline and with compelling content.

Thanks go as well to Trina MacDonald (my terrific new editor), Chris Zahn (the awesomely talented development editor), and Olivia Basegio (the faithful and rocking editorial assistant who kept things rolling behind the scenes). Also, a big thank you to the entire Addison-Wesley/Pearson production team, specifically Kristy Hart, Jovana San Nicolas-Shirley, Keith Cline, Larry Sweazy, Debbie Williams, Nonie Ratcliff, and Chuti Prasertsith. Thanks also to the crew at Safari for getting my book up in Rough Cuts and for quickly fixing things when technical glitches occurred.

Thanks go as well to Neil Salkind, my agent of many years, to the tech reviewers Oliver Drobnik, Rich Wardwell, and Duncan Champney, who helped keep this book in the realm of sanity rather than wishful thinking, and to all my colleagues, both present and former, at TUAW, Ars Technica, and the Digital Media/Inside iPhone blog.

I am deeply indebted to the wide community of iOS developers, including Jon Bauer, Tim Burks, Matt Martel, Tim Isted, Joachim Bean, Aaron Basil, Roberto Gamboni, John Muchow, Scott Mikolaitis, Alex Schaefer, Nick Penree, James Cuff, Jay Freeman, Mark Montecalvo, August Joki, Max Weisel, Optimo, Kevin Brosius, Planetbeing, Pytey, Michael Brennan, Daniel Gard, Michael Jones, Roxfan, MuscleNerd, np101137, UnterPerro, Jonathan Watmough, Youssef Francis, Bryan Henry, William DeMuro, Jeremy Sinclair, Arshad Tayyeb, Jonathan Thompson, Dustin Voss, Daniel Peebles, ChronicProductions, Greg Hartstein, Emanuele Vulcano, Sean Heber, Josh Bleecher Snyder, Eric Chamberlain, Steven Troughton-Smith, Dustin Howett, Dick Applebaum, Kevin Ballard, Hamish Allan, Oliver Drobnik, Rod Strougo, Kevin McAllister, Jay Abbott, Tim Grant Davies, Maurice Sharp, Chris Samuels, Chris Greening, Jonathan Willing, Landon Fuller, Jeremy Tregunna, Wil Macaulay, Stefan Hafeneger, Scott Yelich, chrallelinder, John Varghese, Andrea Fanfani, J. Roman, jtbandes, Artissimo, Aaron Alexander, Christopher Campbell Jensen, Nico Ameghino, Jon Moody, Julián Romero, Scott Lawrence, Evan K. Stone, Kenny Chan Ching-King, Matthias Ringwald, Jeff Tentschert, Marco Fanciulli, Neil Taylor, Sjoerd van Geffen, Absentia, Nownot, Emerson Malca, Matt Brown, Chris Foresman, Aron Trimble, Paul Griffin, Paul Robichaux, Nicolas Haunold, Anatol Ulrich (hypnocode GmbH), Kristian Glass, Remy “psy” Demarest, Yanik Magnan, ashikase, Shane Zatezalo, Tito Ciuro, Mahipal Raythattha, Jonah Williams of Carbon Five, Joshua Weinberg, biappi, Eric Mock, and everyone at the iPhone developer channels at irc.saurik.com and irc.freenode.net, among many others too numerous to name individually. Their techniques, suggestions, and feedback helped make this book possible. If I have overlooked anyone who helped contribute, please accept my apologies for the oversight.

Special thanks go out to my family and friends, who supported me through month after month of new beta releases and who patiently put up with my unexplained absences and frequent howls of despair. I appreciate you all hanging in there with me. And thanks to my children for their steadfastness, even as they learned that a hunched back and the sound of clicking keys is a pale substitute for a proper mother. My kids provided invaluable assistance over the past few months by testing applications, offering suggestions, and just being awesome people. I try to remind myself on a daily basis how lucky I am that these kids are part of my life.

About the Author

Erica Sadun is the bestselling author, coauthor, and contributor to several dozen books on programming, digital video and photography, and web design, including the widely popular The iOS 5 Developer’s Cookbook. She currently blogs at TUAW.com, and has blogged in the past at O’Reilly’s Mac Devcenter, Lifehacker, and Ars Technica. In addition to being the author of dozens of iOS-native applications, Erica holds a Ph.D. in Computer Science from Georgia Tech’s Graphics, Visualization and Usability Center. A geek, a programmer, and an author, she’s never met a gadget she didn’t love. When not writing, she and her geek husband parent three geeks-in-training, who regard their parents with restrained bemusement, when they’re not busy rewiring the house or plotting global dominance.

Preface

Welcome to a new iOS Cookbook!

With iOS 6, Apple’s mobile device family has reached new levels of excitement and possibility. This Cookbook is here to help you get started developing. This revision introduces all new features announced at the latest WWDC, showing you how to incorporate them into your applications.

For this edition, my publishing team has sensibly split the Cookbook material into manageable print volumes. This book, The Core iOS 6 Developer’s Cookbook, provides solutions for the heart of day-to-day development. It covers all the classes you need for creating iOS applications using standard APIs and interface elements. It offers the recipes you need for working with graphics, touches, and views to create mobile applications.

A second volume, The Advanced iOS 6 Developer’s Cookbook, centers on common frameworks such as Store Kit, Game Kit, and Core Location. It helps you build applications that leverage these special-purpose libraries and move beyond the basics. This volume is for those who have a strong grasp on iOS development and are looking for practical how-to for specialized areas.

Finally, there’s Learning iOS 6: A Hands-on Guide to the Fundamentals of iOS Programming, which will cover much of the tutorial material that used to comprise the first several chapters of the Cookbook. There you’ll find all the fundamental how-to you need to learn iOS 6 development from the ground up. From Objective-C to Xcode, debugging to deployment, Learning iOS 6 teaches you how to get started with Apple’s development tool suite.

As in the past, you can find sample code at GitHub. You’ll find the repository for this Cookbook at https://github.com/erica/iOS-6-Cookbook, all of it refreshed for iOS 6 after WWDC 2012.

If you have suggestions, bug fixes, corrections, or any thing else you’d like to contribute to a future edition, please contact me at erica@ericasadun.com. Let me thank you all in advance. I appreciate all feedback that helps make this a better, stronger book.

—Erica Sadun, September 2012

What You’ll Need

It goes without saying that, if you’re planning to build iOS applications, you’re going to need at least one iOS device to test out your application, preferably a new model iPhone or tablet. The following list covers the basics of what you need to begin:

• Apple’s iOS SDK—You can download the latest version of the iOS SDK from Apple’s iOS Dev Center (http://developer.apple.com/ios). If you plan to sell apps through the App Store, become a paid iOS developer. This costs $99/year for individuals and $299/year for enterprise (that is, corporate) developers. Registered developers receive certificates that allow them to “sign” and download their applications to their iPhone/iPod touch for testing and debugging and to gain early access to prerelease versions of iOS. Free-program developers can test their software on the Mac-based simulator but cannot deploy to device or submit to the App Store.

University Student Program

Apple also offers a University Program for students and educators. If you are a computer science student taking classes at the university level, check with your professor to see whether your school is part of the University Program. For more information about the iPhone Developer University Program, see http://developer.apple.com/support/iphone/university.

• A modern Mac running Mac OS X Lion (v 10.7) or, preferably, Mac OS X Mountain Lion (v 10.8)—You need plenty of disk space for development, and your Mac should have as much RAM as you can afford to put into it.

• An iOS device—Although the iOS SDK includes a simulator for you to test your applications in, you really do need to own iOS hardware to develop for the platform. You can tether your unit to the computer and install the software you’ve built. For real-life App Store deployment, it helps to have several units on hand, representing the various hardware and firmware generations, so that you can test on the same platforms your target audience will use.

• An Internet connection—This connection enables you to test your programs with a live Wi-Fi connection as well as with an EDGE or 3G service.

• Familiarity with Objective-C—To program for the iPhone, you need to know Objective-C 2.0. The language is based on ANSI C with object-oriented extensions, which means you also need to know a bit of C too. If you have programmed with Java or C++ and are familiar with C, you should be able to make the move to Objective-C.

Your Roadmap to Mac/iOS Development

One book can’t be everything to everyone. Try as I might, if we were to pack everything you need to know into this book, you wouldn’t be able to pick it up. (As it stands, this book offers an excellent tool for upper-body development. Please don’t sue if you strain yourself lifting it.) There is, indeed, a lot you need to know to develop for the Mac and iOS platforms. If you are just starting out and don’t have any programming experience, your first course of action should be to take a college-level course in the C programming language. Although the alphabet might start with the letter A, the root of most programming languages, and certainly your path as a developer, is C.

Once you know C and how to work with a compiler (something you’ll learn in that basic C course), the rest should be easy. From there, you’ll hop right on to Objective-C and learn how to program with that alongside the Cocoa frameworks. The flowchart shown in Figure P-1 shows you key titles offered by Pearson Education that can help provide the training you need to become a skilled iOS developer.

Figure P-1. A roadmap to becoming an iOS developer.

Once you know C, you’ve got a few options for learning how to program with Objective-C. If you want an in-depth view of the language, you can either read Apple’s own documentation or pick up one of these books on Objective-C:

• Objective-C Programming: The Big Nerd Ranch Guide, by Aaron Hillegass (Big Nerd Ranch, 2012)

• Learning Objective-C 2.0: A Hands-on Guide to Objective-C for Mac and iOS Developers, by Robert Clair (Addison-Wesley, 2012)

• Programming in Objective-C, Fifth Edition, by Stephen Kochan (Addison-Wesley, 2012)

With the language behind you, next up is tackling Cocoa and the developer tools, otherwise known as Xcode. For that, you have a few different options. Again, you can refer to Apple’s own documentation on Cocoa and Xcode,[2] or if you prefer books, you can learn from the best. Aaron Hillegass, founder of the Big Nerd Ranch in Atlanta,[3] is the coauthor of iOS Programming: The Big Nerd Ranch Guide, Second Edition, and author of Cocoa Programming for Mac OS X, soon to be in its fourth edition. Aaron’s book is highly regarded in Mac developer circles and is the most-recommended book you’ll see on the cocoa-dev mailing list. To learn more about Xcode, look no further than Fritz Anderson’s Xcode 4 Unleashed from Sams Publishing.

Note

There are plenty of other books from other publishers on the market, including the bestselling Beginning iPhone 4 Development, by Dave Mark, Jack Nutting, and Jeff LaMarche (Apress, 2011). Another book that’s worth picking up if you’re a total newbie to programming is Beginning Mac Programming, by Tim Isted (Pragmatic Programmers, 2011). Don’t just limit yourself to one book or publisher. Just as you can learn a lot by talking with different developers, you will learn lots of tricks and tips from other books on the market.

To truly master Mac development, you need to look at a variety of sources: books, blogs, mailing lists, Apple’s own documentation, and, best of all, conferences. If you get the chance to attend WWDC, you’ll know what I’m talking about. The time you spend at those conferences talking with other developers, and in the case of WWDC, talking with Apple’s engineers, is well worth the expense if you are a serious developer.

How This Book Is Organized

This book offers single-task recipes for the most common issues new iOS developers face: laying out interface elements, responding to users, accessing local data sources, and connecting to the Internet. Each chapter groups together related tasks, allowing you to jump directly to the solution you’re looking for without having to decide which class or framework best matches that problem.

The Core iOS 6 Developer’s Cookbook offers you “cut-and-paste convenience,” which means you can freely reuse the source code from recipes in this book for your own applications and then tweak the code to suit your app’s needs.

Here’s a rundown of what you find in this book’s chapters:

• Chapter 1, “Gestures and Touches”—On iOS, the touch provides the most important way that users communicate their intent to an application. Touches are not limited to button presses and keyboard interaction. This chapter introduces direct manipulation interfaces, Multi-Touch, and more. You see how to create views that users can drag around the screen and read about distinguishing and interpreting gestures, as well as how to create custom gesture recognizers.

• Chapter 2, “Building and Using Controls”—Take your controls to the next level. This chapter introduces everything you need to know about how controls work. You discover how to build and customize controls in a variety of ways. From the prosaic to the obscure, this chapter introduces a range of control recipes you can reuse in your programs.

• Chapter 3, “Alerting the User”—iOS offers many ways to provide users with a heads-up, from pop-up dialogs and progress bars to local notifications, popovers, and audio pings. Chapter 3 shows how to build these indications into your applications and expand your user-alert vocabulary. It introduces standard ways of working with these classes and offers solutions that allow you to craft linear programs without explicit callbacks.

• Chapter 4, “Assembling Views and Animations”—The UIView class and its subclasses populate the iOS device screens. This chapter introduces views from the ground up. This chapter dives into view recipes, exploring ways to retrieve, animate, and manipulate view objects. You learn how to build, inspect, and break down view hierarchies and understand how views work together. You discover the role geometry plays in creating and placing views into your interface, and you read about animating views so they move and transform onscreen.

• Chapter 5, “View Constraints”—The iOS 6 software development kit (SDK) revolutionized view layout. Apple’s layout features are about to make your life easier and your interfaces more consistent. This is especially important when working across members of the same device family with different screen sizes—like the iPhone 4S and the iPhone 5. This chapter introduces code-level constraint development. You’ll discover how to create relations between onscreen objects and specify the way iOS automatically arranges your views. The outcome is a set of robust rules that adapt to screen geometry.

• Chapter 6, “Text Entry”—Chapter 6 introduces text recipes that support a wide range of solutions. You’ll read about controlling keyboards, making onscreen elements “text aware,” scanning text, formatting text, and so forth. From text fields and text views to iOS’s inline spelling checkers, this chapter introduces everything you need to know to work with iOS text in your apps.

• Chapter 7, “Working with View Controllers”—Discover the various view controller classes that enable you to enlarge and order the virtual spaces your users interact with, learning from how-to recipes that cover page view controllers, split view controllers, navigation controllers, and more.

• Chapter 8, “Common Controllers”—The iOS SDK provides a wealth of system-supplied controllers that you can use in your day-to-day development tasks. This chapter introduces some of the most popular ones. You read about selecting images from your photo library, snapping photos, and recording and editing videos. You discover how to allow users to compose e-mails and text messages, and how to post updates to social services such as Twitter and Facebook.

• Chapter 9, “Accessibility”—This chapter offers a brief overview of VoiceOver accessibility to extend your audience to the widest possible range of users. You read about adding accessibility labels and hints to your applications and testing those features in the simulator and on the iOS device.

• Chapter 10, “Creating and Managing Table Views”—Tables provide a scrolling interaction class that works particularly well both on smaller devices and as a key player on larger tablets. Many iOS apps center on tables due to their simple natural organization features. Chapter 10 introduces tables. It explains how tables work, what kinds of tables are available to you as a developer, and how you can leverage table features in your applications.

• Chapter 11, “Collection Views”—Collection views use many of the same concepts as tables but provide more power and more flexibility. This chapter walks you through all the basics you need to get started. Prepare to read about creating side-scrolling lists, grids, one-of-a-kind layouts like circles, and more. You’ll learn about integrating visual effects through layout specifications and snapping items into place after scrolling, and you’ll discover how to take advantage of built-in animation support to create the most effective interactions possible.

• Chapter 12, “A Taste of Core Data”—Core Data offers managed data stores that can be queried and updated from your application. It provides a Cocoa Touch–based object interface that brings relational data management out from SQL queries and into the Objective-C world of iOS development. Chapter 12 introduces Core Data. It provides just enough recipes to give you a taste of the technology, offering a jumping-off point for further Core Data learning. You learn how to design managed database stores, add and delete data, and query that data from your code and integrate it into your UIKit table views and collection views.

• Chapter 13, “Networking Basics”—As an Internet-connected device, iOS is particularly suited to subscribing to Web-based services. Apple has lavished the platform with a solid grounding in all kinds of network computing services and their supporting technologies. Chapter 13 surveys common techniques for network computing and offers recipes that simplify day-to-day tasks. You read about network reachability, synchronous and asynchronous downloads, using operation queues, working with the iPhone’s secure keychain to meet authentication challenges, XML parsing, JSON serialization, and more.

• Appendix, “Objective-C Literals”—This appendix introduces new Objective-C constructs for specifying numbers, arrays, and dictionaries.

About the Sample Code

For the sake of pedagogy, this book’s sample code uses a single main.m file. This is not how people normally develop iPhone or Cocoa applications, or, honestly, how they should be developing them, but it provides a great way of presenting a single big idea. It’s hard to tell a story when readers must look through five or seven or nine individual files at once. Offering a single file concentrates that story, allowing access to that idea in a single chunk.

These examples are not intended as stand-alone applications. They are there to demonstrate a single recipe and a single idea. One main.m file with a central presentation reveals the implementation story in one place. Readers can study these concentrated ideas and transfer them into normal application structures, using the standard file structure and layout. The presentation in this book does not produce code in a standard day-to-day best-practices approach. Instead, it reflects a pedagogy that offers concise solutions that you can incorporate back into your work as needed.

Contrast that to Apple’s standard sample code, where you must comb through many files to build up a mental model of the concepts that are being demonstrated. Those examples are built as full applications, often doing tasks that are related to but not essential to what you need to solve. Finding just those relevant portions is a lot of work. The effort may outweigh any gains.

In this book, you’ll find exceptions to this one-file-with-the-story rule: the Cookbook provides standard class and header files when a class implementation is the recipe. Instead of highlighting a technique, some recipes offer these classes and categories (that is, extensions to a preexisting class rather than a new class). For those recipes, look for separate .m and .h files in addition to the skeletal main.m that encapsulates the rest of the story.

For the most part, the examples for this book use a single application identifier: com.sadun.helloworld. This book uses one identifier to avoid clogging up your iOS devices with dozens of examples at once. Each example replaces the previous one, ensuring that your home screen remains relatively uncluttered. If you want to install several examples simultaneously, simply edit the identifier, adding a unique suffix, such as com.sadun.helloworld.table-edits. You can also edit the custom display name to make the apps visually distinct. Your Team Provisioning Profile matches every application identifier, including com.sadun.helloworld. This allows you to install compiled code to devices without having to change the identifier; just make sure to update your signing identity in each project’s build settings.

Getting the Sample Code

You’ll find the source code for this book at github.com/erica/iOS-6-Cookbook on the open-source GitHub hosting site. There, you find a chapter-by-chapter collection of source code that provides working examples of the material covered in this book. Recipes are numbered as they are in the book. Recipe 6 in Chapter 5, for example, appears in the C05 folder in the 06 subfolder.

Any project numbered 00 or that has a suffix (like 05b or 02c) refers to material used to create in-text coverage and figures. For example, Chapter 10’s 00 – Cell Types project helped build Figure 10-2, showing system-supplied table view cells styles. Normally I delete these extra projects. Early readers of this manuscript requested that I include them in this edition. You’ll find a half dozen or so of these extra samples scattered around the repository.

Contribute!

Sample code is never a fixed target. It continues to evolve as Apple updates its SDK and the Cocoa Touch libraries. Get involved. You can pitch in by suggesting bug fixes and corrections as well as by expanding the code that’s on offer. GitHub allows you to fork repositories and grow them with your own tweaks and features, and share those back to the main repository. If you come up with a new idea or approach, let me know. My team and I are happy to include great suggestions both at the repository and in the next edition of this Cookbook.

Getting Git

You can download this Cookbook’s source code using the git version control system. An OS X implementation of git is available at http://code.google.com/p/git-osx-installer. OS X git implementations include both command-line and GUI solutions, so hunt around for the version that best suits your development needs.

Getting GitHub

GitHub (http://github.com) is the largest git-hosting site, with more than 150,000 public repositories. It provides both free hosting for public projects and paid options for private projects. With a custom Web interface that includes wiki hosting, issue tracking, and an emphasis on social networking of project developers, it’s a great place to find new code or collaborate on existing libraries. You can sign up for a free account at their website, allowing you to copy and modify the Cookbook repository or create your own open-source iOS projects to share with others.

Contacting the Author

If you have any comments or questions about this book, please drop me an e-mail message at erica@ericasadun.com, or stop by the github repository and contact me there.

Endnotes

[1] No trouts, real or imaginary, were hurt in the development and production of this book. The same cannot be said for countless cans of Diet Coke, who selflessly surrendered their contents in the service of this manuscript.

[2] See the Cocoa Fundamentals Guide (http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaFundamentals.pdf) for a head start on Cocoa, and for Xcode, see A Tour of Xcode (http://developer.apple.com/mac/library/documentation/DeveloperTools/Conceptual/A_Tour_of_Xcode/A_Tour_of_Xcode.pdf).

[3] Big Nerd Ranch: www.bignerdranch.com.

Editor’s Note: We Want to Hear from You!

As the reader of this book, you are our most important critic and commentator. We value your opinion and want to know what we’re doing right, what we could do better, what areas you’d like to see us publish in, and any other words of wisdom you’re willing to pass our way.

You can e-mail or write me directly to let me know what you did or didn’t like about this book—as well as what we can do to make our books stronger.

Please note that I cannot help you with technical problems related to the topic of this book, and that due to the high volume of mail I receive, I might not be able to reply to every message.

When you write, please be sure to include this book’s title and author as well as your name and phone or e-mail address. I will carefully review your comments and share them with the author and editors who worked on the book.

E-mail:       trina.macdonald@pearson.com

Mail:          Trina MacDonald
Senior Acquisitions Editor
Addison-Wesley/Pearson Education, Inc.
75 Arlington St., Ste. 300
Boston, MA 02116

1. Gestures and Touches

The touch represents the heart of iOS interaction; it provides the core way that users communicate their intent to an application. Touches are not limited to button presses and keyboard interaction. You can design and build applications that work directly with users’ gestures in meaningful ways. This chapter introduces direct manipulation interfaces that go far beyond prebuilt controls. You see how to create views that users can drag around the screen. You also discover how to distinguish and interpret gestures, which are a high-level touch abstraction, and gesture recognizer classes, which automatically detect common interaction styles like taps, swipes, and drags. By the time you finish reading this chapter, you’ll have read about many different ways you can implement gesture control in your own applications.

Touches

Cocoa Touch implements direct manipulation in the simplest way possible. It sends touch events to the view you’re working with. As an iOS developer, you tell the view how to respond. Before jumping into gestures and gesture recognizers, you should gain a solid foundation in this underlying touch technology. It provides the essential components of all touch-based interaction.

Each touch conveys information: where the touch took place (both the current and previous location), what phase of the touch was used (essentially mouse down, mouse moved, mouse up in the desktop application world, corresponding to finger or touch down, moved, and up in the direct manipulation world), a tap count (for example, single-tap/double-tap), and when the touch took place (through a time stamp).

iOS uses what is called a responder chain to decide which objects should process touches. As their name suggests, responders are objects that respond to events and they act as a chain of possible managers for those events. When the user touches the screen, the application looks for an object to handle this interaction. The touch is passed along, from view to view, until some object takes charge and responds to that event.

At the most basic level, touches and their information are stored in UITouch objects, which are passed as groups in UIEvent objects. Each object represents a single touch event, containing single or multiple touches. This depends both on how you’ve set up your application to respond (that is, if you’ve enabled Multi-Touch interaction), and how the user touches the screen (that is, the physical number of touch points).

Your application receives touches in view or view controller classes; both implement touch handlers via inheritance from the UIResponder class. You decide where you process and respond to touches. Trying to implement low-level gesture control in nonresponder classes has tripped up many new iOS developers.

Handling touches in views may seem counterintuitive. You probably expect to separate the way an interface looks (its view) from the way it responds to touches (its controller). Further, using views for direct touch interaction may seem to contradict Model-View-Controller design orthogonality, but it can be necessary and help promote encapsulation.

Consider the case of working with multiple touch-responsive subviews such as game pieces on a board. Building interaction behavior directly into view classes allows you to send meaningful semantically rich feedback to your main application while hiding implementation minutia. For example, you can inform your model that a pawn has moved to Queen’s Bishop 5 at the end of an interaction sequence rather than transmit a meaningless series of vector changes. By hiding the way the game pieces move in response to touches, your model code can focus on game semantics instead of view position updates.

Drawing presents another reason to work in the UIView class. When your application handles any kind of drawing operation in response to user touches, you need to implement touch handlers in views. Unlike views, view controllers don’t implement the all-important drawRect: method needed for providing custom presentations.

Working at the view controller level also has its perks. Instead of pulling out primary handling behavior into a secondary class implementation, adding touch management directly to the view controller allows you to interpret standard gestures, such as tap-and-hold or swipes, where those gestures have meaning. This better centralizes your code and helps tie controller interactions directly to your application model.

In the following sections and recipes, you discover how touches work, how you can incorporate them into your apps, and how you connect what a user sees with how that user interacts with the screen.

Phases

Touches have life cycles. Each touch can pass through any of five phases that represent the progress of the touch within an interface. These phases are as follows:

• UITouchPhaseBegan—Starts when the user touches the screen.

• UITouchPhaseMoved—Means a touch has moved on the screen.

• UITouchPhaseStationary—Indicates that a touch remains on the screen surface but that there has not been any movement since the previous event.

• UITouchPhaseEnded—Gets triggered when the touch is pulled away from the screen.

• UITouchPhaseCancelled—Occurs when the iOS system stops tracking a particular touch. This usually occurs due to a system interruption, such as when the application is no longer active or the view is removed from the window.

Taken as a whole, these five phases form the interaction language for a touch event. They describe all the possible ways that a touch can progress or fail to progress within an interface and provide the basis for control for that interface. It’s up to you as the developer to interpret those phases and provide reactions to them. You do that by implementing a series of responder methods.

Touches and Responder Methods

All subclasses of the UIResponder class, including UIView and UIViewController, respond to touches. Each class decides whether and how to respond. When choosing to do so, they implement customized behavior when a user touches one or more fingers down in a view or window.

Predefined callback methods handle the start, movement, and release of touches from the screen. Corresponding to the phases you’ve already seen, the methods involved are as follows. Notice that UITouchPhaseStationary does not generate a callback.

• touchesBegan:withEvent:—Gets called at the starting phase of the event, as the user starts touching the screen.

• touchesMoved:withEvent:—Handles the movement of the fingers over time.

• touchesEnded:withEvent:—Concludes the touch process, where the finger or fingers are released. It provides an opportune time to clean up any work that was handled during the movement sequence.

• touchesCancelled:WithEvent:—Called when Cocoa Touch must respond to a system interruption of the ongoing touch event.

Each of these is a UIResponder method, often implemented in a UIView or UIViewController subclass. All views inherit basic nonfunctional versions of the methods. When you want to add touch behavior to your application, you override these methods and add a custom version that provides the responses your application needs.

Your classes can implement all or just some of these methods. For real-world deployment, you will always want to add a touches-cancelled event to handle the case of a user dragging his or her finger offscreen or the case of an incoming phone call, both of which cancel an ongoing touch sequence. As a rule, you can generally redirect a canceled touch to your touchesEnded:withEvent: method. This allows your code to complete the touch sequence, even if the user’s finger has not left the screen. Apple recommends overriding all four methods as a best practice when working with touches.

Note

Views have a mode called exclusive touch that prevents touches from being delivered to other views in the same window. When enabled, this property blocks other views from receiving touch events. The primary view handles all touch events exclusively.

Touching Views

When dealing with many onscreen views, iOS automatically decides which view the user touched and passes any touch events to the proper view for you. This helps you write concrete direct manipulation interfaces where users touch, drag, and interact with onscreen objects.

Just because a touch is physically on top of a view doesn’t mean that a view has to respond. Each view can use a “hit test” to choose whether to handle a touch or to let that touch fall through to views beneath it. As you see in the recipes that follow, you can use clever response strategies to decide when your view should respond, particularly when you’re using irregular art with partial transparency.

With touch events, the first view that passes the hit test opts to handle or deny the touch. If it passes, the touch continues to the view’s superview and then works its way up the responder chain until it is handled or until it reaches the window that owns the views. If the window does not process it, the touch moves to the application instance, where it is either processed or discarded.

Note

Touches are limited to UIViews and their subclasses. This includes windows. When developing apps targeted at both iPhone 4S-and-earlier and iPhone 5 platforms, make sure your window extends across the entire screen. Problems are most commonly seen when the iPhone 5 is in portrait mode and apps fail to respond to touches at the bottom of the screen. This occurs when the application’s key window is sized to a 3.5″ screen but used on the larger 4″ model. Without a backing window view, touches will not be recognized. Make sure your apps support both 3.5″ and 4″ screens by extending their key UIWindow to the screen’s full vertical extent.

Multi-Touch
iOS supports both single- and Multi-Touch interfaces. Single-touch GUIs handle just one touch at any time. This relieves you of any responsibility to determine which touch you were tracking. The one touch you receive is the only one you need to work with. You look at its data, respond to it, and wait for the next event.

When working with Multi-Touch—that is, when you respond to multiple onscreen touches at once—you receive an entire set of touches. It is up to you to order and respond to that set. You can, however, track each touch separately and see how it changes over time, providing a richer set of possible user interaction. Recipes for both single-touch and Multi-Touch interaction follow in this chapter.

Gesture Recognizers

With gesture recognizers, Apple added a powerful way to detect specific gestures in your interface. Gesture recognizers simplify touch design. They encapsulate touch methods, so you don’t have to implement these yourself, and provide a target-action feedback mechanism that hides implementation details. They also standardize how certain movements are categorized, as drags or swipes, and so forth.

With gesture recognizer classes, you can trigger callbacks when iOS perceives that the user has tapped, pinched, rotated, swiped, panned, or used a long press. Although their software development kit (SDK) implementations remain imperfect, these detection capabilities simplify development of touch-based interfaces. You can code your own for improved reliability, but a majority of developers will find that the recognizers, as-shipped, are robust enough for many application needs. You’ll find several recognizer-based recipes in this chapter. Because recognizers all basically work in the same fashion, you can easily extend these recipes to your specific gesture recognition requirements.

Here is a rundown of the kinds of gestures built in to recent versions of the iOS SDK:

• Taps—Taps correspond to single or multiple finger taps onscreen. Users can tap with one or more fingers; you specify how many fingers you require as a gesture recognizer property and how many taps you want to detect. You can create a tap recognizer that works with single finger taps, or more nuanced recognizers that look for, for example, two-fingered triple-taps.

• Swipes—Swipes are short, single- or Multi-Touch gestures that move in a single cardinal direction: up, down, left, or right. They cannot move too far off course from that primary direction. You set the direction you want your recognizer to work with. The recognizer returns the detected direction as a property.

• Pinches—To pinch or unpinch, a user must move two fingers together or apart in a single movement. The recognizer returns a scale factor indicating the degree of pinching.

• Rotations—To rotate, a user moves two fingers at once either in a clockwise or counterclockwise direction, producing an angular rotation as the main returned property.

• Pan—Pans occur when users drag their fingers across the screen. The recognizer determines the change in translation produced by that drag.

• Long press—To create a long press, the user touches the screen and holds his or her finger (or fingers) there for a specified period of time. You can specify how many fingers must be used before the recognizer triggers.

Recipe: Adding a Simple Direct Manipulation Interface

Your design focus moves from the UIViewController to the UIView when you work with direct manipulation. The view, or more precisely the UIResponder, forms the heart of direct manipulation development. You create touch-based interfaces by customizing methods that derive from the UIResponder class.

Recipe 1-1 centers on touches in action. This example creates a child of UIImageView called DragView and adds touch responsiveness to the class. Being an image view, it’s important to enable user interaction (that is, set setUserInteractionEnabled to YES). This property affects all the view’s children as well as the view itself. User interaction is generally enabled for most views, but UIImageView is the one exception that stumps most beginners; Apple apparently didn’t think people would generally manipulate them.

The recipe works by updating a view’s center to match the movement of an onscreen touch. When a user first touches any DragView, the object stores the start location as an offset from the view’s origin. As the user drags, the view moves along with the finger—always maintaining the same origin offset so that the movement feels natural. Movement occurs by updating the object’s center. Recipe 1-1 calculates x and y offsets and adjusts the view center by those offsets after each touch movement.

Upon being touched, the view pops to the front. That’s due to a call in the touchesBegan:withEvent: method. The code tells the superview that owns the DragView to bring that view to the front. This allows the active element to always appear foremost in the interface.

This recipe does not implement touches-ended or touches-cancelled methods. Its interests lie only in the movement of onscreen objects. When the user stops interacting with the screen, the class has no further work to do.

Recipe 1-1. Creating a Draggable View

Click here to view code image

@interface DragView : UIImageView
{
CGPoint startLocation;
}
@end

@implementation DragView
- (id) initWithImage: (UIImage *) anImage
{
if (self = [super initWithImage:anImage])
self.userInteractionEnabled = YES;
return self;
}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event
{
// Calculate and store offset, and pop view into front if needed
startLocation = [[touches anyObject] locationInView:self];
[self.superview bringSubviewToFront:self];
}

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
// Calculate offset
CGPoint pt = [[touches anyObject] locationInView:self];
float dx = pt.x - startLocation.x;
float dy = pt.y - startLocation.y;
CGPoint newcenter = CGPointMake(
self.center.x + dx,
self.center.y + dy);

// Set new location
self.center = newcenter;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Adding Pan Gesture Recognizers

With gesture recognizers, you can achieve the same kind of interaction shown in Recipe 1-1 without working quite so directly with touch handlers. Pan gesture recognizers detect dragging gestures. They allow you to assign a callback that triggers whenever iOS senses panning.

Recipe 1-2 mimics Recipe 1-1’s behavior by adding a recognizer to the view when it is first initialized. As iOS detects the user dragging on a DragView instance, the handlePan: callback updates the view’s center to match the distance dragged.

This code uses what might seem like an odd way of calculating distance. It stores the original view location in an instance variable (previousLocation) and then calculates the offset from that point each time the view updates with a pan detection callback. This allows you to use affine transforms or apply the setTranslation:inView: method; you normally do not move view centers, as done here. This recipe creates a dx/dy offset pair and applies that offset to the view’s center, changing the view’s actual frame.

Unlike simple offsets, affine transforms allow you to meaningfully work with rotation, scaling, and translation all at once. To support transforms, gesture recognizers provide their coordinate changes in absolute terms rather than relative ones. Instead of issuing iterative offset vectors, the UIPanGestureRecognizer returns a single vector representing a translation in terms of some view’s coordinate system, typically the coordinate system of the manipulated view’s superview. This vector translation lends itself to simple affine transform calculations and can be mathematically combined with other changes to produce a unified transform representing all changes applied simultaneously.

Here’s what the handlePan: method looks like using straight transforms and no stored state:

Click here to view code image

- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
if (uigr.state == UIGestureRecognizerStateEnded)
{
CGPoint newCenter = CGPointMake(
self.center.x + self.transform.tx,
self.center.y + self.transform.ty);
self.center = newCenter;

CGAffineTransform theTransform = self.transform;
theTransform.tx = 0.0f;
theTransform.ty = 0.0f;
self.transform = theTransform;

return;
}

CGPoint translation = [uigr translationInView:self.superview];
CGAffineTransform theTransform = self.transform;
theTransform.tx = translation.x;
theTransform.ty = translation.y;
self.transform = theTransform;
}

Notice how the recognizer checks for the end of interaction and then updates the view’s position and resets the transform’s translation. This adaptation requires no local storage and would eliminate the need for a touchesBegan:withEvent: method. Without these modifications, Recipe 1-2 has to store previous state.

Recipe 1-2. Using a Pan Gesture Recognizer to Drag Views

Click here to view code image

@interface DragView : UIImageView
{
CGPoint previousLocation;
}
@end

@implementation DragView
- (id) initWithImage: (UIImage *) anImage
{
if (self = [super initWithImage:anImage])
{
self.userInteractionEnabled = YES;
UIPanGestureRecognizer *panRecognizer =
[[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePan:)];
self.gestureRecognizers = @[panRecognizer];
}
return self;
}

- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Promote the touched view
[self.superview bringSubviewToFront:self];

// Remember original location
previousLocation = self.center;
}

- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
CGPoint translation = [uigr translationInView:self.superview];
self.center = CGPointMake(previousLocation.x + translation.x,
previousLocation.y + translation.y);
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Using Multiple Gesture Recognizers Simultaneously

Recipe 1-3 builds off the ideas presented in Recipe 1-2, but with several differences. First, it introduces multiple recognizers that work in parallel. To achieve this, the code uses three separate recognizers—rotation, pinch, and pan—and adds them all to the DragView’s gestureRecognizers property. It assigns the DragView as the delegate for each recognizer. This allows the DragView to implement the gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer: delegate method, enabling these recognizers to work simultaneously. Until this method is added to return YES as its value, only one recognizer will take charge at a time. Using parallel recognizers allows you to, for example, both zoom and rotate in response to a user’s pinch gesture.

Note

UITouch objects store an array of gesture recognizers. The items in this array represent each recognizer that receives the touch object in question. When a view is created without gesture recognizers, its responder methods will be passed touches with empty recognizer arrays.

Recipe 1-3 extends the view’s state to include scale and rotation instance variables. These items keep track of previous transformation values and permit the code to build compound affine transforms. These compound transforms, which are established in Recipe 1-3’s updateTransformWithOffset: method, combine translation, rotation, and scaling into a single result. Unlike the previous recipe, this recipe uses transforms uniformly to apply changes to its objects, which is the standard practice for recognizers.

Finally, this recipe introduces a hybrid approach to gesture recognition. Instead of adding a UITapGestureRecognizer to the view’s recognizer array, Recipe 1-3 demonstrates how you can add the kind of basic touch method used in Recipe 1-1 to catch a triple-tap. In this example, a triple-tap resets the view back to the identity transform. This undoes any manipulation previously applied to the view and reverts it to its original position, orientation, and size. As you can see, the touches began, moved, ended, and cancelled methods work seamlessly alongside the gesture recognizer callbacks, which is the point of including this extra detail in this recipe. Adding a tap recognizer would have worked just as well.

This recipe demonstrates the conciseness of using gesture recognizers to interact with touches.

Recipe 1-3. Recognizing Gestures in Parallel

Click here to view code image

@interface DragView : UIImageView <UIGestureRecognizerDelegate>
{
CGFloat tx; // x translation
CGFloat ty; // y translation
CGFloat scale; // zoom scale
CGFloat theta; // rotation angle
}
@end

@implementation DragView
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// Promote the touched view
[self.superview bringSubviewToFront:self];

// initialize translation offsets
tx = self.transform.tx;
ty = self.transform.ty;
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
if (touch.tapCount == 3)
{
// Reset geometry upon triple-tap
self.transform = CGAffineTransformIdentity;
tx = 0.0f; ty = 0.0f; scale = 1.0f; theta = 0.0f;
}
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}

- (void) updateTransformWithOffset: (CGPoint) translation
{
// Create a blended transform representing translation,
// rotation, and scaling
self.transform = CGAffineTransformMakeTranslation(
translation.x + tx, translation.y + ty);
self.transform = CGAffineTransformRotate(self.transform, theta);
self.transform = CGAffineTransformScale(self.transform, scale, scale);
}

- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
CGPoint translation = [uigr translationInView:self.superview];
[self updateTransformWithOffset:translation];
}

- (void) handleRotation: (UIRotationGestureRecognizer *) uigr
{
theta = uigr.rotation;
[self updateTransformWithOffset:CGPointZero];
}

- (void) handlePinch: (UIPinchGestureRecognizer *) uigr
{
scale = uigr.scale;
[self updateTransformWithOffset:CGPointZero];
}

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

- (id) initWithImage:(UIImage *)image
{
// Initialize and set as touchable
if (!(self = [super initWithImage:image])) return nil;

self.userInteractionEnabled = YES;

// Reset geometry to identities
self.transform = CGAffineTransformIdentity;
tx = 0.0f; ty = 0.0f; scale = 1.0f; theta = 0.0f;

// Add gesture recognizer suite
UIRotationGestureRecognizer *rot = [[UIRotationGestureRecognizer alloc]
initWithTarget:self action:@selector(handleRotation:)];
UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePinch:)];
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:@selector(handlePan:)];
self.gestureRecognizers = @[rot, pinch, pan];
for (UIGestureRecognizer *recognizer in self.gestureRecognizers)
recognizer.delegate = self;

return self;
}
@end

Resolving Gesture Conflicts

Gesture conflicts may arise when you need to recognize several types of gestures at the same time. For example, what happens when you need to recognize both single- and double-taps? Should the single-tap recognizer fire at the first tap, even when the user intends to enter a double-tap? Or should you wait and respond only after it’s clear that the user isn’t about to add a second tap? The iOS SDK allows you to take these conflicts into account in your code.

Your classes can specify that one gesture must fail in order for another to succeed. Accomplish this by calling requireGestureRecognizerToFail:. This is a gesture method that takes one argument, another gesture recognizer. This call creates a dependency between the object receiving this message and another gesture object. What it means is this: For the first gesture to trigger, the second gesture must fail. If the second gesture is recognized, the first gesture will not be.

In real life, this typically means that the recognizer adds a delay until it can be sure that the dependent recognizer has failed. It waits until the second gesture is no longer possible. Only then does the first recognizer complete. If you recognize both single- and double-taps, the application waits a little longer after the first tap. If no second tap happens, the single-tap fires. Otherwise, the double-tap fires, but not both.

Your GUI responses will slow down to accommodate this change. Your single-tap responses become slightly laggy. That’s because there’s no way to tell if a second tap is coming until time elapses. You should never use both kinds of recognizers where instant responsiveness is critical to your user experience. Try, instead, to design around situations where that tap means “do something now” and avoid requiring both gestures for those modes.

Don’t forget that you can add, remove, and disable gesture recognizers on-the-fly. A single-tap may take your interface to a place where it then makes sense to further distinguish between single- and double-taps. When leaving that mode, you could disable or remove the double-tap recognizer to regain better single-tap recognition. Tweaks like this limit interface slowdowns to where they’re absolutely needed.

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Constraining Movement

One problem with the simple approach of the earlier recipes in this chapter is that it’s entirely possible to drag a view offscreen to the point where the user cannot see or easily recover it. Those recipes use unconstrained movement. There is no check to test whether the object remains in view and is touchable. Recipe 1-4 fixes this problem by constraining a view’s movement to within its parent.

It achieves this by limiting movement in each direction, splitting its checks into separate x and y constraints. This two-check approach allows the view to continue to move even when one direction has passed its maximum. If the view has hit the rightmost edge of its parent, for example, it can still move up and down.

Figure 1-1 shows a sample interface. The subviews (flowers) are constrained into the black rectangle in the center of the interface and cannot be dragged off-view. Recipe 1-4’s code is general and can adapt to parent bounds and child views of any size.

Recipe 1-4. Bounded Movement

Click here to view code image

- (void) handlePan: (UIPanGestureRecognizer *) uigr
{
CGPoint translation = [uigr translationInView:self.superview];
CGPoint newcenter = CGPointMake(
previousLocation.x + translation.x,
previousLocation.y + translation.y);

// Restrict movement into parent bounds
float halfx = CGRectGetMidX(self.bounds);
newcenter.x = MAX(halfx, newcenter.x);
newcenter.x = MIN(self.superview.bounds.size.width - halfx,
newcenter.x);

float halfy = CGRectGetMidY(self.bounds);
newcenter.y = MAX(halfy, newcenter.y);
newcenter.y = MIN(self.superview.bounds.size.height - halfy,
newcenter.y);

// Set new location
self.center = newcenter;
}

Figure 1-1. The movement of these flowers is bounded into the black rectangle.

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Testing Touches

Most onscreen view elements for direct manipulation interfaces are not rectangular. This complicates touch detection because parts of the actual view rectangle may not correspond to actual touch points. Figure 1-2 shows the problem in action. The screenshot on the right shows the interface with its touch-based subviews. The shot on the left shows the actual view bounds for each subview. The light gray areas around each onscreen circle fall within the bounds, but touches to those areas should not “hit” the view in question.

Figure 1-2. The application should ignore touches to the gray areas that surround each circle (left). The actual interface (right) uses a clear background (zero alpha values) to hide the parts of the view that are not used.

iOS senses user taps throughout the entire view frame. This includes the undrawn area, such as the corners of the frame outside the actual circles of Figure 1-2, just as much as the primary presentation. That means that unless you add some sort of hit test, users may attempt to tap through to a view that’s “obscured” by the clear portion of the UIView frame.

Visualize your actual view bounds by setting its background color, for example:

dragger.backgroundColor = [UIColor lightGrayColor];

This adds the backsplashes shown in Figure 1-2 (left) without affecting the actual onscreen art. In this case, the art consists of a centered circle with a transparent background. Unless you add some sort of test, all taps to any portion of this frame are captured by the view in question. Enabling background colors offers a convenient debugging aid to visualize the true extent of each view; don’t forget to comment out the background color assignment in production code. Alternatively, you can set a view layer’s border width or style.

Recipe 1-5 adds a simple hit test to the views, determining whether touches fall within the circle. This test overrides the standard UIView’s pointInside:withEvent: method. This method returns either YES (the point falls inside the view) or NO (it does not). The test here uses basic geometry, checking whether the touch lies within the circle’s radius. You can provide any test that works with your onscreen views. As you see in Recipe 1-6, which follows in the next section, that test can be expanded for much finer control.

Be aware that the math for touch detection on Retina display devices remains the same as that for older units. The extra onboard pixels do not affect your gesture-handling math. Your view’s coordinate system remains floating point with subpixel accuracy. The number of pixels the device uses to draw to the screen does not affect UIView bounds and UITouch coordinates. It simply provides a way to provide higher detail graphics within that coordinate system.

Note

Do not confuse the point inside test, which checks whether a point falls inside a view, with the similar-sounding hitTest:withEvent:. The hit test returns the topmost view (closest to the user/screen) in a view hierarchy that contains a specific point. It works by calling pointInside:withEvent: on each view. If the point inside method returns YES, the search continues down that hierarchy.

Recipe 1-5. Providing a Circular Hit Test

Click here to view code image

- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
CGPoint pt;
float HALFSIDE = SIDELENGTH / 2.0f;

// normalize with centered origin
pt.x = (point.x - HALFSIDE) / HALFSIDE;
pt.y = (point.y - HALFSIDE) / HALFSIDE;

// x^2 + y^2 = radius^2
float xsquared = pt.x * pt.x;
float ysquared = pt.y * pt.y;

// If the radius <= 1, the point is within the clipped circle
if ((xsquared + ysquared) <= 1.0) return YES;
return NO;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Testing Against a Bitmap

Unfortunately, most views don’t fall into the simple geometries that make the hit test from Recipe 1-5 so straightforward. The flowers shown in Figure 1-1, for example, offer irregular boundaries and varied transparencies. For complicated art, it helps to test touches against a bitmap. Bitmaps provide byte-by-byte information about the contents of an image-based view, allowing you to test whether a touch hits a solid portion of the image or should pass through to any views below.

Recipe 1-6 extracts an image bitmap from a UIImageView. It assumes that the image used provides a pixel-by-pixel representation of the view in question. When you distort that view (normally by resizing a frame or applying a transform), update the math accordingly. CGPoints can be transformed via CGPointApplyAffineTransform() to handle scaling and rotation changes. Keeping the art at a 1:1 proportion to the actual view pixels simplifies lookup and avoids any messy math. You can recover the pixel in question, test its alpha level, and determine whether the touch has hit a solid portion of the view.

This example uses a cutoff of 85. That corresponds to a minimum alpha level of 33% (that is, 85 / 255). This custom pointInside: method considers any pixel with an alpha level below 33% to be transparent. This is arbitrary. Use any level (or other test for that matter) that works with the demands of your actual GUI.

Note

Unless you need pixel-perfect touch detection, you can probably scale down the bitmap so that it uses less memory and adjust the detection math accordingly.

Recipe 1-6. Testing Touches Against Bitmap Alpha Levels

Click here to view code image

// Return the offset for the alpha pixel at (x,y) for RGBA
// 4-bytes-per-pixel bitmap data
static NSUInteger alphaOffset(NSUInteger x, NSUInteger y, NSUInteger w)
{return y * w * 4 + x * 4;}

// Return the bitmap from a provided image
NSData *getBitmapFromImage(UIImage *image)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL)
{
fprintf(stderr, "Error allocating color space\n");
return NULL;
}

CGSize size = image.size;
unsigned char *bitmapData = calloc(size.width * size.height * 4, 1);
if (bitmapData == NULL)
{
fprintf (stderr, "Error: Memory not allocated!");
CGColorSpaceRelease(colorSpace);
return NULL;
}

CGContextRef context = CGBitmapContextCreate (bitmapData,
size.width, size.height, 8, size.width * 4, colorSpace,
kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace );
if (context == NULL)
{
fprintf (stderr, "Error: Context not created!");
free (bitmapData);
return NULL;
}

CGRect rect = CGRectMake(0.0f, 0.0f, size.width, size.height);
CGContextDrawImage(context, rect, image.CGImage);
unsigned char *data = CGBitmapContextGetData(context);
CGContextRelease(context);

NSData *bytes = [NSData dataWithBytes:data length:size.width * size.height * 4];
free(bitmapData);

return bytes;
}

// Store the bitmap data into an NSData instance variable
- (id) initWithImage: (UIImage *) anImage
{
if (self = [super initWithImage:anImage])
{
self.userInteractionEnabled = YES;
data = getBitmapFromImage(anImage);
}
return self;
}

// Does the point hit the view?
- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event
{
if (!CGRectContainsPoint(self.bounds, point)) return NO;
Byte *bytes = (Byte *)data.bytes;
uint offset = alphaOffset(point.x, point.y, self.image.size.width);
return (bytes[offset] > 85);
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Drawing Touches Onscreen

UIView hosts the realm of direct onscreen drawing. Its drawRect: method offers a low-level way to draw content directly, letting you create and display arbitrary elements using Quartz 2D calls. Touch plus drawing join together to build concrete, manipulatable interfaces.

Recipe 1-7 combines gestures with drawRect to introduce touch-based painting. As a user touches the screen, the TouchTrackerView class builds a Bezier path that follows the user’s finger. To paint the progress as the touch proceeds, the touchesMoved:withEvent: method calls setNeedsDisplay. This, in turn, triggers a call to drawRect:, where the view strokes the accumulated Bezier path. Figure 1-3 shows the interface with a path created in this way.

Figure 1-3. A simple painting tool for iOS requires little more than collecting touches along a path and painting that path with UIKit/Quartz 2D calls.

Although you could adapt this recipe to use gesture recognizers, there’s really no point to it. The touches are essentially meaningless, only provided to create a pleasing tracing. The basic responder methods (namely touches began, moved, and so on) are perfectly capable of handling path creation and management tasks.

This example is meant for creating continuous traces. It does not respond to any touch event without a move. If you want to expand this recipe to add a simple dot or mark, you’ll have to add that behavior yourself.

Recipe 1-7. Touch-Based Painting in a UIView

Click here to view code image

@interface TouchTrackerView : UIView
{
UIBezierPath *path;
}
@end

@implementation TouchTrackerView
- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
// Initialize a new path for the user gesture
self.path = [UIBezierPath bezierPath];
path.lineWidth = 4.0f;

UITouch *touch = [touches anyObject];
[path moveToPoint:[touch locationInView:self]];
}

- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
// Add new points to the path
UITouch *touch = [touches anyObject];
[self.path addLineToPoint:[touch locationInView:self]];
[self setNeedsDisplay];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[path addLineToPoint:[touch locationInView:self]];
[self setNeedsDisplay];
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}

- (void) drawRect:(CGRect)rect
{
// Draw the path
[path stroke];
}

- (id) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
self.multipleTouchEnabled = NO;
return self;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Smoothing Drawings

Depending on the device in use and the amount of simultaneous processing involved, capturing user gestures may produce results that are rougher than desired. Touch events are often limited by CPU demands as well as by shaking hands. A smoothing algorithm can offset those limitations by interpolating between points. Figure 1-4 demonstrates the kind of angularity that derives from granular input and the smoothing that can be applied instead.

Figure 1-4. Catmull-Rom smoothing can be applied in real time to improve arcs between touch events. The images shown here are based on an identical gesture input, with and without smoothing applied.

Catmull-Rom splines create continuous curves between key points. This algorithm ensures that each initial point you provide remains part of the final curve. The resulting path retains the original path’s shape. You choose the number of interpolation points between each pair of reference points. The trade-off lies between processing power and greater smoothing. The more points you add, the more CPU resources you’ll consume. As you can see when using the sample code that accompanies this chapter, a little smoothing goes a long way, even on newer devices. The latest iPad is so responsive that it’s hard to draw a particularly jaggy line in the first place.

Recipe 1-8 demonstrates how to extract points from an existing Bezier path and then apply splining to create a smoothed result. Catmull-Rom uses four points at a time to calculate intermediate values between the second and third points, using a granularity you specify between those points.

Recipe 1-8 provides an example of just one kind of real-time geometric processing you might add to your applications. Many other algorithms out there in the world of computational geometry can be applied in a similar manner.

Recipe 1-8. Creating Smoothed Bezier Paths Using Catmull-Rom Splining

Click here to view code image

#define VALUE(_INDEX_) [NSValue valueWithCGPoint:points[_INDEX_]]

@implementation UIBezierPath (Points)
void getPointsFromBezier(void *info, const CGPathElement *element)
{
NSMutableArray *bezierPoints = (__bridge NSMutableArray *)info;

// Retrieve the path element type and its points
CGPathElementType type = element->type;
CGPoint *points = element->points;

// Add the points if they're available (per type)
if (type != kCGPathElementCloseSubpath)
{
[bezierPoints addObject:VALUE(0)];
if ((type != kCGPathElementAddLineToPoint) &&
(type != kCGPathElementMoveToPoint))
[bezierPoints addObject:VALUE(1)];
}
if (type == kCGPathElementAddCurveToPoint)
[bezierPoints addObject:VALUE(2)];
}

- (NSArray *)points
{
NSMutableArray *points = [NSMutableArray array];
CGPathApply(self.CGPath, (__bridge void *)points, getPointsFromBezier);
return points;
}
@end

#define POINT(_INDEX_) \
[(NSValue *)[points objectAtIndex:_INDEX_] CGPointValue]

@implementation UIBezierPath (Smoothing)
- (UIBezierPath *) smoothedPath: (int) granularity
{
NSMutableArray *points = [self.points mutableCopy];
if (points.count < 4) return [self copy];

// Add control points to make the math make sense
// Via Josh Weinberg
[points insertObject:[points objectAtIndex:0] atIndex:0];
[points addObject:[points lastObject]];

UIBezierPath *smoothedPath = [UIBezierPath bezierPath];

// Copy traits
smoothedPath.lineWidth = self.lineWidth;

// Draw out the first 3 points (0..2)
[smoothedPath moveToPoint:POINT(0)];

for (int index = 1; index < 3; index++)
[smoothedPath addLineToPoint:POINT(index)];

for (int index = 4; index < points.count; index++)
{
CGPoint p0 = POINT(index - 3);
CGPoint p1 = POINT(index - 2);
CGPoint p2 = POINT(index - 1);
CGPoint p3 = POINT(index);

// now add n points starting at p1 + dx/dy up
// until p2 using Catmull-Rom splines
for (int i = 1; i < granularity; i++)
{
float t = (float) i * (1.0f / (float) granularity);
float tt = t * t;
float ttt = tt * t;

CGPoint pi; // intermediate point
pi.x = 0.5 * (2*p1.x+(p2.x-p0.x)*t +
(2*p0.x-5*p1.x+4*p2.x-p3.x)*tt + (3*p1.x-p0.x-3*p2.x+p3.x)*ttt);
pi.y = 0.5 * (2*p1.y+(p2.y-p0.y)*t +
(2*p0.y-5*p1.y+4*p2.y-p3.y)*tt + (3*p1.y-p0.y-3*p2.y+p3.y)*ttt);
[smoothedPath addLineToPoint:pi];
}

// Now add p2
[smoothedPath addLineToPoint:p2];
}

// finish by adding the last point
[smoothedPath addLineToPoint:POINT(points.count - 1)];

return smoothedPath;
}

@end

// Example usage:
// Replace the path with a smoothed version after drawing completes
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
[path addLineToPoint:[touch locationInView:self]];
path = [path smoothedPath:4];
[self setNeedsDisplay];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Using Multi-Touch Interaction

Enabling Multi-Touch interaction in UIView instances lets iOS recover and respond to more than one finger touch at a time. Set the UIView property multipleTouchEnabled to YES or override isMultipleTouchEnabled for your view. When enabled, each touch callback returns an entire set of touches. When that set’s count exceeds 1, you know you’re dealing with Multi-Touch.

In theory, iOS supports an arbitrary number of touches. You can explore that limit by running the following recipe on an iPad, using as many fingers as possible at once. The practical upper limit has changed over time; this recipe modestly demurs from offering a specific number.

When Multi-Touch was first explored on the iPhone, developers did not dream of the freedom and flexibility that Multi-Touch combined with multiple users offered. Adding Multi-Touch to your games and other applications opens up not just expanded gestures but also new ways of creating profoundly exciting multiuser experiences, especially on larger screens like the iPad. I encourage you to include Multi-Touch support in your applications wherever it is practical and meaningful.

Multi-Touch touches are not grouped. If you touch the screen with two fingers from each hand, for example, there’s no way to determine which touches belong to which hand. The touch order is also arbitrary. Although grouped touches retain the same finger order (or, more specifically, the same memory address) for the lifetime of a single touch event, from touch down through movement to release, the correspondence between touches and fingers may and likely will change the next time your user touches the screen. When you need to distinguish touches from each other, build a touch dictionary indexed by the touch objects, as shown in this recipe.

Perhaps it’s a comfort to know that if you need it, the extra finger support has been built in. Unfortunately, when you are using three or more touches at a time, the screen has a pronounced tendency to lose track of one or more of those fingers. It’s hard to programmatically track smooth gestures when you go beyond two finger touches. So instead of focusing on gesture interpretation, think of the Multi-Touch experience more as a series of time-limited independent interactions. You can treat each touch as a distinct item and process it independently of its fellows.

Recipe 1-9 adds Multi-Touch to a UIView by setting its multipleTouchEnabled property and tracing the lines that each finger draws. It does this by keeping track of each touch’s physical address in memory but without pointing to or retaining the touch per Apple’s recommendations.

This is, obviously, an oddball approach, but it has worked reliably throughout the history of the SDK. That’s because each UITouch object persists at a single address throughout the touch-move-release life cycle. Apple recommends against retaining UITouch instances, which is why the integer values of these objects are used as keys in this recipe. By using the physical address as a key, you can distinguish each touch, even as new touches are added or old touches are removed from the screen.

Be aware that new touches can start their life cycle via touchesBegan:withEvent: independently of others as they move, end, or cancel. Your code should reflect that reality.

This recipe expands from Recipe 1-7. Each touch grows a separate Bezier path, which is painted in the view’s drawRect method. Recipe 1-7 essentially started a new drawing at the end of each touch cycle. That worked well for application bookkeeping but failed when it came to creating a standard drawing application, where you expect to iteratively add elements to a picture.

Recipe 1-9 continues adding traces into a composite picture without erasing old items. Touches collect into an ever-growing mutable array, which can be cleared on user demand. This recipe draws in-progress tracing in a slightly lighter color, to distinguish it from paths that have already been stored to the drawing’s stroke array.

Recipe 1-9. Accumulating User Tracings for a Composite Drawing

Click here to view code image

@interface TouchTrackerView : UIView
{
NSMutableArray *strokes;
NSMutableDictionary *touchPaths;
}
- (void) clear;
@end

@implementation TouchTrackerView

// Establish new views with storage initialized for drawing
- (id) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.multipleTouchEnabled = YES;
strokes = [NSMutableArray array];
touchPaths = [NSMutableDictionary dictionary];
}

return self;
}

// On clear remove all existing strokes, but not in-progress drawing
- (void) clear
{
[strokes removeAllObjects];
[self setNeedsDisplay];
}

// Start touches by adding new paths to the touchPath dictionary
- (void) touchesBegan:(NSSet *) touches withEvent:(UIEvent *) event
{
for (UITouch *touch in touches)
{
NSString *key = [NSString stringWithFormat:@"%d", (int) touch];
CGPoint pt = [touch locationInView:self];

UIBezierPath *path = [UIBezierPath bezierPath];
path.lineWidth = IS_IPAD? 8: 4;
path.lineCapStyle = kCGLineCapRound;
[path moveToPoint:pt];

[touchPaths setObject:path forKey:key];
}
}
// Trace touch movement by growing and stroking the path
- (void) touchesMoved:(NSSet *) touches withEvent:(UIEvent *) event
{
for (UITouch *touch in touches)
{
NSString *key =
[NSString stringWithFormat:@"%d", (int) touch];
UIBezierPath *path = [touchPaths objectForKey:key];
if (!path) break;

CGPoint pt = [touch locationInView:self];
[path addLineToPoint:pt];
}

[self setNeedsDisplay];
}

// On ending a touch, move the path to the strokes array
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
NSString *key = [NSString stringWithFormat:@"%d", (int) touch];
UIBezierPath *path = [touchPaths objectForKey:key];
if (path) [strokes addObject:path];
[touchPaths removeObjectForKey:key];
}

[self setNeedsDisplay];
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}

// Draw existing strokes in dark purple, in-progress ones in light
- (void) drawRect:(CGRect)rect
{
[COOKBOOK_PURPLE_COLOR set];
for (UIBezierPath *path in strokes)
[path stroke];

[[COOKBOOK_PURPLE_COLOR colorWithAlphaComponent:0.5f] set];
for (UIBezierPath *path in [touchPaths allValues])
[path stroke];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Note

Apple provides many Core Graphics/Quartz 2D resources on its developer website. Although many of these forums, mailing lists, and source code examples are not iOS specific, they offer an invaluable resource for expanding your iOS Core Graphics knowledge.

Recipe: Detecting Circles

In a direct manipulation interface like iOS, you’d imagine that most people could get by just pointing to items onscreen. And yet, circle detection remains one of the most requested gestures. Developers like having people circle items onscreen with their fingers. In the spirit of providing solutions that readers have requested, Recipe 1-10 offers a relatively simple circle detector, which is shown in Figure 1-5.

Figure 1-5. The dot and the outer ellipse show the key features of the detected circle.

In this implementation, detection uses a multistep test. A time test checks that the stroke was not lingering. A circle gesture should be quickly drawn. There’s an inflection test checking that the touch did not change directions too often. A proper circle includes four direction changes. This test allows for five. There’s a convergence test. The circle must start and end close enough together that the points are somehow related. A fair amount of leeway is needed because when you don’t provide direct visual feedback, users tend to undershoot or overshoot where they began. The pixel distance used here is generous, approximately a third of the view size.

The final test looks at movement around a central point. It adds up the arcs traveled, which should equal 360 degrees in a perfect circle. This example allows any movement that falls within 45 degrees for not-quite-finished circles and 180 degrees for circles that continue on a bit wider, allowing the finger to travel more naturally.

Upon these tests being passed, the algorithm produces a least bounding rectangle and centers that rectangle on the geometric mean of the points from the original gesture. This result is assigned to the circle instance variable. It’s not a perfect detection system (you can try to fool it when testing the sample code), but it’s robust enough to provide reasonably good circle checks for many iOS applications.

Recipe 1-10. Detecting Circles

Click here to view code image

// Retrieve center of rectangle
CGPoint GEORectGetCenter(CGRect rect)
{
return CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
}

// Build rectangle around a given center
CGRect GEORectAroundCenter(CGPoint center, float dx, float dy)
{
return CGRectMake(center.x - dx, center.y - dy, dx * 2, dy * 2);
}

// Center one rect inside another
CGRect GEORectCenteredInRect(CGRect rect, CGRect mainRect)
{
CGFloat dx = CGRectGetMidX(mainRect)-CGRectGetMidX(rect);
CGFloat dy = CGRectGetMidY(mainRect)-CGRectGetMidY(rect);
return CGRectOffset(rect, dx, dy);
}

// Return dot product of two vectors normalized
CGFloat dotproduct (CGPoint v1, CGPoint v2)
{
CGFloat dot = (v1.x * v2.x) + (v1.y * v2.y);
CGFloat a = ABS(sqrt(v1.x * v1.x + v1.y * v1.y));
CGFloat b = ABS(sqrt(v2.x * v2.x + v2.y * v2.y));
dot /= (a * b);

return dot;
}

// Return distance between two points
CGFloat distance (CGPoint p1, CGPoint p2)
{
CGFloat dx = p2.x - p1.x;
CGFloat dy = p2.y - p1.y;

return sqrt(dx*dx + dy*dy);
}

// Offset in X
CGFloat dx(CGPoint p1, CGPoint p2)
{
return p2.x - p1.x;
}

// Offset in Y
CGFloat dy(CGPoint p1, CGPoint p2)
{
return p2.y - p1.y;
}

// Sign of a number
NSInteger sign(CGFloat x)
{
return (x < 0.0f) ? (-1) : 1;
}

// Return a point with respect to a given origin
CGPoint pointWithOrigin(CGPoint pt, CGPoint origin)
{
return CGPointMake(pt.x - origin.x, pt.y - origin.y);
}

// Calculate and return least bounding rectangle
#define POINT(_INDEX_) [(NSValue *)[points \
objectAtIndex:_INDEX_] CGPointValue]

CGRect boundingRect(NSArray *points)
{
CGRect rect = CGRectZero;
CGRect ptRect;

for (int i = 0; i < points.count; i++)
{
CGPoint pt = POINT(i);
ptRect = CGRectMake(pt.x, pt.y, 0.0f, 0.0f);
rect = (CGRectEqualToRect(rect, CGRectZero)) ?
ptRect : CGRectUnion(rect, ptRect);
}

return rect;
}

CGRect testForCircle(NSArray *points, NSDate *firstTouchDate)
{
if (points.count < 2)
{
NSLog(@"Too few points (2) for circle");
return CGRectZero;
}

// Test 1: duration tolerance
float duration = [[NSDate date]
timeIntervalSinceDate:firstTouchDate];
NSLog(@"Transit duration: %0.2f", duration);

float maxDuration = 2.0f;
if (duration > maxDuration)
{
NSLog(@"Excessive duration");
return CGRectZero;
}

// Test 2: Direction changes should be limited to near 4
int inflections = 0;
for (int i = 2; i < (points.count - 1); i++)
{
float deltx = dx(POINT(i), POINT(i-1));
float delty = dy(POINT(i), POINT(i-1));
float px = dx(POINT(i-1), POINT(i-2));
float py = dy(POINT(i-1), POINT(i-2));

if ((sign(deltx) != sign(px)) ||
(sign(delty) != sign(py)))
inflections++;
}

if (inflections > 5)
{
NSLog(@"Excessive inflections");
return CGRectZero;
}

// Test 3: Start and end points near each other
float tolerance = [[[UIApplication sharedApplication]
keyWindow] bounds].size.width / 3.0f;
if (distance(POINT(0), POINT(points.count - 1)) > tolerance)
{
NSLog(@"Start too far from end");
return CGRectZero;
}

// Test 4: Count the distance traveled in degrees.
CGRect circle = boundingRect(points);
CGPoint center = GEORectGetCenter(circle);
float distance = ABS(acos(dotproduct(
pointWithOrigin(POINT(0), center),
pointWithOrigin(POINT(1), center))));
for (int i = 1; i < (points.count - 1); i++)
distance += ABS(acos(dotproduct(
pointWithOrigin(POINT(i), center),
pointWithOrigin(POINT(i+1), center))));

float transitTolerance = distance - 2 * M_PI;

if (transitTolerance < 0.0f) // fell short of 2 PI
{
if (transitTolerance < - (M_PI / 4.0f)) // under 45
{
NSLog(@"Transit too short");
return CGRectZero;
}
}

if (transitTolerance > M_PI) // additional 180 degrees
{
NSLog(@"Transit too long ");
return CGRectZero;
}

return circle;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Creating a Custom Gesture Recognizer

It takes little work to transform the code shown in Recipe 1-10 into a custom recognizer, as introduced in Recipe 1-11. Subclassing UIGestureRecognizer enables you to build your own circle recognizer that you can add to views in your applications.

Start by importing UIGestureRecognizerSubclass.h into your new class. The file declares everything you need your recognizer subclass to override or customize. For each method you override, make sure to call the original version of the method by calling the superclass method before invoking your new code.

Gestures fall into two types: continuous and discrete. The circle recognizer is discrete. It either recognizes a circle or fails. Continuous gestures include pinches and pans, where recognizers send updates throughout their life cycle. Your recognizer generates updates by setting its state property.

Recognizers are basically state machines for fingertips. All recognizers start in the possible state (UIGestureRecognizerStatePossible), and then for continuous gestures pass through a series of changed states (UIGestureRecognizerStateChanged). Discrete recognizers either succeed in recognizing a gesture (UIGestureRecognizerStateRecognized) or fail (UIGestureRecognizerStateFailed), as demonstrated in Recipe 1-11. The recognizer sends actions to its target each time you update state except when the state is set to possible or failed.

The rather long comments you see in Recipe 1-11 belong to Apple, courtesy of the subclass header file. I’ve included them here because they help explain the roles of the key methods that override their superclass. The reset method returns the recognizer back to its quiescent state, allowing it to prepare itself for its next recognition challenge.

The touches began (and so on) methods are called at similar points as their UIResponder analogs, enabling you to perform your tests at the same touch life cycle points. This example waits to check for success or failure until the touches ended callback, and uses the same testForCircle method defined in Recipe 1-10.

Note

As an overriding philosophy, gesture recognizers should fail as soon as possible. When they succeed, you should store information about the gesture in local properties. The circle gesture should save any detected circle so users know where the gesture occurred.

Recipe 1-11. Creating a Gesture Recognizer Subclass

Click here to view code image

#import <UIKit/UIGestureRecognizerSubclass.h>
@implementation CircleRecognizer

// called automatically by the runtime after the gesture state has
// been set to UIGestureRecognizerStateEnded any internal state
// should be reset to prepare for a new attempt to recognize the gesture
// after this is received all remaining active touches will be ignored
// (no further updates will be received for touches that had already
// begun but haven't ended)
- (void)reset
{
[super reset];

points = nil;
firstTouchDate = nil;
self.state = UIGestureRecognizerStatePossible;
}

// mirror of the touch-delivery methods on UIResponder
// UIGestureRecognizers aren't in the responder chain, but observe
// touches hit-tested to their view and their view's subviews
// UIGestureRecognizers receive touches before the view to which
// the touch was hit-tested
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];

if (touches.count > 1)
{
self.state = UIGestureRecognizerStateFailed;
return;
}

points = [NSMutableArray array];
firstTouchDate = [NSDate date];
UITouch *touch = [touches anyObject];
[points addObject: [NSValue valueWithCGPoint:
[touch locationInView:self.view]]];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesMoved:touches withEvent:event];
UITouch *touch = [touches anyObject];
[points addObject: [NSValue valueWithCGPoint:
[touch locationInView:self.view]]];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent: event];
BOOL detectionSuccess = !CGRectEqualToRect(CGRectZero,
testForCircle(points, firstTouchDate));
if (detectionSuccess)
self.state = UIGestureRecognizerStateRecognized;
else
self.state = UIGestureRecognizerStateFailed;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Dragging from a Scroll View
iOS’s rich set of gesture recognizers doesn’t always accomplish exactly what you’re looking for. Here’s an example. Imagine a horizontal scrolling view filled with image views, one next to another, so you can scroll left and right to see the entire collection. Now, imagine that you want to be able to drag items out of that view and add them to a space directly below the scrolling area. To do this, you need to recognize downward touches on those child views (that is, orthogonal to the scrolling direction).

This was the puzzle I encountered while trying to help developer Alex Hosgrove, who was trying to build an application roughly equivalent to a set of refrigerator magnet letters. Users could drag those letters down into a workspace and then play with and arrange the items they’d chosen. There were two challenges with this scenario. First, who owned each touch? Second, what happened after the downward touch was recognized?

Both the scroll view and its children own an interest in each touch. A downward gesture should generate new objects; a sideways gesture should pan the scroll view. Touches have to be shared to allow both the scroll view and its children to respond to user interactions. This problem can be solved using gesture delegates.

Gesture delegates allow you to add simultaneous recognition, so that two recognizers can operate at the same time. You add this behavior by declaring a protocol (UIGestureRecognizerDelegate) and adding a simple delegate method:

Click here to view code image

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

You cannot reassign gesture delegates for scroll views, so you must add this delegate override to the implementation for the scroll view’s children.

The second question, converting a swipe into a drag, is addressed by thinking about the entire touch lifetime. Each touch that creates a new object starts as a directional drag but ends up as a pan once the new view is created. A pan recognizer works better here than a swipe recognizer, whose lifetime ends at the point of recognition.

To make this happen, Recipe 1-12 manually adds that directional-movement detection, outside of the built-in gesture detection. In the end, that working-outside-the-box approach provides a major coding win. That’s because once the swipe has been detected, the underlying pan gesture recognizer continues to operate. This allows the user to keep moving the swiped object without having to raise his or her finger and retouch the object in question.

This implementation detects swipes that move down at least 16 vertical pixels without straying more than 8 pixels to either side. When this code detects a downward swipe, it adds a new DragView (the same class used earlier in this chapter) to the screen and allows it to follow the touch for the remainder of the pan gesture interaction.

At the point of recognition, the class marks itself as having handled the swipe (gestureWasHandled) and disables the scroll view for the duration of the panning event. This allows the child complete control over the ongoing pan gesture without the scroll view reacting to further touch movement.

Recipe 1-12. Dragging Items Out of Scroll Views

Click here to view code image

@implementation DragView

#define DX(p1, p2)    (p2.x - p1.x)
#define DY(p1, p2)    (p2.y - p1.y)

#define SWIPE_DRAG_MIN 16
#define DRAGLIMIT_MAX 8

// Categorize swipe types
typedef enum {
TouchUnknown,
TouchSwipeLeft,
TouchSwipeRight,
TouchSwipeUp,
TouchSwipeDown,
} SwipeTypes;

@implementation PullView
// Create a new view with an embedded pan gesture recognizer
- (id) initWithImage: (UIImage *) anImage
{
if (self = [super initWithImage:anImage])
{
self.userInteractionEnabled = YES;
UIPanGestureRecognizer *pan =
[[[UIPanGestureRecognizer alloc] initWithTarget:self
action:@selector(handlePan:)] autorelease];
pan.delegate = self;
self.gestureRecognizers = @[pan];
}

// Allow simultaneous recognition
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
shouldRecognizeSimultaneouslyWithGestureRecognizer:
(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

// Handle pans by detecting swipes
- (void) handlePan: (UISwipeGestureRecognizer *) uigr
{
// Only deal with scroll view superviews
if (![self.superview isKindOfClass:[UIScrollView class]]) return;

// Extract superviews
UIView *supersuper = self.superview.superview;
UIScrollView *scrollView = (UIScrollView *) self.superview;

// Calculate location of touch
CGPoint touchLocation = [uigr locationInView:supersuper];

// Handle touch based on recognizer state

if(uigr.state == UIGestureRecognizerStateBegan)
{
// Initialize recognizer
gestureWasHandled = NO;
pointCount = 1;
startPoint = touchLocation;
}

if(uigr.state == UIGestureRecognizerStateChanged)
{
pointCount++;

// Calculate whether a swipe has occurred
float dx = DX(touchLocation, startPoint);
float dy = DY(touchLocation, startPoint);

BOOL finished = YES;
if ((dx > SWIPE_DRAG_MIN) && (ABS(dy) < DRAGLIMIT_MAX))
touchtype = TouchSwipeLeft;
else if ((-dx > SWIPE_DRAG_MIN) && (ABS(dy) < DRAGLIMIT_MAX))
touchtype = TouchSwipeRight;
else if ((dy > SWIPE_DRAG_MIN) && (ABS(dx) < DRAGLIMIT_MAX))
touchtype = TouchSwipeUp;
else if ((-dy > SWIPE_DRAG_MIN) && (ABS(dx) < DRAGLIMIT_MAX))
touchtype = TouchSwipeDown;
else
finished = NO;

// If unhandled and a downward swipe, produce a new draggable view
if (!gestureWasHandled && finished &&
(touchtype == TouchSwipeDown))
{
dv = [[DragView alloc] initWithImage:self.image];
dv.center = touchLocation;
[supersuper addSubview:dv];
scrollView.scrollEnabled = NO;
gestureWasHandled = YES;
}
else if (gestureWasHandled)
{
// allow continued dragging after detection
dv.center = touchLocation;
}
}

if(uigr.state == UIGestureRecognizerStateEnded)
{
// ensure that the scroll view returns to scrollable
if (gestureWasHandled)
scrollView.scrollEnabled = YES;
}
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Live Touch Feedback

Have you ever needed to record a demo for an iOS app? There’s always compromise involved. Either you use an overhead camera and struggle with reflections and the user’s hand blocking the screen or you use a tool like Reflection (http://reflectionapp.com) but you only get to see what’s directly on the iOS device screen. These app recordings lack any indication of the user’s touch and visual focus.

Recipe 1-13 offers a simple set of classes (called TOUCHkit) that provide a live touch feedback layer for demonstration use. With it, you can see both the screen that you’re recording as well as the touches that create the interactions you’re trying to present. It provides a way to compile your app for both normal and demonstration deployment. You don’t change your core application to use it. It’s designed to work as a single toggle, providing builds for each use.

To demonstrate this, the code shown in Recipe 1-13 is bundled in the sample code repository with a standard Apple demo. This shows how you can roll the kit into nearly any standard application.

Enabling Touch Feedback

You add touch feedback by switching on the TOUCHkit feature, without otherwise affecting your normal code. To enable TOUCHkit, you set a single flag, compile and use that build for demonstration, complete with touch overlay. For App Store deployment, you disable the flag. The application reverts to its normal behavior, and there are no App Store unsafe calls to worry about:

#define USES_TOUCHkit    1

This recipe assumes that you’re using a standard application with a single primary window. When compiled in, the kit replaces that window with a custom class that captures and duplicates all touches, allowing your application to show the user’s touch bubble feedback.

There is one key code-level change you must make, but it’s a very small one. In your application delegate class, you define a WINDOW_CLASS to use when building your iOS screen:

Click here to view code image

#if USES_TOUCHkit
#import "TOUCHkitView.h"
#import "TOUCHOverlayWindow.h"
#define WINDOW_CLASS TOUCHOverlayWindow
#else
#define WINDOW_CLASS UIWindow
#endif

Then instead of declaring a UIWindow, you use whichever class has been set by the toggle:

Click here to view code image

WINDOW_CLASS *window;
window = [[WINDOW_CLASS alloc]
initWithFrame:[[UIScreen mainScreen] bounds]];

From here, you can set the window’s rootViewController as normal.

Intercepting and Forwarding Touch Events

The key to this overlay lies in intercepting touch events, creating a floating presentation above your normal interface, and then forwarding those events on to your application. A TOUCHkit view lies on top of your interface. The custom window class grabs user touch events and presents them as circles in the TOUCHkit view. It then forwards them as if the user were interacting with a normal UIWindow. To accomplish this, this recipe uses event forwarding.

Event forwarding is achieved by calling a secondary event handler. The TOUCHOverlayWindow class overrides UIWindow’s sendEvent: method to force touch drawing and then invokes its superclass implementation to return control to the normal responder chain.

The following implementation is drawn from Apple’s Event Handling Guide for iOS. It collects all the touches associated with the current event, allowing Multi-Touch as well as single touch interactions, dispatches them to TOUCHkit view layer, and then redirects them to the window via the normal UIWindow sendEvent: implementation:

Click here to view code image

@implementation TOUCHOverlayWindow
- (void) sendEvent:(UIEvent *)event
{
// Collect touches
NSSet *touches = [event allTouches];
NSMutableSet *began = nil;
NSMutableSet *moved = nil;
NSMutableSet *ended = nil;
NSMutableSet *cancelled = nil;

// Sort the touches by phase for event dispatch
for(UITouch *touch in touches) {
switch ([touch phase]) {
case UITouchPhaseBegan:
if (!began) began = [NSMutableSet set];
[began addObject:touch];
break;
case UITouchPhaseMoved:
if (!moved) moved = [NSMutableSet set];
[moved addObject:touch];
break;
case UITouchPhaseEnded:
if (!ended) ended = [NSMutableSet set];
[ended addObject:touch];
break;
case UITouchPhaseCancelled:
if (!cancelled) cancelled = [NSMutableSet set];
[cancelled addObject:touch];
break;
default:
break;
}
}

// Create pseudo-event dispatch
if (began)
[[TOUCHkitView sharedInstance]
touchesBegan:began withEvent:event];
if (moved)
[[TOUCHkitView sharedInstance]
touchesMoved:moved withEvent:event];
if (ended)
[[TOUCHkitView sharedInstance]
touchesEnded:ended withEvent:event];
if (cancelled)
[[TOUCHkitView sharedInstance]
touchesCancelled:cancelled withEvent:event];

// Call normal handler for default responder chain
[super sendEvent: event];
}
@end

Implementing the TOUCHkit Overlay View

The TOUCHkit overlay is a single clear UIView singleton. It’s created the first time the application requests its shared instance, and the call adds it to the application’s key window. The overlay’s user interaction flag is disabled, allowing touches to continue on through the responder chain, even after processing those touches through the standard began/moved/ended/cancelled event callbacks.

The touch processing events draw a circle at each touch point, creating a strong pointer to the touches until that drawing is complete. Recipe 1-13 details the callback and drawing methods that handle that functionality.

Recipe 1-13. Creating a Touch Feedback Overlay View

Click here to view code image

+ (id) sharedInstance
{
// Create shared instance if it does not yet exist
if(!sharedInstance)
{
sharedInstance = [[self alloc] initWithFrame:CGRectZero];
}

// Parent it to the key window
if (!sharedInstance.superview)
{
UIWindow *keyWindow= [UIApplication sharedApplication].keyWindow;
sharedInstance.frame = keyWindow.bounds;
[keyWindow addSubview:sharedInstance];
}

return sharedInstance;
}

// You can override the default touchColor if you want
- (id) initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = NO;
self.multipleTouchEnabled = YES;
touchColor =
[[UIColor whiteColor] colorWithAlphaComponent:0.5f];
touches = nil;
}

return self;
}

// Basic Touches processing
- (void) touchesBegan:(NSSet *)theTouches withEvent:(UIEvent *)event
{
touches = theTouches;
[self setNeedsDisplay];
}

- (void) touchesMoved:(NSSet *)theTouches withEvent:(UIEvent *)event
{
touches = theTouches;
[self setNeedsDisplay];
}

- (void) touchesEnded:(NSSet *)theTouches withEvent:(UIEvent *)event
{
touches = nil;
[self setNeedsDisplay];
}

// Draw touches interactively
- (void) drawRect: (CGRect) rect
{
// Clear
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClearRect(context, self.bounds);

// Fill see-through
[[UIColor clearColor] set];
CGContextFillRect(context, self.bounds);

float size = 25.0f; // based on 44.0f standard touch point

for (UITouch *touch in touches)
{
// Create a backing frame
[[[UIColor darkGrayColor] colorWithAlphaComponent:0.5f] set];
CGPoint aPoint = [touch locationInView:self];
CGContextAddEllipseInRect(context,
CGRectMake(aPoint.x - size, aPoint.y - size, 2 * size, 2 * size));
CGContextFillPath(context);

// Draw the foreground touch
float dsize = 1.0f;
[touchColor set];
aPoint = [touch locationInView:self];
CGContextAddEllipseInRect(context,
CGRectMake(aPoint.x - size - dsize, aPoint.y - size - dsize,
2 * (size - dsize), 2 * (size - dsize)));
CGContextFillPath(context);
}

// Reset touches after use
touches = nil;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Recipe: Adding Menus to Views

The UIMenuController class allows you to add pop-up menus to any item that acts as a first responder. Normally menus are used with text views and text fields, enabling users to select, copy, and paste. Menus also provide a way to add actions to interactive elements like the small drag views used throughout this chapter. Figure 1-6 shows a customized menu. In Recipe 1-14, this menu is presented after long-tapping a flower. The actions will zoom, rotate, or hide the associated drag view.

Figure 1-6. Contextual pop-up menus allow you to add interactive actions to first responder views.

This recipe demonstrates how to retrieve the shared menu controller and assign items to it. Set the menu’s target rectangle (typically the bounds of the view that presents it), adjust the menu’s arrow direction, and update the menu with your changes. The menu can now be set visible.

Menu items work with standard target-action callbacks, but you do not assign the target directly. Their target is always the first responder view. This recipe omits a canPerformAction:withSender: responder check, but you’ll want to add that if some views support certain actions and other views do not. With menus, that support is often tied to state. For example, you don’t want to offer a copy command if the view has no content to copy.

Recipe 1-14. Adding Menus to Interactive Views

Click here to view code image

- (BOOL) canBecomeFirstResponder
{
// Menus only work with first responders
return YES;
}

- (void) pressed: (UILongPressGestureRecognizer *) recognizer
{
if (![self becomeFirstResponder])
{
NSLog(@"Could not become first responder");
return;
}

UIMenuController *menu = [UIMenuController sharedMenuController];
UIMenuItem *pop = [[UIMenuItem alloc]
initWithTitle:@"Pop" action:@selector(popSelf)];
UIMenuItem *rotate = [[UIMenuItem alloc]
initWithTitle:@"Rotate" action:@selector(rotateSelf)];
UIMenuItem *ghost = [[UIMenuItem alloc]
initWithTitle:@"Ghost" action:@selector(ghostSelf)];
[menu setMenuItems:@[pop, rotate, ghost]];

[menu setTargetRect:self.bounds inView:self];
menu.arrowDirection = UIMenuControllerArrowDown;
[menu update];

[menu setMenuVisible:YES];
}

- (id) initWithImage: (UIImage *) anImage
{
if (!(self = [super initWithImage:anImage])) return nil;

self.userInteractionEnabled = YES;
UILongPressGestureRecognizer *pressRecognizer =
[[UILongPressGestureRecognizer alloc] initWithTarget:self
action:@selector(pressed:)];
[self addGestureRecognizer:pressRecognizer];

return self;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 1.

Summary

UIViews and their underlying layers provide the onscreen components your users see. Touch input lets users interact directly with views via the UITouch class and gesture recognizers. As this chapter has shown, even in their most basic form, touch-based interfaces offer easy-to-implement flexibility and power. You discovered how to move views around the screen and how to bound that movement. You read about testing touches to see whether views should or should not respond to them. You saw how to “paint” on a view and how to attach recognizers to views to interpret and respond to gestures. Here’s a collection of thoughts about the recipes in this chapter that you might want to ponder before moving on:

• Be concrete. iOS devices have perfectly good touch screens. Why not let your users drag items around the screen or trace lines with their fingers? It adds to the reality and the platform’s interactive nature.

• Users typically have five fingers per hand. iPads, in particular, offer a lot of screen real estate. Don’t limit yourself to a one-finger interface when it makes sense to expand your interaction into Multi-Touch territory for one or more users, screen space allowing.

• A solid grounding in Quartz graphics and Core Animation will be your friend. Using drawRect:, you can build any kind of custom UIView presentation you want, including text, Bezier curves, scribbles, and so forth.

• If Cocoa Touch doesn’t provide the kind of specialized gesture recognizer you’re looking for, write your own. It’s not that hard, although it helps to be as thorough as possible when considering the states your custom touch might pass through.

• Use Multi-Touch whenever possible, especially when you can expand your application to invite more than one user to touch the screen at a time. Don’t limit yourself to one-person, one-touch interactions when a little extra programming will open doors of opportunity for multiuser use.

• Explore! This chapter only touched lightly on the ways you can use direct manipulation in your applications. Use this material as a jumping-off point to explore the full vocabulary of the UITouch class.

2. Building and Using Controls

The UIControl class provides the basis for many iOS interactive elements, such as buttons, text fields, sliders, and switches. These view objects have more in common than just deriving from their ancestor class. Controls all use similar layout paradigms and target-action triggers. Learning to create a single control, no matter how specialized, teaches you how all controls work. Controls may appear visually unique and specialized, but use a single design pattern. This chapter introduces controls and their use. You discover how to build and customize controls in a variety of ways. From the prosaic to the obscure, this chapter introduces a range of control recipes you can reuse in your programs.

The UIControl Class

In iOS, controls refer to the members of a library of prebuilt objects designed for user interaction. Controls consist of buttons and text fields, sliders and switches, along with other Apple-supplied objects. A control’s role is to transform user interactions into callbacks. Users touch and manipulate controls and in doing so communicate with your application.

The UIControl class lies at the root of the control class tree. Controls are subclasses of UIView from which they inherit all attributes for display and layout. The subclass adds a response mechanism that enhances views with interactivity.

All controls implement ways to dispatch messages when users interact with their interface. Controls send messages using a target-action pattern. When you define a new control, you tell it who receives messages (the target), what messages to send (the action), and when to send those messages (the triggering condition, such as a user completing a touch within its bounds).

Target-Action

The target-action design pattern offers a low-level way of responding to user interactions. You encounter these almost exclusively for children of the UIControl class. With target-action, you tell the control to message a given object when a specific user event takes place. For example, you’d specify which object receives a selector when users press a button or adjusts a slider.

You supply an arbitrary selector. The selector is not checked by the compiler at runtime, so use caution in preparing your code. The following snippet sets a target-action pair that calls the playSound: selector when a user releases a touch inside a button. If the target (self) does not implement that method, the application crashes at runtime with an undefined method call error:

Click here to view code image

[button addTarget:self action:@selector(playSound:)
forControlEvents:UIControlEventTouchUpInside];

Target-actions do not rely on an established method vocabulary the way delegates do. Unlike delegates and their required protocols, there’s no guarantees about a playSound: implementation. It’s up to the developer to make sure that the callback refers to an existing method. A cautious programmer will test the target before assigning a target-action pair with a given selector. Here’s an example:

Click here to view code image
if ([self respondsToSelector:@selector(playSound:)])
[button addTarget:self action:@selector(playSound:)
forControlEvents:UIControlEventTouchUpInside];

Standard UIControl target-action pairs always pass either zero, one, or two arguments. These optional arguments offer the interaction object (such as a button, slider, or switch that has been manipulated) and a UIEvent object that represents the user’s input. Your selector can choose to pass any or all of these. In the preceding example, the selector uses one argument, the UIButton instance that was tapped. This self-reference, where the triggered object is included with the call, enables you to build more general action code that knows which control produced the callback.

Kinds of Controls

System-supplied members of the UIControl family include buttons, segmented controls, switches, sliders, page controls, and text fields. Each of these controls can be found in Interface Builder’s Object Library (Command-Control-Option-3, View > Utilities > Show Object Library, Select Controls), as shown in Figure 2-1.

Figure 2-1. Interface Builder provides its available controls in the Object Library. From the top-left, these are labels (UILabel), buttons (UIButton), segmented controls (UISegmentedControl), text fields (UITextField), sliders (UISlider), switch (UISwitch), activity indicator and progress indicator (UIActivityIndicatorView and UIProgressView, these are not technically controls), page control (UIPageControl), and stepper (UIStepper).

Control Events

Controls respond primarily to three kinds of events: those based on touch, those based on value, and those based on edits. Table 2-1 lists the full range of event types available to controls.

Table 2-1. UIControl Event Types

For the most part, events break down along the following lines. Buttons use touch events; the single UIControlEventTouchUpInside event accounts for nearly all button interaction and is the default event created by Interface Builder (IB) connections. Value events (for example, UIControlEventValueChanged) correspond to user-initiated adjustments to segmented controls, switches, sliders, and page controls. Refresh controls for tables also trigger value events. When users switch, slide, or tap those objects, the control value changes. UITextField objects trigger editing events. Users cause these events by tapping into (or out from) the text field, or by changing the text field contents.

As with all iOS GUI elements, you can lay out controls in Xcode’s Interface Builder screen or instantiate them in code. This chapter discusses some IB approaches but focuses more intently on code-based solutions. IB layout, once mastered, remains pretty much the same regardless of the item involved. You place an object into the interface, customize it with inspectors, and connect it to other IB objects.

Buttons

UIButton instances provide simple buttons. Users can tap them to trigger a callback via target-action programming. You specify how the button looks, what art it uses, and what text it displays.

iOS offers two ways to build buttons. You can use a typed button, which ship with several predesigned styles, or build a custom button from scratch. The current iOS software development kit (SDK) offers the following precooked types. As you can see, the buttons available are not general purpose. They were added to the SDK primarily for Apple’s convenience, not yours. That’s because, as a rule, Apple does not add UI features that they do not primarily consume themselves. Nonetheless, you can use these in your programs as needed if you follow Apple’s Human Interface Guidelines (HIG). Figure 2-2 shows each button.

Figure 2-2. iOS SDK offers five typed buttons, which you can access in IB or build directly in to your applications. From left to right, these are the Detail Disclosure button, the Info Light and Info Dark buttons, the Contact Add button, and the Rounded Rectangle.

• Detail Disclosure—This is the same round, blue circle with the chevron you see when you add a detail disclosure accessory to table cells. Detail disclosures are used in tables to lead to a screen that shows details about the currently selected cell.

• Info Light and Info Dark—These two buttons offer a small circled i like you see on a Macintosh’s Dashboard widget and are meant to provide access to an information or settings screen. These are used in many basic applications to flip the view from one side to the other.

• Contact Add—This round, blue circle has a white + in its center and can be seen in the Mail application for adding new recipients to a mail message.

• Rounded Rectangle—This button provides a simple rounded rectangle that surrounds the button text. In its default state it is not an especially attractive button (that is, it’s not very “Apple” looking), but it is simple to program and use in your applications.

Strictly speaking the UIButtonTypeCustom is also a “precooked” button in that it adds a label. As it offers no further appearance support, most developers can treat it as a fully custom button.

To use a typed button in code, allocate it, set its frame, and add a target. Don’t worry about adding custom art or creating the overall look of the button. The SDK takes care of all that. For example, here’s how to build a simple rounded rectangle button:

Click here to view code image

UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[button setFrame: CGRectMake(0.0f, 0.0f, 80.0f, 30.0f)];
[button setCenter: self.view.center];
[button setTitle:@"Beep" forState:UIControlStateNormal];
[button addTarget:self action:@selector(playSound)
forControlEvents:UIControlEventTouchUpInside];
[contentView addSubview:button];

To build one of the other standard button types, omit the title line. Rounded rectangles is the only precooked button type that uses a title.

Most buttons use the “touch up inside” trigger, where the user’s touch ends inside the button’s bounds. iOS UI standards allow a user to cancel a button press by pulling his or her finger off a button before releasing the finger from the screen. The UIControlEventTouchUpInside event choice mirrors that standard.

When using a precooked button, you must conform to Apple’s mobile Human Interface Guidelines (HIG) on how these buttons can be used. Adding a detail disclosure, for example, to lead to an information page can get your application rejected from the App Store. It might seem a proper extrapolation of the button’s role, but if it does not meet the exact wording of how Apple expects the button to be used, it may not pass review. (Obviously, this depends on the reviewer, but you’ll be hard pressed to defend an application that violates the HIG.) To avoid potential issues, you may want to use rounded rectangle and custom buttons wherever possible.

Buttons in Interface Builder

Buttons appear by default in the Interface Builder library as Rounded Rect Button objects (see Figure 2-1, top row, second from the left). To use them, drag them into your interface. You can then change them to another button type via the attributes inspector (View > Utility > Show Attributes Inspector, Command-Option-4). A button-type pop-up appears at the top of the inspector. Use this pop-up menu to select the button type you want to use.

If your button uses text, you can enter that text in the Title field. The Image and Background pull-downs let you choose a primary and background image for the button. Each button provides four configuration settings. The four button states are Default (the button in its normal state), Highlighted (when a user is currently touching the button), Selected (an “on” version of the button, for buttons that support toggled states), and Disabled (when the button is unavailable for user interaction).

Changes in the Object Attributes > Button > State Configuration section apply to the currently selected configuration. You might, for example, use a different button text color for a button in its default state versus its disabled state.

To preview each state, locate the three check boxes in Object Attributes > Control > Content. The Highlighted, Selected, and Enabled options let you set the button state. After previewing, and before you compile, make sure you returned the button to the actual state it needs to be in when you first run the application.

Connecting Buttons to Actions

When you Control-drag (right-drag) from a button to an IB object such as the File’s Owner view controller in the XIB editor, IB presents a pop-up menu of actions to choose from. These actions are polled from the target object’s available IBActions. Connecting to an action creates a target-action pair for the button’s touch up inside event. You can also Control-drag from the button to your code, where Xcode will add empty function definitions to your implementation file.

Alternatively, you can Control-click (right-click) the button, scroll down to Touch Up Inside, and drag from the unfilled dot to the target you want to connect to (in this case, the File’s Owner object). The same pop-up menu appears with its list of available actions.

Note

In IB, you also encounter buttons that look like button views and act like views but are not, in fact, views. Bar button items (UIBarButtonItem) store the properties of toolbar and navigation bar buttons but are not buttons themselves. The toolbars and navigation bars build buttons internally to represent these logical entities.

Recipe: Building Buttons

When using the UIButtonTypeCustom style, you supply all button art. The number of images depends on how you want the button to work. For a simple pushbutton, you might add a single background image and vary the label color to highlight when the button is pushed.

For a toggle-style button, you might use four images: for the “off” state in a normal presentation, the off state when highlighted (that is, pressed), and two more for the “on” state. You choose and design the interaction details, making sure to add local state (the Boolean isOn instance variable in Recipe 2-1) to extend a simple pushbutton to a toggle. If you supply a normal image to buttons and do not specify highlight or disabled images, iOS automatically generates these variants for you.

Recipe 2-1 builds a button that toggles on and off, demonstrating the basic detail that goes into building custom buttons. When tapped, the button switches its art from green (on) to red (off), or from red to green. This allows your (noncolorblind) users to instantly identify a current state. The displayed text reinforces the state setting. Figure 2-3 (left) shows the button created by this recipe.

Figure 2-3. Use UIImage stretching to resize art for arbitrary button widths. Set the left cap width to specify where the stretching can take place.

The UIImage resizable image calls in this recipe play an important role in button creation. Resizable images enable you to create buttons of arbitrary width, turning circular art into lozenge-shaped buttons. You specify the caps (that is, the art that should not be stretched). In this case, the cap is 110 pixels wide on the left and right. If you were to change the button width from the 300 pixels used in this recipe to 220, the button loses the middle stretch, as shown in Figure 2-3 (right).

Buttons can assign image and background image by state. Images set the actual content of the button. Background images provide resizable backdrops over which images and title text may appear. Recipe 2-1 uses background images, letting the button’s built-in title field float over the supplied art.

Note

You can round the corners of your views and buttons to different degrees by adjusting layer properties. Adding the Quartz Core framework to your project lets you access view layers, where you can set the layer’s cornerRadius property programmatically. Then set the view’s clipsToBounds property to YES to achieve that Apple look.

Recipe 2-1. Building a UIButton That Toggles On and Off

Click here to view code image

#define CAPWIDTH    110.0f
#define INSETS      (UIEdgeInsets){0.0f, CAPWIDTH, 0.0f, CAPWIDTH}
#define CAPWIDTH    110.0f
#define INSETS      (UIEdgeInsets){0.0f, CAPWIDTH, 0.0f, CAPWIDTH}
#define BASEGREEN   [[UIImage imageNamed:@"green-out.png"] \
resizableImageWithCapInsets:INSETS]
#define PUSHGREEN   [[UIImage imageNamed:@"green-in.png"] \
resizableImageWithCapInsets:INSETS]
#define BASERED     [[UIImage imageNamed:@"red-out.png"]  \
resizableImageWithCapInsets:INSETS]
#define PUSHRED     [[UIImage imageNamed:@"red-in.png"]  \
resizableImageWithCapInsets:INSETS]

- (void) toggleButton: (UIButton *) aButton
{
if ((isOn = !isOn))
{
[self setBackgroundImage:BASEGREEN forState:UIControlStateNormal];
[self setBackgroundImage:PUSHGREEN forState:UIControlStateHighlighted];
[self setTitle:@"On" forState:UIControlStateNormal];
[self setTitle:@"On" forState:UIControlStateHighlighted];
}
else
{
[self setBackgroundImage:BASERED forState:UIControlStateNormal];
[self setBackgroundImage:PUSHRED forState:UIControlStateHighlighted];
[self setTitle:@"Off" forState:UIControlStateNormal];
[self setTitle:@"Off" forState:UIControlStateHighlighted];
}
}

+ (id) button
{
PushButton *button = [PushButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0.0f, 0.0f, 300.0f, 233.0f);

// Set up the button aligment properties
button.contentVerticalAlignment =
UIControlContentVerticalAlignmentCenter;
button.contentHorizontalAlignment =
UIControlContentHorizontalAlignmentCenter;

// Set the font and color
[button setTitleColor:
[UIColor whiteColor] forState:UIControlStateNormal];
[button setTitleColor:
[UIColor lightGrayColor] forState:UIControlStateHighlighted];
button.titleLabel.font = [UIFont boldSystemFontOfSize:24.0f];

// Set up the art
[button setBackgroundImage:BASEGREEN forState:UIControlStateNormal];
[button setBackgroundImage:PUSHGREEN forState:UIControlStateHighlighted];
[button setTitle:@"On" forState:UIControlStateNormal];
[button setTitle:@"On" forState:UIControlStateHighlighted];
button.isOn = YES;

// Add action. Client can add one too.
[button addTarget:button action:@selector(toggleButton:)
forControlEvents: UIControlEventTouchUpInside];

return button;
}

Multiline Button Text

The button’s titleLabel property allows you to modify title attributes such as its font and line break mode. Here, the font is set to a very large value (basically ensuring that the text needs to wrap to display correctly) and used with word wrap and centered alignment:

Click here to view code image
button.titleLabel.font = [UIFont boldSystemFontOfSize:36.0f];
[button setTitle:@"Lorem Ipsum Dolor Sit" forState:
UIControlStateNormal];
button.titleLabel.textAlignment = UITextAlignmentCenter;
button.titleLabel.lineBreakMode = UILineBreakModeWordWrap;

By default, button labels stretch from one end of your button to the other. This means that text may extend farther out than you might otherwise want, possibly beyond the edges of your button art. To fix this problem, you can force carriage returns in word wrap mode by embedding new line literals (that is, \n) into the text. This allows you to control how much text appears on each line of the button title.

Adding Animated Elements to Buttons

When working with buttons, you can creatively layer art in front of or behind them. Use the standard UIView hierarchy to do this, making sure to disable user interaction for any view that might otherwise obscure your button (setUserInteractionEnabled:NO). The image view contents “leak” through to the viewer, enabling you to add live animation elements to the button.

The sample art used in Recipe 2-1 is translucent, allowing you to experiment with this approach. The sample code for this Recipe adds optional butterfly art that you can layer behind the button and animate.

Animated elements are particularly helpful when you’re trying to show state, such as an operation in progress. They can communicate to users why a button has become unresponsive or creates a different reaction to being pressed. For example, a turbo-enhanced button in a game might provide extra force when tapped. An animated visual helps users identify the change in functionality.

Separating art and text away from button implementation can play other roles in your development. Adding these elements behind or on top of an otherwise empty button allows you to localize both graphic design and phrasing based on your intended deployment without having to redesign the button directly.

Adding Extra State to Buttons

Recipe 2-1 created a two-state button, providing visuals for on and off states. At times, you may want to implement buttons with further easy-to-distinguish states. Games provide the most common example of this. Many developers implement buttons that typically showcase four states: locked levels, unlocked-but-not-played, unlocked-and-partially-played, unlocked-and-mastered.

This recipe used a simple Boolean toggle (the isOn instance variable) to store the on/off state and to select the art used (in the toggleButton: method) based on that state. You can easily expand this example for a wider range of art and button states by storing the state as an integer, and providing a switch statement for art selection.

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Animating Button Responses

There’s more to UIControl instances than frames and target-action. All controls inherit from the UIView class. This means you can use UIView animation blocks when working with controls just as you would with standard views. Recipe 2-2 builds a toggle switch that zooms itself whenever a user touches it, returning to its original size when the touch leaves the control.

This recipe creates a livelier interaction element that helps focus greater attention on the control in question.

Note

To add a little iOS 6 flare to your instances, take note that buttons now support delicious NSAttributedString values via setAttributedTitleForState:. Recipe 2-4, which follows later in this chapter, updates a segmented control’s text color using this method. Attributed strings are discussed in more detail in the Core Text chapter in The Advanced iOS 6 Developer’s Cookbook.

Recipe 2-2. Adding UIView Animation Blocks to Controls

Click here to view code image

- (void) zoomButton: (id) sender
{
// Slightly enlarge the button
[UIView animateWithDuration:0.2f animations:^{
button.transform = CGAffineTransformMakeScale(1.1f, 1.1f);}];
}

- (void) relaxButton: (id) sender
{
// Return the button to its normal size
[UIView animateWithDuration:0.2f animations:^{
button.transform = CGAffineTransformIdentity;}];
}

- (void) toggleButton: (UIButton *) button
{
if (isOn = !isOn)
{
[button setTitle:@"On" forState:UIControlStateNormal];
[button setTitle:@"On"
forState:UIControlStateHighlighted];
[button setBackgroundImage:BASEGREEN
forState:UIControlStateNormal];
[button setBackgroundImage:PUSHGREEN
forState:UIControlStateHighlighted];
}
else
{
[button setTitle:@"Off" forState:UIControlStateNormal];
[button setTitle:@"Off"
forState:UIControlStateHighlighted];
[button setBackgroundImage:BASERED
forState:UIControlStateNormal];
[button setBackgroundImage:PUSHRED
forState:UIControlStateHighlighted];
}

[self relaxButton:button];
}

+ (id) button
{
PushButton *button = [PushButton buttonWithType:UIButtonTypeCustom];
button.frame = CGRectMake(0.0f, 0.0f, 220.0f, 233.0f);

// Add actions
[button addTarget:button action:@selector(toggleButton:)
forControlEvents: UIControlEventTouchUpInside];
[button addTarget:button action:@selector(zoomButton:)
forControlEvents: UIControlEventTouchDown |
UIControlEventTouchDragInside | UIControlEventTouchDragEnter];
[button addTarget:button action:@selector(relaxButton:)
forControlEvents: UIControlEventTouchDragExit |
UIControlEventTouchCancel | UIControlEventTouchDragOutside];

return button;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Adding a Slider with a Custom Thumb

UISlider instances provide a control allowing users to choose a value by sliding a knob (called its thumb) between its left and right extent. You’ll have seen sliders in the iPod/Music application, where the class is used to control volume.

Slider values default to 0.0 for the minimum and 1.0 for the maximum, although you can easily customize these in the IB attributes inspector or by setting the minimumValue and maximumValue properties. To stylize the ends of the control, add in a related pair of images (minimumValueImage and maximumValueImage) that reinforce those settings. For example, you might show a snowman on one end and a steaming cup of tea on the other for a slider that controls temperature settings. You can also set the color of the track before and after the thumb. Adjust the minimumTrackTintColor and maximumTrackTintColor properties (available in iOS 5.0 and later).

The slider’s continuous property controls whether a slider continually sends value updates as a user drags the thumb. When set to NO (the default is YES), the slider only sends an action event when the user releases the thumb.

Customizing UISlider

In addition to setting minimum and maximum images, the UISlider class lets you directly update its thumb component. You can set a thumb to whatever image you like by calling setThumbImage:forState:. Recipe 2-3 takes advantage of this option to dynamically build thumb images on the fly, as shown in Figure 2-4. The indicator bubble appears above the user’s finger as part of the custom-built thumb. This bubble provides instant feedback both textually (the number inside the bubble) and graphically (the shade of the bubble reflects the slider value, moving from black to white as the user drags).

Figure 2-4. Core Graphics/Quartz calls enable this slider’s thumb image to dim or brighten based on the current slider value. The text inside the thumb bubble mirrors that value. (As the user slides, the stretched icon in the title bar view adjusts its size to provide additional feedback as a sample of a target-action client in the recipe sample code.)

Note

When compositing a UIView, iOS creates a bitmap for the view’s layer. The performance is approximately the same between using a custom view or a custom generated bitmap.

This kind of dynamically built feedback could be based on any kind of data. You might grab values from onboard sensors or make calls out to the Internet just as easily as you use the user’s finger movement with a slider. No matter what live update scheme you use, dynamic updates are certainly graphics intensive—but it’s not as expensive as you might fear. The Core Graphics calls are fast, and the memory requirements for the thumb-sized images are minimal.

This particular recipe assigns two thumb images to the slider. The bubble appears only when the slider is in use, for its UIControlStateHighlighted. In its normal state, namely UIControlStateNormal, only the smaller rectangular thumb appears. Users can tap on the thumb to review the current setting. The context-specific feedback bubble mimics the letter highlights on the standard iOS keyboard.

To accommodate these changes in art, the slider updates its frame at the start and end of each gesture. On being touched (UIControlEventTouchDown), the frame expands by 60 pixels in height. This extra space provides enough room to show the expanded thumb during interaction.

When the finger is removed from the screen (UIControlEventTouchUpInside or UIControlEventTouchUpOutside), the slider returns to its previous dimensions. This restores space to other objects, ensuring that the slider will not activate unless a user directly touches it.

Adding Efficiency

Recipe 2-3 stores a previous value for the slider to minimize the overall computational burden on iOS. It updates the thumb with a new custom image when the slider has changed by at least 0.1, or 10% in value. You can omit this check, if you want, and run the recipe with full live updating. When tested, this provided reasonably fast updates, even on a first-generation iPod touch unit. On recent iPhones and iPads, it has no performance issues at all.

This recipe also avoids any issues at the ends of the slider—namely when the thumb gets caught at 0.9 and won’t update properly to 1.0. In this recipe, a hard-coded workaround for values above 0.98 handles that particular situation by forcing updates.

Recipe 2-3. Building Dynamic Slider Thumbs

Click here to view code image

// Create a thumb image using a grayscale/numeric level
UIImage *thumbWithLevel (float aLevel)
{
float INSET_AMT = 1.5f;
CGRect baseRect = CGRectMake(0.0f, 0.0f, 40.0f, 100.0f);
CGRect thumbRect = CGRectMake(0.0f, 40.0f, 40.0f, 20.0f);

UIGraphicsBeginImageContext(baseRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();

// Create a filled rect for the thumb
[[UIColor darkGrayColor] setFill];
CGContextAddRect(context,
CGRectInset(thumbRect, INSET_AMT, INSET_AMT));
CGContextFillPath(context);

// Outline the thumb
[[UIColor whiteColor] setStroke];
CGContextSetLineWidth(context, 2.0f);
CGContextAddRect(context,
CGRectInset(thumbRect, 2.0f * INSET_AMT, 2.0f * INSET_AMT));
CGContextStrokePath(context);

// Create a filled ellipse for the indicator
CGRect ellipseRect = CGRectMake(0.0f, 0.0f, 40.0f, 40.0f);
[[UIColor colorWithWhite:aLevel alpha:1.0f] setFill];
CGContextAddEllipseInRect(context, ellipseRect);
CGContextFillPath(context);

// Label with a number
NSString *numstring = [NSString stringWithFormat:@"%0.1f", aLevel];
UIColor *textColor = (aLevel > 0.5f) ?
[UIColor blackColor] : [UIColor whiteColor];
[textColor set];
UIFont *font = [UIFont fontWithName:@"Georgia" size:20.0f];
[numstring drawInRect:CGRectInset(ellipseRect, 0.0f, 6.0f)
withFont:font lineBreakMode:NSLineBreakByCharWrapping
alignment:NSTextAlignmentCenter];

// Outline the indicator circle
[[UIColor grayColor] setStroke];
CGContextSetLineWidth(context, 3.0f);
CGContextAddEllipseInRect(context,
CGRectInset(ellipseRect, 2.0f, 2.0f));
CGContextStrokePath(context);

// Build and return the image
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

// Return a base thumb image without the bubble
UIImage *simpleThumb()
{
float INSET_AMT = 1.5f;
CGRect baseRect = CGRectMake(0.0f, 0.0f, 40.0f, 100.0f);
CGRect thumbRect = CGRectMake(0.0f, 40.0f, 40.0f, 20.0f);

UIGraphicsBeginImageContext(baseRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();

// Create a filled rect for the thumb
[[UIColor darkGrayColor] setFill];
CGContextAddRect(context,
CGRectInset(thumbRect, INSET_AMT, INSET_AMT));
CGContextFillPath(context);

// Outline the thumb
[[UIColor whiteColor] setStroke];
CGContextSetLineWidth(context, 2.0f);
CGContextAddRect(context,
CGRectInset(thumbRect, 2.0f * INSET_AMT, 2.0f * INSET_AMT));
CGContextStrokePath(context);

// Retrieve the thumb
UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return theImage;
}

// Update the thumb images as needed
- (void) updateThumb
{
// Only update the thumb when registering significant changes
if ((self.value < 0.98) &&
(ABS(self.value - previousValue) < 0.1f)) return;

// create a new custom thumb image for the highlighted state
UIImage *customimg = thumbWithLevel(self.value);
[self setThumbImage: customimg
forState: UIControlStateHighlighted];
previousValue = self.value;
}

// Expand the slider to accommodate the bigger thumb
- (void) startDrag: (UISlider *) aSlider
{
self.frame = CGRectInset(self.frame, 0.0f, -30.0f);
}

// At release, shrink the frame back to normal
- (void) endDrag: (UISlider *) aSlider
{
self.frame = CGRectInset(self.frame, 0.0f, 30.0f);
}

- (id) initWithFrame:(CGRect) aFrame
{
if (!(self = [super initWithFrame:aFrame]))
return self;

// Initialize slider settings
previousValue = -99.0f;
self.value = 0.0f;

[self setThumbImage:simpleThumb() forState:UIControlStateNormal];

// Create the callbacks for touch, move, and release
[self addTarget:self action:@selector(startDrag:)
forControlEvents:UIControlEventTouchDown];
[self addTarget:self action:@selector(updateThumb)
forControlEvents:UIControlEventValueChanged];
[self addTarget:self action:@selector(endDrag:)
forControlEvents:UIControlEventTouchUpInside |
UIControlEventTouchUpOutside];
return self;
}

+ (id) slider
{
CustomSlider *slider = [[CustomSlider alloc]
initWithFrame:(CGRect){.size=CGSizeMake(200.0f, 40.0f)}];

return slider;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Appearance Proxies

Tired of tinting every button, navigation bar, or slider in your application by hand? Don’t waste time subclassing. Proxies allow you to customize the default appearance of all members of a view class, especially controls. Instead of updating an instance’s properties, you apply the same updates to the proxy. These calls set the shared appearance for slider instances:

Click here to view code image

[[UISlider appearance] setMinimumTrackTintColor:[UIColor blackColor]];
[[UISlider appearance] setMaximumTrackTintColor:[UIColor grayColor]];

At times you may only want to apply appearance proxies on a container-by-container basis. For example, you might want all navigation bar buttons to appear blue but not affect bar buttons in toolbars. In that case, you use a container-aware appearance proxy:

Click here to view code image

[[UIBarButtonItem appearanceWhenContainedIn:
[UINavigationBar class], nil]
setTintColor:[UIColor blueColor]];

The container list specifies a Boolean AND condition. For example, this proxy applies only to navigation bars that appear in popover controllers:

Click here to view code image

[[UIBarButtonItem appearanceWhenContainedIn:
[UINavigationBar class], [UIPopoverController class], nil]
setTintColor:[UIColor blueColor]];

Properties applied to actual instances “win” over those inherited from proxies. In the following code, the left button (Hello) defaults to purple but the right button (World) overrides that default to appear in green:

Click here to view code image

UIBarButtonItem *hello = BARBUTTON(@"Hello", nil);
UIBarButtonItem *world = BARBUTTON(@"World", nil);
world.tintColor = [UIColor greenColor];

UINavigationItem *navigationItem =
[[UINavigationItem alloc] initWithTitle@""];
navigationItem.leftBarButtonItem = hello;
navigationItem.rightBarButtonItem = world;

[[UIBarButtonItem appearanceWhenContainedIn:
[UINavigationBar class], nil]
setTintColor:[UIColor purpleColor]];

The UI_APPEARANCE_SELECTOR tag marks all properties that can be affected by appearance proxies. You’ll find these marks in the UIKit header files. Here’s an example:

Click here to view code image

@property(nonatomic,retain) UIColor *tintColor
__OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0)
UI_APPEARANCE_SELECTOR;

In the latest versions of the SDK, you’ll find the UIKit header files in the Xcode application bundle. They’re stored in the SDKs folder (in /Contents/Developer/Platforms/ iPhoneOS.platform/Developer/SDKs/) under the release folder for the current OS (for instance, iPhoneOS6.0.sdk/System/Library/Frameworks). Look inside the specific Framework folder (for example, UIKit) to find the header files that apply to the iOS version you’re using.

Beware of guessing. Setting an appearance proxy for a view’s backgroundColor property does not throw either a compile-time or runtime error but it can seriously mess up your application’s presentation bringing the crazy to your screen. Background color is not authorized as an appearance selector. At the time of this writing, the following classes are the ones that support appearance proxies:

• UIActivityIndicatorView

• UIBarButtonItem

• UIBarItem

• UINavigationBar

• UIPageControl

• UIProgressView

• UIRefreshControl

• UISearchBar

• UISegmentedControl

• UISlider

• UIStepper

• UISwitch

• UITabBar

• UITabBarItem

• UITableView

• UITableViewHeaderFooterView

• UIToolbar

What’s more, the items available for appearance proxy assignment vary by class. For example, a page control can set its pageIndicatorTintColor, and a stepper can assign a custom divider image. View the documentation for each class to find which methods and properties allow you to adjust appearance. For example, Figure 2-5 shows the appearance customization calls for the UIStepper class.

Figure 2-5. Appearance protocols enable you to customize the default instance appearance of a class by sending messages to a class’s proxy (for example, [[UIStepper appearance] setTintColor:blueColor];). These appearance methods, which can be applied to both direct instances and via the class proxy, are listed together in class documentation for easy reference, as shown in this screenshot. This list of appearance methods belongs to the UIStepper class.

A thorough search for appearance selectors in the UIKit header folder (that is, grep UI_APPEARANCE_SELECTOR *.h) also provides a helpful reference for those who like to look up information at the command line. Here are the header file matches for UIStepper, corresponding to the same calls shown in Figure 2-5:

Click here to view code image

UIStepper.h:- (UIImage *)decrementImageForState:(UIControlState)state
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (UIImage *)incrementImageForState:(UIControlState)state
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (UIImage*)backgroundImageForState:(UIControlState)state
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (UIImage*)dividerImageForLeftSegmentState:(UIControlState)state
rightSegmentState:(UIControlState)state
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (void)setBackgroundImage:(UIImage*)image
forState:(UIControlState)state NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (void)setDecrementImage:(UIImage *)image
forState:(UIControlState)state NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (void)setDividerImage:(UIImage*)image
forLeftSegmentState:(UIControlState)leftState
rightSegmentState:(UIControlState)rightState
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:- (void)setIncrementImage:(UIImage *)image
forState:(UIControlState)state NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;
UIStepper.h:@property(nonatomic,retain) UIColor *tintColor
NS_AVAILABLE_IOS(6_0) UI_APPEARANCE_SELECTOR;

Recipe: Creating a Twice-Tappable Segmented Control

The UISegmentedControl class presents a multiple-button interface, where users can choose one choice out of a group. The control provides two styles of use. In its normal radio-button-style mode, a button once selected remains selected. Users can tap on other buttons, but they cannot generate a new event by retapping their existing choice. The alternative momentary style lets users tap on each button as many times as desired but stores no state about a currently selected item. It provides no highlights to indicate the most recent selection.

Recipe 2-4 builds a hybrid approach. It allows users to see their currently selected option and to reselect that choice if needed. This is not the way segmented controls normally work. There are times, though, when you want to generate a new result on reselection (as in momentary mode) while visually showing the most recent selection (as in radio button mode).

Unfortunately, “obvious” solutions to create this desired behavior don’t work. You cannot add target-action pairs that detect UIControlEventTouchUpInside. UIControlEventValueChanged is the only control event generated by UISegmentedControl instances. (You can easily test this yourself by adding a target-action pair for touch events.)

Here is where subclassing comes in to play. It’s relatively simple to create a new class based on UISegmentedControl that does respond to that second tap. Recipe 2-4 defines that class. Its code works by detecting when a touch has occurred, operating independently of the segmented control’s internal touch handlers that are subclassed from UIControl.

Segment switches remain unaffected; they’ll continue to update and switch back and forth as users tap them. Unlike the parent class, here touches on an already-touched segment continue to do something. In this case, they request that the object’s delegate produce the performSegmentAction method.

Don’t add target-action pairs to your segmented controllers the way you’d normally do. Because all touch down events are detected, target-actions for value-changed events would add a second callback and trigger twice whenever you switched segments. Instead, implement the delegate callback and let object delegation handle the updates.

Second-Tap Feedback

Segmented controls do not natively offer any meaningful feedback when detecting a second tap on the already selected segment. Retapping a segment (blue in the default appearance presentation) leaves the segment blue and selected and otherwise unchanged. Other than creating some other kind of visual update outside the control (like changing the title in the navigation bar), users have no sense that their second tap has affected the interface.

To work around this limitation, roll your own feedback. UIKit’s text attribute feature (first introduced in iOS 5.x) offers an excellent match to this challenge. Segment controls provide optional attributes based on state. The setTitleTextAttributes:forState: method lets you introduce a visual flourish limited to the selected segment.

Recipe 2-4 uses this method to change the selected text color from white to a light gray as it is tapped, resetting that change at the end of the interaction as the touch ends. Each time the user taps a segment, the code updates the selected segment text color for the duration of that touch, adding needed feedback for a better user experience.

Controls and Attributes

In iOS 6, many UIKit classes, including text fields, text views, labels, buttons, and refresh controls now allow you to assign attributed (Core Text-style) strings to their text and title properties:

[myButton setAttributedTitle:attributedString forState:UIControlStateNormal]

In addition, iOS now provides a new (and small) vocabulary of UIKit-specific text attributes like font, color, and shadow. These are used with navigation bars, segmented controls, and bar items (that is, bar-style elements). You set attributes by calling setTitleTextAttributes: (navigation bar) and setTitleTextAttributes:forState: (segmented control and bar items). Pass an attribute dictionary using the following dictionary keys and values.

• UITextAttributeFont—Provide a UIFont instance.

• UITextAttributeTextColor—Provide a UIColor instance.

• UITextAttributeTextShadowColor—Provide a UIColor instance.

• UITextAttributeTextShadowOffset—Provide an NSValue instance wrapping a UIOffset struct. Offsets include two floats, horizontal and vertical. Use UIOffsetMake() to construct the struct from a pair of floating point values.

For example, this recipe sets a segmented control’s text color to light gray for its selected state. Whenever the control is selected, the text color changes from white to gray.

Recipe 2-4. Creating a Segmented Control Subclass That Responds to a Second Tap

Click here to view code image

@class DoubleTapSegmentedControl;

@protocol DoubleTapSegmentedControlDelegate <NSObject>
- (void) performSegmentAction: (DoubleTapSegmentedControl *) aDTSC;
@end

@interface DoubleTapSegmentedControl : UISegmentedControl
{
id __weak <DoubleTapSegmentedControlDelegate> delegate;
}
@property (nonatomic, weak) id delegate;
@end

@implementation DoubleTapSegmentedControl
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[super touchesBegan:touches withEvent:event];
if (self.delegate) [self.delegate performSegmentAction:self];

// Add a little extra feedback
NSDictionary *attributeDictionary =
@{UITextAttributeTextColor : [UIColor lightGrayColor]};
[self setTitleTextAttributes:attributeDictionary
forState:UIControlStateSelected];
}

- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
[self setTitleTextAttributes:nil forState:UIControlStateSelected];
}

- (void) touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
[self touchesEnded:touches withEvent:event];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Working with Switches and Steppers

The UISwitch object offers a simple on/off toggle that lets users choose a Boolean value. (The switch internationalizes to 1/0 for most non-English localizations.) The switch object contains a single (settable) value property, called on. This returns either YES or NO, depending on current state of the control. You can programmatically update a switch’s value by changing the property value directly or calling setOn:animated:, which offers a way to animate the change:

Click here to view code image

- (void) didSwitch: (UISwitch *) theSwitch
{
self.title = [NSString stringWithFormat:@"%
theSwitch.on ? @"On" : @"Off"];
}

- (void) viewDidAppear: (BOOL) animated
{
// Create the switch
UISwitch *theSwitch = [[UISwitch alloc] init];

// Trigger on value changes
[theSwitch addTarget:self action:@selector(didSwitch:)
forControlEvents:UIControlEventValueChanged];

[self.view addSubview:theSwitch];

// Initialize to "off"
theSwitch.on = NO;
self.title = @"Off";
}

In this example, when the switch updates, it changes the view controller’s title. IB offers relatively few options for working with a switch. You can enable it and set its initial value, but beyond that there’s not much to customize. A switch produces a value-changed event when a user adjusts it.

Note

Do not name UISwitch instances as switch. Recall that switch is a reserved C word; it is used for conditional statements. This simple oversight has tripped up many iOS developers.

The UIStepper class provides an alternative to sliders and switches. Sliders offer a continuous range of values; switches offer a simple Boolean on/off choice. Steppers fall somewhat in the middle. Instances present a pair of buttons, one labeled – and the other labeled +. These iteratively increment or decrement its value property.

You generally want to assign a range to the control by setting its minimumValue and maximumValue to some reasonable bounds so the control ties in more tightly to actual application features such as volume, speed, and other measurable amounts. You do not have to do so, but there are few use cases where you want to allow user input for unbounded variables. You can make the stepper “wrap” by setting its wraps property to YES. When the value exceeds the maximum or falls below the minimum, the value wraps around from min to max or max to min, depending on the button pressed.

By default, the stepper autorepeats. That is, it continues to change as long as the user holds one of its buttons. You can disable this by setting the autorepeat property to NO. The amount the value changes at each tap is controlled by the stepValue property. Don’t ever set stepValue to 0 or a negative number; you’ll raise a runtime exception.

Recipe: Subclassing UIControl

UIKit provides many prebuilt controls that you can use directly in your applications. There are buttons and switches and sliders and more. But why stop there? You don’t have to limit yourself to Apple-supplied items. Why not create your own?

Recipe 2-5 demonstrates how to subclass UIControl to build new controls from scratch. This example creates a simple color picker. In use, it lets the user select a color by touching or dragging within the control. As the user traces left and right, the color changes its hue. Up and down movements adjust the color’s saturation. The brightness and alpha levels for the color are fixed at 100%.

This is a really simple control to work with, because there’s not much interaction involved beyond retrieving the x and y coordinates of the touch. It provides a basic example that demonstrates most of the development issues involved in subclassing UIControl.

So why build custom controls? First, you can set your own design style. Elements that you place into your interface can and should match your application’s aesthetics. If Apple’s prebuilt switches, sliders, and other GUI elements don’t provide a natural fit into your interface, custom-built controls satisfy your application’s needs without sacrificing cohesive design.

Second, you can create controls that Apple didn’t provide. From selecting ratings by swiping through a series of stars, or choosing a color from a set of pop-up crayons, custom controls allow your app to interact with the user beyond the system-supplied buttons and switches in the SDK. It’s easy to build unique eye-catching interactive elements by subclassing UIControl.

Finally, custom controls allow you to add features that you cannot access directly or through subclassing. With relatively little work, you can build your own buttons and steppers from the ground up, allowing you to adjust their interaction vocabulary exactly as you wish.

Always keep your custom items visually distinct from system supplied ones. Don’t run afoul of HIG issues. When you do use lookalike items, you may want to add a note to Apple when submitting apps to the App Store. Make it clear that you have created a new class rather than using private APIs or otherwise accessing their objects in a manner that’s not App Store safe. Even then, you might be rejected for creating items that could potentially “confuse” the end user.

Creating Controls

The process of building a UIControl generally involves four distinct steps. As Recipe 2-5 demonstrates, you begin by subclassing UIControl to create a new custom class. In that class, you lay out the visual look of the control in your initialization. Next, you build methods to track and interpret touches, and finish by generating events and visual feedback.

Nearly all controls offer a value of some kind. For example, switches have isOn, sliders have a floating point value, and text fields offer text. The kinds of values you provide with a custom control are arbitrary. They can be integers, floats, strings, or even (as in Recipe 2-5) a color.

In Recipe 2-5, the control layout is basically a colored rectangle. More complex controls require more complex layout, but even a simple layout like the one shown here can function to provide all the touch interaction space and feedback needed for a coherent end-user experience.

Tracking Touches

UIControl instances use an embedded method set to work with touches. These methods allow the control to track touches throughout their interaction with the control object:

• beginTrackingWithTouch:withEvent:—Called when a touch enters a control’s bounds

• continueTrackingWithTouch:withEvent:—Follows the touch with repeated calls as the touch remains within the control bounds

• endTrackingWithTouch:withEvent:—Handles the last touch for the event

• cancelTrackingWithEvent:—Manages a touch cancellation

Add your custom control logic by implementing any or all these methods in a UIControl subclass. Recipe 2-5 uses the begin and continue methods to locate the user touch and track it until the touch is lifted or otherwise leaves the control.

Dispatching Events

Controls use target-action pairs to communicate changes triggered by events. When you build a new control, you must decide what kind of events your object will generate and add code to trigger those events.

Add dispatch to your custom control by calling sendActionsForControlEvents:. This method lets you send an event (for example, UIControlEventValueChanged) to your control’s targets. Controls transmit these updates by messaging the UIApplication singleton. As Apple notes, the application acts as the centralized dispatch point for all messages.

Create as complete a control vocabulary as possible, no matter how simple your class. You cannot anticipate exactly how the class will be used in the future. Overdesigning your events provides flexibility for future use. Recipe 2-5 dispatches a wide range of events for what is, after all, a very simple control.

Where you dispatch events depends a lot on the control you end up building. Switch controls, for example, are really only interested in touch up events, which is when their value changes. Sliding controls, in contrast, center on touch movement and require continuing updates as the control tracks finger movement. Adjust your coding accordingly, and be mindful of presenting appropriate visual changes during all parts of your touch cycle.

Recipe 2-5. Building a Custom Color Control

Click here to view code image

@implementation ColorControl
- (id)initWithFrame:(CGRect)frame
{
if (!(self = [super initWithFrame:frame])) return nil;
_value = nil;
self.backgroundColor = [UIColor grayColor];
return self;
}

- (void) updateColorFromTouch: (UITouch *) touch
{
// Calculate hue and saturation
CGPoint touchPoint = [touch locationInView:self];
float hue = touchPoint.x / self.frame.size.width;
float saturation = touchPoint.y / self.frame.size.height;

// Update the color value and change background color
self.value = [UIColor colorWithHue:hue
saturation:saturation brightness:1.0f alpha:1.0f];
self.backgroundColor = self.value;
[self sendActionsForControlEvents:UIControlEventValueChanged];
}

// Continue tracking touch in control
- (BOOL) continueTrackingWithTouch: (UITouch *) touch
withEvent: (UIEvent *) event
{
// Test if drag is currently inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Update color value
[self updateColorFromTouch:touch];

return YES;
}

// Start tracking touch in control
- (BOOL) beginTrackingWithTouch: (UITouch *) touch
withEvent: (UIEvent *) event
{
// Touch Down
[self sendActionsForControlEvents:UIControlEventTouchDown];

// Update color value
[self updateColorFromTouch:touch];

return YES;
}

// End tracking touch
- (void) endTrackingWithTouch: (UITouch *)touch
withEvent: (UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];

// Update color value
[self updateColorFromTouch:touch];
}

// Handle touch cancel
- (void)cancelTrackingWithEvent: (UIEvent *) event
{
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Building a Star Slider

Rating sliders allow users to grade items such as movies, software, and so forth by dragging their fingers across a set of images. It’s a common task for touch-based interfaces but one that’s not well served by a simple UISlider instance, with its floating-point values. Instead, a picker like the one built in Recipe 2-6 limits a user’s choice to a discrete set of elements, producing a bounded integer value between zero and the maximum number of items shown. As a user’s finger touches each star, the control’s value updates and a corresponding event is spawned, allowing your application to treat the star slider like any other UIControl subclass.

The art is arbitrary. The example shown in Figure 2-6 uses stars, but there’s no reason to limit yourself to stars. Use any art you like, as long as you provide both “on” and “off” images. You might consider hearts, diamonds, smiles, and so on. You can easily update this recipe to provide a starting count of the stars before presentation.

Figure 2-6. Recipe 2-6 creates a custom star slider control that animates each star upon selection. A simple animation block causes the star to zoom out and back as the control’s value updates.

In addition to simple sliding, Recipe 2-6 adds animation elements. Upon achieving a new value, the rightmost star adds a simple animation block to zoom out and back, providing lively feedback to the user in addition to the highlighted visuals. Because the user’s finger lays on top of the stars in real use (rather than in the simulator-based screenshot shown in Figure 2-6), the animation uses exaggerated transforms to provide feedback that extends beyond expected finger sizes. Here, the art is quite small and the zoom goes to 150% of the original size, but you can easily adapt both in your applications to match your needs.

Apart from the minimal layout and feedback elements, Recipe 2-6 follows the same kind of custom UIControl subclass approach used by Recipe 2-5, tracking touches through their life cycle and spawning events at opportune times. The minimal code needed to add the star elements and feedback in this recipe demonstrates how simple UIControl subclassing really is.

Recipe 2-6. Building a Discrete Valued Star Slider

Click here to view code image

@implementation StarSlider
- (id) initWithFrame: (CGRect) aFrame
{
if (self = [super initWithFrame:aFrame])
{
// Lay out five stars, with spacing between, and at the ends
float minimumWidth = WIDTH * 8.0f;
float minimumHeight = 34.0f;

// This control uses a minimum 260x34 sized frame
self.frame = CGRectMake(0.0f, 0.0f,
MAX(minimumWidth, aFrame.size.width),
MAX(minimumHeight, aFrame.size.height));

// Add stars — initially assuming fixed width
float offsetCenter = WIDTH;
for (int i = 1; i <= 5; i++)
{
UIImageView *imageView = [[UIImageView alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, WIDTH, WIDTH)];
imageView.image = OFF_ART;
imageView.center = CGPointMake(offsetCenter,
self.frame.size.height / 2.0f);
offsetCenter += WIDTH * 1.5f;
[self addSubview:imageView];
}
}

// Place on a contrasting background
self.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.25f];

return self;
}

// Handle the value update for the touch point
- (void) updateValueAtPoint: (CGPoint) p
{
int newValue = 0;
UIImageView *changedView = nil;

// Iterate through stars to check against touch point
for (UIImageView *eachItem in [self subviews])
if (p.x < eachItem.frame.origin.x)
eachItem.image = OFF_ART;
else
{
changedView = eachItem; // last item touched
eachItem.image = ON_ART;
newValue++;
}

// Handle value change
if (self.value != newValue)
{
self.value = newValue;
[self sendActionsForControlEvents:
UIControlEventValueChanged];

// Animate the new value with a zoomed pulse
[UIView animateWithDuration:0.15f
animations:^{changedView.transform =
CGAffineTransformMakeScale(1.5f, 1.5f);}
completion:^(BOOL done){[UIView
animateWithDuration:0.1f
animations:^{changedView.transform =
CGAffineTransformIdentity;}];}];
}
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Establish touch down event
CGPoint touchPoint = [touch locationInView:self];
[self sendActionsForControlEvents:UIControlEventTouchDown];

// Calculate value
[self updateValueAtPoint:touchPoint];
return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Test if drag is currently inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Calculate value
[self updateValueAtPoint:[touch locationInView:self]];
return YES;
}

- (void) endTrackingWithTouch: (UITouch *)touch
withEvent: (UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}

- (void)cancelTrackingWithEvent: (UIEvent *) event
{
// Cancelled touch
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Building a Touch Wheel

This next recipe creates a touch wheel, like the ones used on older model iPods. Touch wheels provide infinitely scrollable input. Users can rotate their finger clockwise or counterclockwise, and the object’s value increases or decreases accordingly. Each complete turn around the wheel (that is, a traversal of 360 degrees) corresponds to a value change of 1.0. Clockwise changes are positive; counterclockwise changes are negative. The value accumulates on each touch, although it can be reset; simply assign the control’s value property back to 0.0. This property is not a standard part of UIControl instances, even though many controls use values.

This recipe computes user changes by casting out vectors from the control’s center. The code adds differences in the angle as the finger moves, updating the current value accordingly. For example, three spins around the touch wheel add or subtract 3 to or from the current value, depending on the direction of movement.

This basic wheel defined in Recipe 2-7 tracks touch rotation but does little else. The original iPod scroll wheel offered five click points: in the center circle and at the four cardinal points of the wheel. Adding click support and the associated button-like event support (for UIControlEventTouchUpInside) are left as an exercise for the reader.

Recipe 2-7. Building a Touch Wheel Control

Click here to view code image

@implementation ScrollWheel

// Layout the wheel
- (id) initWithFrame: (CGRect) aFrame
{
if (self = [super initWithFrame:aFrame])
{
// This control uses a fixed 200x200 sized frame
self.frame = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f);
self.center = CGPointMake(CGRectGetMidX(aFrame),
CGRectGetMidY(aFrame));

// Add the touchwheel art
UIImageView *iv = [[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"wheel.png"]];
[self  addSubview:iv];
}
return self;
}

- (BOOL)beginTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
CGPoint p = [touch locationInView:self];

// Center point of view in own coordinate system
CGPoint cp = CGPointMake(self.bounds.size.width / 2.0f,
self.bounds.size.height / 2.0f);

// First touch must touch the gray part of the wheel
if (!pointInsideRadius(p, cp.x, cp)) return NO;
if (pointInsideRadius(p, 30.0f, cp)) return NO;

// Set the initial angle
self.theta = getangle([touch locationInView:self], cp);

// Establish touch down
[self sendActionsForControlEvents:UIControlEventTouchDown];

return YES;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{

CGPoint p = [touch locationInView:self];

// Center point of view in own coordinate system
CGPoint cp = CGPointMake(self.bounds.size.width / 2.0f,
self.bounds.size.height / 2.0f);

// Touch updates
if (CGRectContainsPoint(self.frame, p))
[self sendActionsForControlEvents:
UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchDragOutside];

// Falls outside too far, with boundary of 50 pixels?
if (!pointInsideRadius(p, cp.x + 50.0f, cp)) return NO;

float newtheta = getangle([touch locationInView:self], cp);
float dtheta = newtheta - self.theta;

// correct for edge conditions
int ntimes = 0;
while ((ABS(dtheta) > 300.0f)  && (ntimes++ < 4))
if (dtheta > 0.0f) dtheta -= 360.0f; else dtheta += 360.0f;

// Update current values
self.value -= dtheta / 360.0f;
self.theta = newtheta;

// Send value changed alert
[self sendActionsForControlEvents:UIControlEventValueChanged];

return YES;
}

- (void) endTrackingWithTouch: (UITouch *)touch
withEvent: (UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:
UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:
UIControlEventTouchUpOutside];
}

- (void)cancelTrackingWithEvent: (UIEvent *) event
{
// Cancel
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Creating a Pull Control

Imagine a cord at the top of your screen. Pull it hard enough and it rings a bell or otherwise triggers some sort of event, via a control target-action mechanism. For example, it could roll out a secondary view, start a download, or begin video playback. This recipe builds a control that resembles a ribbon. The control updates clients when the interaction, which must start on top of the “ribbon,” pulls down far enough to trigger a request. The ribbon winds itself back up after, preparing for the next interaction.

Figure 2-7 shows the control built by this recipe, which is attached in this case to the bottom of a secondary view. Tugs bring the view into place and return it off screen when finished.

Figure 2-7. The ribbon control must be tugged a minimum distance before it triggers and rewinds back up. Each success sends out a value-changed message to its target-action clients.

Discoverability

Making the ribbon interaction discoverable presented a particular challenge for this recipe. Users might not immediately make the connection between a hanging red shape and a manipulatable control.

Developer Matthijs Hollemans suggested a simple approach to address this challenge. Until the user interacts with the ribbon, it wiggles slightly a few times, separated by several seconds between each wiggle. The wiggle draws attention to the nature of the control, and the wiggles stop as soon as the user has correctly worked through the control style. A system preference can override this behavior for repeat application uses:

Click here to view code image

- (void) wiggle
{
if (wiggleCount++ > 3) return;

// Wiggle slightly
[UIView animateWithDuration:0.25f animations:^(){
pullImageView.center = CGPointMake(
pullImageView.center.x, pullImageView.center.y + 10.0f);
} completion:^(BOOL finished){
[UIView animateWithDuration:0.25f animations:^(){
pullImageView.center = CGPointMake(
pullImageView.center.x, pullImageView.center.y - 10.0f);
}];
}];

// Repeat until the count is overridden or it wiggles 3 times
[self performSelector:@selector(wiggle) withObject:nil afterDelay:4.0f];
}

Adding accelerator-based movement provides another way to draw user attention to a nonobvious interaction control. Developer Charles Choi recommends allowing the ribbon to respond gently to device movements, offering an alternative mechanism for enhancing the control’s discoverability. That approach is left as an exercise for the reader.

Take your inspiration from Apple itself. Apple integrates discoverability hints throughout iOS, such as using slide to unlock text that suggests how and what to slide through simple animation.

Testing Touches

Recipe 2-8 limits interactions in two ways. First, the user must touch inside the ribbon art to begin interaction. If not, the touch falls through and the control will not respond to it, even though touches may have begun on top of the control’s frame. Second, the recipe tests against the ribbon bitmap to ensure that touches began on a solid (non-transparent) part of the art. As you can see in Figure 2-7 a notch appears at the bottom of the artwork. Touches in this notch won’t start a tracking sequence. The recipe compares the touch position with pixels in the art. If the transparency (alpha level) of the art falls below 85 (about 67% transparent), the touch won’t connect with the ribbon.

The sample code provided for this recipe does not test for stretched art. It assumes a one-to-one relationship between the art and the on-screen presentation. Because of this, you will either have to drop the transparency test or adapt it for stretched art if you choose to resize this control in any way.

Once the tracking begins, the art follows the touch movement, dragging up or down with the user’s finger. If this touch travel exceeds 75 points in this recipe, the control triggers. It sends off a value changed event to its clients. Strictly speaking, this control does not have a “value,” but touch up inside felt like a poor match to the way the control operates.

Upon reaching that point, the continue tracking method returns NO, indicating that tracking has finished, and the control has finished its business for this particular interaction. If the touch travel fails to exceed the threshold, or the user stops interacting without reaching that point, the control scrolls back its artwork to the beginning point. This resets the visual presentation, making it ready for the next interaction.

Recipe 2-8. Building a Draggable Ribbon Control

Click here to view code image

- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
// Establish touch down event
CGPoint touchPoint = [touch locationInView:self];
CGPoint ribbonPoint = [touch locationInView:pullImageView];

// Find the data offset in the image
Byte *bytes = (Byte *) ribbonData.bytes;
uint offset = alphaOffset(ribbonPoint.x, ribbonPoint.y,
pullImageView.bounds.size.width);

// Test for containment and alpha value to disallow touches outside
// the ribbon and inside the notched area

if (CGRectContainsPoint(pullImageView.frame, touchPoint) &&
(bytes[offset] > 85))
{
[self sendActionsForControlEvents:UIControlEventTouchDown];
touchDownPoint = touchPoint;
return YES;
}

return NO;
}

- (BOOL)continueTrackingWithTouch:(UITouch *)touch
withEvent:(UIEvent *)event
{
// Once the user has interacted, don't wiggle any more
wiggleCount = 999;

// Test for inside/outside touches
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.frame, touchPoint))
[self sendActionsForControlEvents:UIControlEventTouchDragInside];
else
[self sendActionsForControlEvents:UIControlEventTouchDragOutside];

// Adjust art based on the degree of drag
CGFloat dy = MAX(touchPoint.y - touchDownPoint.y, 0.0f);
dy = MIN(dy, self.bounds.size.height - 75.0f);
pullImageView.frame = CGRectMake(0.0f,
dy + 75.0f - ribbonImage.size.height,
ribbonImage.size.width, ribbonImage.size.height);

// Detect if travel has been sufficient to trigger everything
if (dy > 75.0f)
{
// It has. Play a click, trigger the callback, and roll
// the view back up.
[self playClick];
[UIView animateWithDuration:0.3f animations:^(){
pullImageView.frame = CGRectMake(0.0f,
75.0f - ribbonImage.size.height, ribbonImage.size.width,
ribbonImage.size.height);
} completion:^(BOOL finished){
[self sendActionsForControlEvents:UIControlEventValueChanged];
}];

// No more interaction needed or allowed
return NO;
}

// Continue interaction
return YES;
}

- (void) endTrackingWithTouch: (UITouch *)touch withEvent: (UIEvent *)event
{
// Test if touch ended inside or outside
CGPoint touchPoint = [touch locationInView:self];
if (CGRectContainsPoint(self.bounds, touchPoint))
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];

// Roll back the ribbon, regardless of where the touch ended
[UIView animateWithDuration:0.3f animations:^(){
pullImageView.frame = CGRectMake(0.0f,
75.0f - ribbonImage.size.height,
ribbonImage.size.width, ribbonImage.size.height);
}];
}

// Handle cancelled tracking
- (void)cancelTrackingWithEvent: (UIEvent *) event
{
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Recipe: Building a Custom Lock Control

I created the lock control you see in Figure 2-8 for a conference I spoke at after wrapping up the last edition of this Cookbook. At that time, numerous people asked me to include it in the next edition. It’s surprisingly easy to build from a UIControl point of view. It consists of four elements: a backdrop, the lock image (which switches to an unlocked version on success), the drag track, and the thumb.

Figure 2-8. This simple lock control unlocks and removes itself after the user successfully swipes across at least three quarters of the way.

Recipe 2-9 shows the code that backs this control’s behavior. Interactions are given a generous margin. Touches within 20 points of the track and its thumb are considered proper hits. This control is quite Spartan, and the extra space (roughly half the size of a standard finger tip) allows more confident access to the control.

Similarly, users only need to drag over about 75% of the way to complete the action. Again, this margin confirms that the user has intended a full unlock but doesn’t require frustrating precision. It took a bit of fiddling and user testing to get the “springiness” right; after releasing the thumb, it’s pulled back to the left if you haven’t finished a successful drag. I ended up using a half second, slightly longer than most interface changes usually take. To compare, a keyboard usually appears in a third of a second.

Recipe 2-9. Creating a Lock Control

Click here to view code image

// Update the art to reflect the lock's value (open or closed)
- (void) setValue:(BOOL)newValue
{
if (value == newValue) return;
value = newValue;
lockView.image = [UIImage imageNamed:value ?
@"lockclosed-art.png" : @"lockopen-art.png"];
}

// Limit tracking to the drag track, and further to the thumb, with a
// generous margin for inaccurate touches
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:self];
CGRect largeTrack = CGRectInset(trackView.frame, -20.0f, -20.0f);
if (!CGRectContainsPoint(largeTrack, touchPoint)) return NO;

touchPoint = [touch locationInView:trackView];
CGRect largeThumb = CGRectInset(thumbView.frame, -20.0f, -20.0f);
if (!CGRectContainsPoint(largeThumb, touchPoint)) return NO;

[self sendActionsForControlEvents:UIControlEventTouchDown];
return YES;
}

// During drags, smooth the thumb movement by animating it
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:trackView];
CGFloat trackStart = [self thumbStartPoint].x;
CGFloat trackEnd = trackView.bounds.size.width * 0.97 –
thumbView.bounds.size.width;

CGFloat offset = touchPoint.x;
offset = MAX(offset, trackStart);
offset = MIN(offset, trackEnd);

// Update the thumb
[UIView animateWithDuration:0.1f animations:^(){
thumbView.center = CGPointMake(
CGRectGetMidX(thumbView.bounds) + offset,
CGRectGetMidY(trackView.bounds));
}];

return YES;
}

// Check for a successful unlock (75% across)
- (void) endTrackingWithTouch: (UITouch *)touch withEvent: (UIEvent *)event
{
CGPoint touchPoint = [touch locationInView:trackView];
CGFloat offset = touchPoint.x;
if (offset > trackView.frame.size.width * 0.75f)
{
// complete, unlock, and fade away
[self sendActionsForControlEvents:UIControlEventValueChanged];
self.value = NO;
[UIView animateWithDuration:0.5f animations:^(){
self.alpha = 0.0f;
} completion:^(BOOL finished) {
[self removeFromSuperview];
}];
}
else
{
// fail - reset
[UIView animateWithDuration:0.2f animations:^(){
thumbView.center = [self thumbStartPoint];
}];
}

if (CGRectContainsPoint(trackView.bounds, touchPoint))
[self sendActionsForControlEvents:UIControlEventTouchUpInside];
else
[self sendActionsForControlEvents:UIControlEventTouchUpOutside];
}

- (void)cancelTrackingWithEvent: (UIEvent *) event
{
[self sendActionsForControlEvents:UIControlEventTouchCancel];
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Adding a Page Indicator Control

The UIPageControl class provides a line of dots that indicates which item of a multipage view is currently displayed. The dots at the bottom of the SpringBoard home page present an example of this kind of control in action. Sadly, the UIPageControl class is a disappointment in action. Its instances are awkward to handle, hard to tap, and will generally annoy your users. So when using it, make sure you add alternative navigation options so that the page control acts more as an indicator and less as a control.

Figure 2-9 shows a page control with three pages. Taps to the left or right of the bright-colored current page indicator trigger UIControlEventValueChanged events, launching whatever method you set as the control’s action. You can query the control for its new value by calling currentPage and set the available page count by adjusting the numberOfPages property. SpringBoard limits the number of dots representing pages to nine, but your application can use a higher number, particularly in landscape mode.

Figure 2-9. The UIPageControl class offers an interactive indicator for multipage presentations. Taps to the left or right of the active dot enable users to select new pages—at least in theory. The page control is hard to tap, requires excessive user precision, and offers poor response performance.

Listing 2-1 uses a UIScrollView instance to display three pages of images. Users can scroll through the pictures using swipes, and the page indicator updates accordingly. Similarly, users can tap on the page control and the scroller animates the selected page into place. This two-way relationship is built by adding a target-action callback to the page control and a delegate callback to the scroller. Each callback updates the other object, providing a tight coupling between the two.

Listing 2-1. Using the UIPageControl Indicator

Click here to view code image

@implementation TestBedViewController
- (void) pageTurn: (UIPageControl *) aPageControl
{
// Animate to the new page
float width = self.view.frame.size.width;
int whichPage = aPageControl.currentPage;
[UIView animateWithDuration:0.3f
animations:^{sv.contentOffset =
CGPointMake(width * whichPage, 0.0f);}];
}

- (void) scrollViewDidScroll: (UIScrollView *) aScrollView
{
// Update the page control to match the current scroll
CGPoint offset = aScrollView.contentOffset;
float width = self.view.frame.size.width;
pageControl.currentPage = offset.x / width;
}

- (void) loadView
{
[super loadView];

float width = self.view.frame.size.width;

// Create the scroll view and set its content size and delegate
sv = [[UIScrollView alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, width, width)];
sv.contentSize = CGSizeMake(NPAGES * width, sv.frame.size.height);
sv.pagingEnabled = YES;
sv.delegate = self;

// Load in all the pages
for (int i = 0; i < NPAGES; i++)
{
NSString *filename =
[NSString stringWithFormat:@"image%d.png", i+1];
UIImageView *iv = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:filename]];
iv.frame = CGRectMake(i * width, 0.0f, width, width);
[sv addSubview:iv];
}

// Place the scroll view
[self.view addSubview:sv];

// Update the page control attributes and add a target
pageControl.numberOfPages = 3;
pageControl.currentPage = 0;
[pageControl addTarget:self action:@selector(pageTurn:)
forControlEvents:UIControlEventValueChanged];
}
@end

Recipe: Creating a Customizable Paged Scroller

Listing 2-1 introduced a basic paged scroller but didn’t add any dynamic interaction to the equation. That example started and ended with three pages. In real life, page controls are far more useful when you can add and delete pages on the fly. Recipe 2-10 does exactly that. It adds buttons that build and remove views for the UIScrollView.

This approach uses not two but four separate controls to produce the add-and-remove interface of Figure 2-10. The four buttons include an add button built using the standard Contacts Add button style, a delete button that mimics that style, a confirm button that looks like an X, which is built to fit over the delete button, and a full-screen, completely clear cancel button.

Figure 2-10. The + and – buttons let users add and remove paged views from the scroller. Deletion requires an extra step as a confirm button animates into place.

The buttons work like this: As long as there are fewer than eight pages, the user can tap Add to create a new view in the UIScrollView. When this button is tapped, the number of pages for the page control updates and the new view scrolls into place. There’s also a check for the current page count; when that page count hits the maximum, the code disables the Add button. The eight-page limit is arbitrary. You can adjust the code for a larger or smaller number.

Upon the Delete button being tapped, a confirm button animates into place and the invisible cancel button is enabled, covering the rest of the screen. If the user taps Confirm, the page deletes. A tap anywhere else causes the action to cancel, hiding the Confirm button without performing a page deletion.

This confirm/cancel approach mirrors Apple’s delete-with-caution policy that’s seen in table edits and in other user interfaces. It takes two taps to delete a page, and the user can cancel out without penalty. This prevents accidental page deletion and provides a safe exit route should the user decide not to continue.

Recipe 2-10. Adding and Deleting Pages On-the-Fly

Click here to view code image

@implementation TestBedViewController

// Page turn via the page control
- (void) pageTurn: (UIPageControl *) aPageControl
{
int whichPage = aPageControl.currentPage;
[UIView animateWithDuration:0.3f animations:^{
scrollView.contentOffset =
CGPointMake(dimension * whichPage, 0.0f);}];
}

// Page update via scrolling. Add math flexibility
- (void)scrollViewDidEndDecelerating:(UIScrollView *)aScrollView
{
pageControl.currentPage = floor((scrollView.contentOffset.x /
dimension) + 0.25);
}

// Return a new color
- (UIColor *)randomColor
{
float red = (64 + (random() % 191)) / 256.0f;
float green = (64 + (random() % 191)) / 256.0f;
float blue = (64 + (random() % 191)) / 256.0f;
return [UIColor colorWithRed:red green:green blue:blue alpha:1.0f];
}

// Layout pages on addition, deletion, and new orientation
- (void) layoutPages
{
int whichPage = pageControl.currentPage;

// Update the scroll view and its content size
scrollView.frame = CGRectMake(0.0f, 0.0f, dimension, dimension);
scrollView.contentSize =
CGSizeMake(pageControl.numberOfPages * dimension, dimension);
scrollView.center = CGPointMake(
CGRectGetMidX(self.view.bounds),
CGRectGetMidY(self.view.bounds));

// Layout only pages (tagged with 999)
float offset = 0.0f;
for (UIView *eachView in scrollView.subviews)
{
if (eachView.tag == 999)
{
eachView.frame = CGRectMake(offset, 0.0f,
dimension, dimension);
offset += dimension;
}
}

// Scroll to the new page location
scrollView.contentOffset = CGPointMake(dimension * whichPage, 0.0f);
}

// Add a new page to the layout
- (void) addPage
{
pageControl.numberOfPages = pageControl.numberOfPages + 1;
pageControl.currentPage = pageControl.numberOfPages - 1;

UIView *aView = [[UIView alloc] init];
aView.backgroundColor = [self randomColor];
aView.tag = 999;
[scrollView addSubview:aView];

[self layoutPages];
}

// User request for a new page
- (void) requestAdd: (UIButton *) button
{
[self addPage];
addButton.enabled = (pageControl.numberOfPages < 8) ? YES : NO;
deleteButton.enabled = YES;
[self pageTurn:pageControl];
}

// Remove the current page
- (void) deletePage
{
int whichPage = pageControl.currentPage;
pageControl.numberOfPages = pageControl.numberOfPages - 1;
int i = 0;
for (UIView *eachView in scrollView.subviews)
{
if ((i == whichPage) && (eachView.tag == 999))
{
[eachView removeFromSuperview];
break;
}

if (eachView.tag == 999) i++;
}

[self layoutPages];
}

// Cancel out of delete
- (void) hideConfirmAndCancel
{
cancelButton.enabled = NO;
[UIView animateWithDuration:0.3f animations:^(void){
confirmButton.center =
CGPointMake(deleteButton.center.x - 300.0f,
deleteButton.center.y);}];
}

// User confirms deletion of current page
- (void) confirmDelete: (UIButton *) button
{
[self deletePage];
addButton.enabled = YES;
deleteButton.enabled = (pageControl.numberOfPages > 1) ? YES : NO;
[self pageTurn:pageControl];
[self hideConfirmAndCancel];
}

// User cancelled delete
- (void) cancelDelete: (UIButton *) button
{
[self hideConfirmAndCancel];
}

// User requests deletion of current page
- (void) requestDelete: (UIButton *) button
{
// Bring forth the cancel and confirm buttons
[cancelButton.superview bringSubviewToFront:cancelButton];
[confirmButton.superview bringSubviewToFront:confirmButton];
cancelButton.enabled = YES;

// Animate the confirm button into place
confirmButton.center =
CGPointMake(deleteButton.center.x - 300.0f,
deleteButton.center.y);

[UIView animateWithDuration:0.3f animations:^(void){
confirmButton.center = deleteButton.center;
}];
}

// On load, setup the page control and scroll view
- (void) viewDidLoad
{
// Update the page control
pageControl.numberOfPages = 0;
[pageControl addTarget:self action:@selector(pageTurn:)
forControlEvents:UIControlEventValueChanged];

// Create the scroll view and set its content size and delegate
scrollView = [[UIScrollView alloc] init];
scrollView.pagingEnabled = YES;
scrollView.delegate = self;
[self.view addSubview:scrollView];

// Load in pages
for (int i = 0; i < INITPAGES; i++)
[self addPage];
pageControl.currentPage = 0;

// Increase the size of the add button for more touchability
addButton.frame = CGRectInset(addButton.frame, -20.0f, -20.0f);
}

// Update the view layout
- (void) viewDidAppear:(BOOL)animated
{
dimension = MIN(self.view.bounds.size.width,
self.view.bounds.size.height) * 0.8f;
[self layoutPages];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 2.

Building Toolbars

It’s easy to define and lay out toolbars in code provided that you’ve supplied yourself with a few handy macro definitions. The following macros return proper bar button items for the four available styles of items and can easily be adapted if you need more control options in your code. These macros are intended for automatic reference counting (ARC) use. If you use manual retain-release (MRR) development, make sure to adapt them with appropriate autorelease calls:

Click here to view code image

#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] \
initWithTitle:TITLE style:UIBarButtonItemStylePlain\
target:self action:SELECTOR]
#define IMGBARBUTTON(IMAGE, SELECTOR) [[UIBarButtonItem alloc] \
initWithImage:IMAGE style:UIBarButtonItemStylePlain \
target:self action:SELECTOR]
#define SYSBARBUTTON(ITEM, SELECTOR) [[UIBarButtonItem alloc] \
initWithBarButtonSystemItem:ITEM \
target:self action:SELECTOR]
#define CUSTOMBARBUTTON(VIEW) [[UIBarButtonItem alloc] \
initWithCustomView:VIEW]

Those styles are text items, image items, system items, and custom view items. Each of these macros provides an UIBarButtonItem that can be placed into a UIToolbar. Listing 2-2 demonstrates these macros in action, showing how to add each style, including spacers. You can even add a custom view to your toolbars, as Listing 2-2 does. It inserts a UISwitch instance as one of the bar button items, as shown in Figure 2-11.

Figure 2-11. Custom toolbar items can include views such as this switch.

The fixed space bar button item represents the only instance where you need to move beyond these handy macros. You must set the item’s width property to define how much space the item occupies. Here are a few final pointers:

• Fixed spaces can have widths. Of all UIBarButtonItems, only UIBarButtonSystemItemFixedSpace items can be assigned a width. So create the spacer item, set its width, and only then add it to your items array.

• Use a single flexible space for left or right alignment. Adding a single UIBarButtonSystemItemFlexibleSpace at the start of an items list right-aligns all the remaining items. Adding one at the end left-aligns. Use two, one at the start and one at the end, to create center alignments.

• Take missing items into account. When hiding a bar button item due to context, when you’re not using layout constraints, don’t just use flexible spacing to get rid of the item. Instead, replace the item with a fixed-width space that matches the item’s original size. That preserves the layout and leaves all the other icons in the same position both before and after the item disappears.

• Navigation bars now support multiple items. Starting in iOS 5.0, navigation bars and their navigation items now allow you to add arrays of bar button items. If you used to add toolbars to your navigation bars, you can probably work past that by adding item arrays instead (for example, self.navigationItem.rightBarButtonItems = anArray). All the toolbar hints listed here, including flexible spacers, apply to navigation item layout as well.

Listing 2-2. Creating Toolbars in Code

Click here to view code image

@implementation TestBedViewController
- (void) action
{
// no action actually happens
}

- (void) viewDidLoad
{
UIToolbar *tb = [[UIToolbar alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, 320.0f, 44.0f)];
tb.center = CGPointMake(160.0f, 200.0f);
NSMutableArray *tbitems = [NSMutableArray array];

// Set up the items for the toolbar
[tbitems addObject:BARBUTTON(@"Title", @selector(action))];
[tbitems addObject:SYSBARBUTTON(UIBarButtonSystemItemAdd,
@selector(action))];
[tbitems addObject:IMGBARBUTTON([UIImage
imageNamed:@"TBUmbrella.png"], @selector(action))];
[tbitems addObject:CUSTOMBARBUTTON([[UISwitch alloc] init])];
[tbitems addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace,
nil)];
[tbitems addObject:IMGBARBUTTON([UIImage
imageNamed:@"TBPuzzle.png"], @selector(action))];

// Add fixed 20 pixel width
UIBarButtonItem *bbi = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace
target:nil action:nil];
bbi.width = 20.0f;
[tbitems addObject:bbi];

tb.items = tbitems;
[self.view addSubview:tb];
}
@end

Summary

This chapter introduced many ways to interact with and get the most from the controls in your applications. Before you move on to the next chapter, here are a few thoughts for you to ponder:

• Just because an item belongs to the UIControl class doesn’t mean you can’t treat it like a UIView. Give it subviews, resize it, animate it, move it around the screen, or tag it for later.

• Core Graphics and Quartz 2D let you build visual elements as needed. Combine the comfort of the SDK classes with a little real-time wow to add punch to your presentation.

• Use attributed strings and UIKit attribute dictionaries to customize your control’s text features. Pick fonts, colors, and shadows as demanded by your design.

• If iOS SDK hasn’t delivered the control you need, consider adapting an existing control or building a new control from scratch.

• Apple provides top-notch examples of user interface excellence. Consider mimicking their examples when creating new interaction styles such as adding confirm buttons to safeguard a delete action.

• Interface Builder doesn’t always provide the best solution for creating interfaces. With toolbars, you may save time in Xcode rather than customizing each element by hand in IB.

3. Alerting the User

At times, you need to grab your user’s attention. New data might arrive or some status might change. You might want to tell your user that there’s going to be a wait before anything more happens—or that the wait is over and it’s time to come back and pay attention. iOS offers many ways to provide that heads-up to the user: from alerts and progress bars to audio pings. In this chapter, you discover how to build these indications into your applications and expand your user-alert vocabulary. You see real-life examples that showcase these classes and discover how to make sure your user pays attention at the right time.

Talking Directly to Your User Through Alerts

Alerts speak to your user. Members of the UIAlertView and UIActionSheet classes pop up or scroll in above other views to deliver their messages. These lightweight classes add two-way dialog to your apps. Alerts visually “speak” to users and can prompt them to reply. You present your alert, get user acknowledgment, and then dismiss the alert to move on with other tasks.

If you think that alerts are nothing more than messages with an attached OK button, think again. Alert objects provide incredible versatility. You can build menus, text input, make queries, and more. In this chapter’s recipes, you see how to create a wide range of useful alerts that you can use in your own programs.

Use alerts and action sheets with caution. Well-designed applications rarely depend on system-supplied components. Limiting yourself to Apple’s ever more dated vocabulary of blue pop-ups may send a message that you skimped on your interface. That said, while the default UI is boring, it’s not always a bad design decision; plus they help get you out of the starting gate. Make an informed choice as to whether these components fit into your overall application or if they stand out as a design incongruity.

Note

The Doctor Who TV series remains exempt from any criticism when using little blue boxes in its design.

Building Simple Alerts

To create alerts, allocate a UIAlertView object. Initialize it with a title and an array of button titles. The title is an NSString, as are the button titles. In the button array, each string represents a single button that should be shown.

The method snippet shown here creates and displays the simplest alert scenario. It shows a message with a single OK button. The alert doesn’t bother with delegates or callbacks, so its lifetime ends when the user taps a button:

Click here to view code image

- (void) showAlert: (NSString *) theMessage
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Title"
message:theMessage
delegate:nil
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
[av show];
}

Add buttons by introducing them as parameters to otherButtonTitles:. Make sure you end your arbitrary list of buttons with nil. Adding nil tells the method where your list finishes. The following snippet creates an alert with three buttons (Cancel, Option, and OK). Because this code does not declare a delegate, there’s no way to recover the alert and determine which of these three buttons was pushed. The alert displays until a user taps and then it automatically dismisses without any further effect:

Click here to view code image

- (void) showAlert: (NSString *) theMessage
{
UIAlertView *av = [[UIAlertView alloc] initWithTitle:@"Title"
message:theMessage
delegate:nil
cancelButtonTitle:@"Cancel"
otherButtonTitles: @"Option", @"OK", nil];
[av show];
}

When working with alerts, space is at a premium. Adding more than two buttons causes the alert to display in multiline mode. Figure 3-1 shows a pair of alerts depicting both two-button (side-by-side display) and three-button (line-by-line display) presentations. Limit the number of alert buttons you add at any time to no more than three or four. Fewer buttons work better; one or two is ideal. If you need to use more buttons, consider using action sheet objects, which are discussed later in this chapter, rather than alert views.

Figure 3-1. Alerts work best with one or two buttons (left). Alerts with more than two buttons stack the buttons as a list, producing a less elegant presentation (right).

UIAlertView objects provide no visual “default” button highlights. The only highlighting is for the Cancel button, as you can see in Figure 3-1. As a rule, Cancel buttons appear at the bottom or left of alerts.

Alert Delegates

Need to know if a user tapped OK or Cancel? Alerts use delegates to recover user choices after they’ve been made using a simple callback. Delegates should declare the UIAlertViewDelegate protocol. In normal use, you often set the delegate to your primary (active) view controller object.

Delegate methods enable you to react as different buttons are pressed. As you’ve already seen, you can omit that delegate support if all you need to do is show some message with an OK button.

After the user has seen and interacted with your alert, the delegate receives an alertView: clickedButtonAtIndex: callback. The second parameter passed to this method indicates which button was pressed. Button numbering begins with zero. The Cancel button defaults to button 0. Even though it appears at the left in some views and the bottom at others, its button numbering remains the same unless you adjust the Cancel button index (retrievable via the cancelButtonIndex property). This is not true for action sheet objects, which are discussed later in this chapter.

Here is a simple example of an alert presentation and callback, which prints out the selected button number to the debugging console:

Click here to view code image

@interface TestBedViewController : UIViewController
<UIAlertViewDelegate>
@end

@implementation TestBedViewController
- (void) alertView:(UIAlertView *) alertView
clickedButtonAtIndex: (int) index
{
NSLog(@"User selected button %d\n", index);
}

- (void) showAlert
{
UIAlertView *av = [[UIAlertView alloc]
initWithTitle:@"Alert View Sample"
message:@"Select a Button"
delegate:self
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"One", @"Two", @"Three", nil];
av.tag = MAIN_ALERT;
[av show];
}
@end

If your controller works with multiple alerts, tags help identify which alert produced a given callback. Unlike controls that use target-action pairs, all alerts trigger the same methods. Adding an alert-tag-based switch statement lets you differentiate your responses to each alert.

Displaying the Alert

The show instance method tells your alert to appear. When shown, the alert works in a modal fashion. That is, it dims the screen behind it and blocks user interaction with your application behind the modal window. This modal interaction continues until your user acknowledges the alert through a button tap, typically by selecting OK or Cancel. Upon doing so, control passes to any alert delegate, allowing that delegate to finish working with the alert and respond to the selected button.

The alert properties remain modifiable after creation. You may customize the alert by updating its title or message properties. The message is the optional text that appears below the alert title and above its buttons. You can add more buttons via addButtonWithTitle:. You can also change the alert’s frame and add subviews.

Kinds of Alerts

The alertViewStyle property allows you to create several alert styles. The default style (UIAlertViewStyleDefault) creates a standard alert, with a title, message text, followed by buttons, as shown in Figure 3-1. It is the bread and butter of the alert world, allowing you to query for button presses such as Yes/No, Cancel/Okay, and other simple choices.

iOS offers three more styles (starting in iOS 5), specifically for entering text:

• UIAlertViewStylePlainTextInput—This alert style enables users to enter text.

• UIAlertViewStyleSecureTextInput—When security is an issue, this alert style allows users to enter text, which is automatically obscured as they type it. The text appears as a series of large dots, but the input can be read programmatically by the delegate callback.

• UIAlertViewStyleLoginAndPasswordInput—This alert style offers two entry fields, including a plain-text user account login field and an obscured text password field.

When working with text entry alerts, keep your button choices simple. Use no more than two side-by-side buttons—usually OK and Cancel. Too many buttons create improper visuals, with text fields floating off above or to the sides of the alert.

You can recover the entered text from the alert view from each text field. The textFieldAtIndex: method takes one argument, an integer index starting at 0, and returns the text field at that index. In real use, the only text field that is not at index 0 is the password field, which uses index 1. After you’ve retrieved a text field, you can query its contents using its text property, as follows:

NSLog(@"%@", [myAlert textFieldAtIndex:0].text);

“Please Wait”: Showing Progress to Your User

Waiting is an intrinsic part of the computing experience and will remain so for the foreseeable future. It’s your job as a developer to communicate that fact to your users. Cocoa Touch provides classes that tell your users to wait for a process to complete. These progress indicators come in two forms: as a spinning wheel that persists for the duration of its presentation and as a bar that fills from left to right as your process moves forward from start to end. The classes that provide these indications are as follows:

• UIActivityIndicatorView—A progress indicator offers a spinning circle that tells your user to wait without providing specific information about its degree of completion. iOS’s activity indicator is small, but its live animation catches the user’s eye and is best suited for quick disruptions in a normal application.

• UIProgressView—This view presents a progress bar. The bar provides concrete feedback as to how much work has been done and how much remains while occupying a relatively small space. It presents as a thin, horizontal rectangle that fills itself from left to right as progress takes place. This classic user interface element works best for long delays, where users want to know to what degree the job has finished.

Be aware of blocking. Both of these classes must be used on your main thread, as is the rule with GUI objects. Computationally heavy code can block, keeping views from updating in real time. If your code blocks, your progress view may not update in real time as progress is actually made, getting stuck on its initial value instead.

Should you need to display asynchronous feedback, use threading. For example, you may use a UIActivityIndicatorView on the main thread and perform computation on a second thread. Your threaded computations can then perform view updates on the main thread to provide a steady stream of progress notifications that will keep your user in sync with the actual work being done.

Using UIActivityIndicatorView

UIActivityIndicatorView instances offer lightweight views that display a standard rotating progress wheel, as shown in Figure 3-2. The key word to keep in mind when working with these views is small. All activity indicators are tiny and do not look right when zoomed past their natural size.

Figure 3-2. Removing buttons from an alert lets you create heads-up displays for ongoing actions.

iOS offers several different styles of the UIActivityIndicatorView class. UIActivityIndicatorViewStyleWhite and UIActivityIndicatorViewStyleGray are 20-by-20 points in size. The white version looks best against a black background, and the gray looks best against white. It’s a thin, sharp style. UIActivityIndicatorViewStyleWhiteLarge is meant for use on dark backgrounds. It provides the largest, clearest indicator at 37-by-37 points in size:

Click here to view code image

UIActivityIndicatorView *aiv = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:
UIActivityIndicatorViewStyleWhiteLarge];

You can tint your activity indicator using the color property. When you set a color, it overrides the view style, although retains the view size (regular or large):

aiv.color = [UIColor blueColor];

You need not center indicators on the screen. Place them wherever they work best for you. As a clear-backed view, the indicator blends over whatever backdrop view lies behind it. The predominant color of that backdrop helps select which color of indicator to use.

For general use, just add the activity indicator as a subview to the window, view, toolbar, or navigation bar you want to overlay. Allocate the indicator and initialize it with a frame, preferably centered within whatever parent view you’re using. Start the indicator in action by sending startAnimating. To stop, call stopAnimating. Cocoa Touch takes care of the rest, hiding the view when not in use.

Using UIProgressView

Progress views enable your users to follow task progress as it happens rather than just saying “Please wait.” They present bars that fill over time. The bars indicate the degree to which a task has finished. Progress bars work best for long waits where providing state feedback enables your users to retain the feel of control.

To create a progress view, allocate a UIProgressView instance and set its frame. To use the bar, issue setProgress:. This takes one argument, a floating-point number that ranges between 0% (0.0, no progress) and 100% (1.0, finished). Progress view bars come in two styles: basic white or light gray. The setStyle: method chooses the kind you prefer, either UIProgressViewStyleDefault or UIProgressViewStyleBar. The latter is meant for use in toolbars.

Recipe: No-Button Alerts

Alert views offer a simple way to display an asynchronous message without involving user interaction. You can create a UIAlertView instance without buttons and use it to create a heads-up display (HUD) about ongoing actions in your application. Because alerts are modal, they prevent any other user interaction during their tenure. This allows you to block users from touching the screen when the program must pause to handle critical operations.

You can build a HUD alert and show it just as you would a normal buttoned version. No-button alerts provide an excellent way to throw up a “Please Wait” message, as shown in Figure 3-2.

No-button alerts present a special challenge because they cannot call back to a delegate method. They do not auto-dismiss, even when tapped. Instead, you must manually dismiss the alert when you are done displaying it. Call dismissWithClickedButtonIndex:animated: to do so.

Recipe 3-1 builds a custom ActivityAlert class, with a static alert view instance. No more than one alert can ever be active at a time, so this implementation uses class methods to ensure that the class acts as a singleton provider.

A UIActivityIndicator appears below the alert title. This creates the progress wheel you see at the bottom of the alert in Figure 3-2. This provides visual feedback to the user that some activity or process is ongoing that prevents user interaction.

Once an alert is created, it works like any other view, and you can add subviews and otherwise update its look. Unfortunately, Interface Builder does not offer alert views in its library, so all customization must be done in code, as shown here. Recipe 3-1 builds the subview and adds it to the alert after first presenting the alert with show. Showing the alert allows it to build a real view that you can modify and customize.

Removing buttons can create an imbalance in the overall presentation geometry. The space that the buttons normally occupy does not go away. In Recipe 3-1, that space is used for the activity indicator. When you’re just using text, adding a carriage return (@"\n") to the start of your message helps balance the bottom where buttons normally go with the spacing at the top.

Note

Be aware that alerts display in a separate window—that is, not the same window that contains the primary view, navigation bar, and bar button that you see in the sample app. An alert’s view is not part of your main window’s hierarchy

Recipe 3-1. Displaying and Dismissing a No-Button Alert

Click here to view code image

@implementation ActivityAlert
+ (void) presentWithText: (NSString *) alertText
{
if (alertView)
{
// With an existing alert, update the text and re-show it
alertView.title = alertText;
[alertView show];
}
else
{
// Create an alert with plenty of room
alertView = [[UIAlertView alloc]
initWithTitle:alertText
message:@"\n\n\n\n\n\n"
delegate:nil
cancelButtonTitle:nil
otherButtonTitles: nil];
[alertView show];

// Build a new activity indicator and animate it
activity = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:
UIActivityIndicatorViewStyleWhiteLarge];
activity.center = CGPointMake(
CGRectGetMidX(alertView.bounds),
CGRectGetMidY(alertView.bounds));
[activity startAnimating];

// Add it to the alert
[alertView addSubview: activity];
}
}

// Update the alert's title
+ (void) setTitle: (NSString *) aTitle
{
alertView.title = aTitle;
}

// Update the alert's message, making sure to pad it
// to the proper number of lines. Keep the message short.
+ (void) setMessage: (NSString *) aMessage;
{
NSString *message = aMessage;
while ([message componentsSeparatedByString:@"\n"].count < 7)
message = [message stringByAppendingString:@"\n"];
alertView.message = message;
}

// Dismiss the alert and reset the static variables
+ (void) dismiss
{
if (alertView)
{
[alertView dismissWithClickedButtonIndex:0 animated:YES];

[activity removeFromSuperview];
activity = nil;
alertView = nil;
}
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Building a Floating Progress Monitor

In the rare case that you must block user interaction for an extended period of time, progress bars on a modal alert allow you to provide status updates to the user. Whenever possible avoid this scenario. Most long-term operations can and should be sent to another thread, with progress updating with nonblocking feedback. For those rare times when you must, you may embed a progress view onto an alert just as you embedded an activity monitor.

To support alert-based progress bars, you’ll need to be able to update the embedded bar as progress occurs. This is easily added with an extra method call. You’ll probably want to provide text updates as well. Use the message field for this. Finally, a well designed application will allow users to cancel any extended operation like this, so consider providing a Cancel button in your alert as well:

Click here to view code image

+ (void) setProgress: (float) amount
{
progress.progress = amount;
}

// Use short messages
+ (void) setMessage: (NSString *) aMessage;
{
NSString *message = aMessage;
while ([message componentsSeparatedByString:@"\n"].count < 7)
message = [message stringByAppendingString:@"\n"];
alertView.message = message;
}

It might be nice to add a Cancel button that sends out a notification or calls a delegate method to the floating progress indicator. This is left as an exercise for the reader.

Note

Oliver Drobnik has written a terrific introduction to using blocks with alerts and action sheets (http://cocoanetics.com/2012/06/block-based-action-sheet). Let’s hope Apple gives these classes a major facelift with iOS 7. As is, alert views and action sheets are some of the oldest items in iOS. Originally a single class in iPhone OS 1.0, their antiquity is evidenced by the word clicked everywhere as part of method names. Drobnik’s block-based refresh helps bring these creaky ugly blue artifacts into the modern age.

Recipe: Creating Modal Alerts with Run Loops

The indirect nature of the alert (namely its delegate callback approach) can produce unnecessarily complex code. It’s easy to build a custom class that directly returns a button choice value. Consider the following code. It requests an answer from the alert shown in Figure 3-3 and then uses the answer that the class method returns:

Click here to view code image

- (void) alertText: (id) sender
{
// OK = 1, Cancel = 0
UIAlertView *alertView = [[UIAlertView alloc]
initWithTitle:@"What is your name?"
message:@"Please enter your name"
delegate:nil
cancelButtonTitle:@"Cancel"
otherButtonTitles:@"Okay", nil];
alertView.alertViewStyle = UIAlertViewStylePlainTextInput;

ModalAlertDelegate *delegate =
[ModalAlertDelegate delegateWithAlert:alertView];

// Build a response from the text field
NSString *response = @"No worries. I'm shy too.";
if ([delegate show])
response = [NSString stringWithFormat:@"Hello %@",
[alertView textFieldAtIndex:0].text];

// Display the response
[[[UIAlertView alloc]
initWithTitle:nil message:response
delegate:nil cancelButtonTitle:nil
otherButtonTitles:@"Okay", nil] show];
}

Figure 3-3. This modal alert returns immediate answers because it’s built using its own run loop.

To create an alert that returns an immediate result requires a bit of ingenuity. A custom class called ModalAlertDelegate can handle things for you. Create an instance, passing it the alert view you want to work with. It assigns itself as the alert’s delegate and takes responsibility for presenting the alert. It implements its own version of show, which you normally call directly on the alert. As you can see in Recipe 3-2, the code calls CFRunLoopRun(), which makes the method sit and wait until the user finishes interacting with the alert. The method goes no further as the run loop runs.

The delegate cancels that run loop on a button click and returns the value of the selected item. When the user finishes interacting, the calling method can finally proceed past the run loop.

Be aware that although you can run one alert after another using this method, sometimes the calls may crowd each other. Leave enough time for the previous alert to disappear before presenting the next. Should an alert fail to show, it’s probably due to this overlap issue. In such a case, use a delayed selector to call the next alert request. A tenth of a second offers plenty of time to allow the new alert to show.

Presumably, future generations of iOS will deprecate delegate-based alerts and replace them with completion-block-powered alternatives. This will remove any need for run loop-based modal workarounds.

Recipe 3-2. Creating Alerts That Return Immediate Results

Click here to view code image

@interface ModalAlertDelegate : NSObject <UIAlertViewDelegate>
{
UIAlertView *alertView;
int index;
}
+ (id) delegateWithAlert: (UIAlertView *) anAlert;
- (int) show;
@end

@implementation ModalAlertDelegate
- (id)initWithAlert: (UIAlertView *) anAlert
{
if (!(self = [super init])) return self;
alertView = anAlert;
return self;
}

-(void)alertView:(UIAlertView*)aView
clickedButtonAtIndex:(NSInteger)anIndex
{
// Store the selected button index
index = anIndex;

// Done with the alert view
alertView = nil;

// Stop the ongoing run loop and return control
CFRunLoopStop(CFRunLoopGetCurrent());
}

- (int) show
{
// Act as the alert's delegate, and show it
[alertView setDelegate:self];
[alertView show];

// Wait until the user interacts
CFRunLoopRun();

return index;
}

+ (id) delegateWithAlert: (UIAlertView *) anAlert
{
ModalAlertDelegate *mad = [[self alloc] initWithAlert:anAlert];
return mad;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Recipe: Using Variadic Arguments with Alert Views

Methods that can take a variable number of arguments are called variadic. They are declared using ellipses after the last parameter. Both NSLog and printf are variadic. You can supply them with a format string along with any number of arguments.

Because most alerts center on text, it’s handy to build methods that create alerts from format strings. Recipe 3-3 creates a say: method that collects the arguments passed to it and builds a string with them. The string is then passed to an alert view, which is then shown, providing a handy instant display.

The say: method does not parse or otherwise analyze its parameters. Instead, it grabs the first argument, uses that as the format string, and passes the remaining items to the NSString initWithFormat:arguments: method. This builds a string, which is then passed to a one-button alert view as its title.

Defining your own utility methods with variadic arguments lets you skip several steps where you have to build a string with a format and then call a method. With say: you can combine this into a single call, as follows:

Click here to view code image

[NotificationAlert say:
@"I am so happy to meet you, %@", yourName];

This recipe, admittedly, uses a very thin example of variadic arguments. They can do a lot more than just get passed along to a string initialization.

Recipe 3-3. Using a Variadic Method for UIAlertView Creation

Click here to view code image

+ (void) say: (id)formatstring,...
{
if (!formatstring) return;

va_list arglist;
va_start(arglist, formatstring);
id statement = [[NSString alloc]
initWithFormat:formatstring arguments:arglist];
va_end(arglist);

UIAlertView *av = [[UIAlertView alloc]
initWithTitle:statement message:nil
delegate:nil cancelButtonTitle:@"Okay"
otherButtonTitles:nil];
[av show];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Presenting Lists of Options

UIActionSheet instances create simple iOS menus. On the iPhone and iPod touch, they slide choices, basically a list of buttons representing possible actions, onto the screen and wait for the user to respond. On the iPad, they appear in popovers and do not display Cancel buttons. Instead, users cancel actions by tapping outside the popover.

Action sheets are different from alerts, although both classes derive from the same origins. They were split into separate classes early in iPhone history. Alerts stand apart from the interface and are better used for demanding attention. Menus slide into a view and better integrate with ongoing application work. Cocoa Touch supplies five ways to present menus:

• showInView—On the iPhone and iPod touch, this method slides the menu up from the bottom of the view. On the iPad, the action sheet is centered in the middle of the screen.

• showFromToolBar: and showFromTabBar:—For the iPhone and iPod touch, when you’re working with toolbars, tab bars, or any other kinds of bars that provide those horizontally grouped buttons that you see at the bottom of many applications, these methods align the menu with the top of the bar and slide it out exactly where it should be. On the iPad, the action sheet is centered in the middle of the screen.

• showFromBarbuttonItem:animated:—On the iPad, this method presents the action sheet as a popover from the specified bar button.

• showFromRect:inView:animated:—Shows the action sheet originating from the rectangle you specify in the coordinates of the view you specify.

Note

Do not use showInView with tabbed child view controllers. The action sheet appears properly but the lower part with the Cancel button becomes irresponsive.

The following snippet shows how to initialize and present a simple UIActionSheet instance. Its initialization method introduces a concept missing from UIAlertView: the Destructive button. Colored in red, a Destructive button indicates an action from which there is no return, such as permanently deleting a file (see Figure 3-4, left). Its bright red color warns the user about the choice. Use this option sparingly.

Figure 3-4. On the iPhone and iPod touch, action sheet menus slide in from the bottom of the view. Although the Destructive menu button appears gray in the print edition of this book (left), it is red on iOS devices and indicates permanent actions with possible negative consequences to your users. Adding many menu items produces the scrolling list on the right.

Action sheet values are returned in button order. In the left Figure 3-4 example, the Destructive button is number 0, and the Cancel button is number 4. This behavior contradicts default alert view values, where the Cancel button returns 0. With action sheets, the Cancel button’s position sets its number. This may vary, depending on how you add your buttons. In some configurations (no Destructive button), Cancel defaults to the first item as choice 0. You also can check the cancel button index via the sheet’s cancelButtonIndex property. This snippet prints the selected button index:

Click here to view code image

- (void) actionSheet:(UIActionSheet *)actionSheet
didDismissWithButtonIndex:(NSInteger)buttonIndex
{
self.title = [NSString stringWithFormat:@"Button %d", buttonIndex];
}

- (void) action: (UIBarButtonItem *) sender
{
// Destructive = 0, One = 1, Two = 2, Three = 3, Cancel = 4
UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:@"Title"
delegate:self
cancelButtonTitle:@"Cancel"
destructiveButtonTitle:@"Destructive"
otherButtonTitles:@"One", @"Two", @"Three", nil];
[actionSheet showFromBarButtonItem:sender animated:YES];
}

Avoid using Cancel buttons on the iPad. Allow users to tap outside the action sheet to cancel interaction after presenting a sheet:

Click here to view code image

UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:theTitle delegate:nil
cancelButtonTitle:IS_IPAD ? nil : @"Cancel"
destructiveButtonTitle:nil otherButtonTitles:nil];

Canceling an iPad action sheet returns a (default) value of –1. You can override this, but I cannot recommend doing so.

Note

You can use the same second run loop approach shown in Recipe 3-2 to retrieve results with action sheets as you can with alert views.

Scrolling Menus

As a rough rule of thumb, you can fit a maximum of about seven buttons (including Cancel) into a portrait orientation and about four buttons into landscape on the iPhone and iPod touch. (There’s quite a bit more room on the iPad.) Going beyond this number triggers the scrolling presentation shown in Figure 3-4 (right). Notice that the Cancel button is presented below the list, although its numbering remains consistent with shorter menu presentations. The Cancel button is always numbered after any previous buttons. As Figure 3-4 demonstrates, this presentation falls fairly low on the aesthetics scale and should be avoided where possible.

Note

Be aware that early betas of iOS 6 included a rather nasty bug for scrolling alert sheets that interfered with run loops.

Displaying Text in Action Sheets

Action sheets offer many of the same text presentation features as alert views, but they do so with a much bigger canvas. The following snippet demonstrates how to display a text message using a UIActionSheet object. It provides a handy way to present a lot of text simultaneously:

Click here to view code image

- (void) show: (id)formatstring,...
{
if (!formatstring) return;

va_list arglist;
va_start(arglist, formatstring);
id statement = [[NSString alloc]
initWithFormat:formatstring arguments:arglist];
va_end(arglist);

UIActionSheet *actionSheet = [[UIActionSheet alloc]
initWithTitle:statement
delegate:nil cancelButtonTitle:nil
destructiveButtonTitle:nil
otherButtonTitles:@"OK", nil];

[actionSheet showInView:self.view];
}

Recipe: Building Custom Overlays

Although UIAlertView and UIActionSheet provide excellent modal progress indicators, you can also roll your own completely from scratch. Recipe 3-4 uses a simple tinted UIView overlay with a UIActivityIndicatorView.

The overlay view occupies the entire screen size. Using the entire screen lets the overlay fit over the navigation bar. That’s because the overlay view must be added to the application window and not, as you might think, to the main UIViewController’s view. That view only occupies the space under the navigation bar (the “application frame” in UIScreen terms), allowing continued access to any buttons and other control items in the bar. Filling the window helps block that access.

To restrict any user touches with the screen, the overlay sets its userInteractionEnabled property to YES. This catches any touch events, preventing them from reaching the GUI below the alert, creating a modal presentation where interaction cannot continue until the alert has finished. You can easily adapt this approach to dismiss an overlay with a touch, but be aware when creating alerts like this that the view does not belong to a view controller. It will not update itself during device orientation changes. If you need to work with a landscape/portrait-aware system, you can catch that value before showing the overlay and subscribe to reorientation notifications.

Recipe 3-4. Presenting and Hiding a Custom Alert Overlay

Click here to view code image

- (void) removeOverlay: (UIView *) overlayView
{
[overlayView removeFromSuperview];
}

- (void) action: (id) sender
{
UIWindow *window = self.view.window;

// Create a tinted overlay, sized to the window
UIView *overlayView = [[UIView alloc] initWithFrame:window.bounds];
overlayView.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.5f];
overlayView.userInteractionEnabled = YES;

// Add an activity indicator
UIActivityIndicatorView *aiv = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:
UIActivityIndicatorViewStyleWhiteLarge];
aiv.center = CGPointMake(
CGRectGetMidX(overlayView.bounds),
CGRectGetMidY(overlayView.bounds));
[aiv startAnimating];

[overlayView addSubview:aiv];
[window addSubview:overlayView];

// Use a time delay to simulate a task finishing
[self performSelector:@selector(removeOverlay:)
withObject:overlayView afterDelay:5.0f];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Tappable Overlays

A custom overlay can present information as well as limit interaction. You can easily expand the overlay approach from Recipe 3-4 to dismiss itself on a touch. When tapped, a view removes itself from the screen. This behavior makes it particularly suitable for showing information in a way normally reserved for the UIAlertView class:

Click here to view code image

@interface TappableOverlay : UIView
@end
@implementation TappableOverlay
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
// Remove this view when it is touched
[self removeFromSuperview];
}
@end

Recipe: Basic Popovers

At the time of this writing, popovers remain an iPad-only feature. That may change as Apple introduces new iOS models or ports some of this functionality to the iPhone family of devices. Often you’ll want to present information using a popover as an alternative to presenting a modal view. There are several basic rules of popovers that you need to incorporate into your day-to-day development:

• Always hang onto your popovers. Create strong local variables that retain your popovers until they are no longer needed. In Recipe 3-5, the variable is reset once the popover is dismissed.

• Always check for existing popovers and dismiss them. This is especially important if you create popovers with different roles in your apps. For example, you may provide popovers for more than one bar button item. Before you present any new popover, dismiss the existing one.

• Always set your content size. The default iPad popover is long and thin and may not appeal to your design aesthetics. Setting the contentSizeForViewInPopover property of your view controllers allows you to specify exactly what dimensions the popover should use.

• Think carefully about your permitted arrow directions. With bar button items (as in Recipe 3-5), I use an up direction because my bar buttons are consistently at the top of my screen. Pick an arrow direction that makes sense, pointing toward the calling object. If you have a master controller on the left of your screen and the popover goes out to its right, use a left arrow. Arrow directions can be OR’ed together as needed when space is tight, to pick the best possible solution. Popovers will try to determine the ideal arrow direction based on the rectangle they point to. Setting the arrow direction overrides this behavior.

• Always provide an iPhone option. Don’t sacrifice functionality when changing platforms. Instead, provide an iPhone-family alternative, usually a modally presented controller instead of a popover.

• Never add a Done button to popovers. Although you normally add a Done button to modal presentations, skip them in popovers. Users tap outside of the popover to dismiss it. A Done button is simply redundant.

Recipe 3-5. Basic Popovers

Click here to view code image

- (void) popoverControllerDidDismissPopover:
(UIPopoverController *)popoverController
{
// Stop holding onto the popover
popover = nil;
}

- (void) action: (id) sender
{
// Always check for existing popover
if (popover)
[popover dismissPopoverAnimated:YES];

// Retrieve the nav controller from the storyboard
UIStoryboard *storyboard =
[UIStoryboard storyboardWithName:@"Storyboard"
bundle:[NSBundle mainBundle]];
UINavigationController *controller =
[storyboard instantiateInitialViewController];

// Present either modally or as a popover
if (IS_IPHONE)
{
[self.navigationController presentViewController:controller
animated:YES completion:nil];
}
else
{
// No done button on iPads
UIViewController *vc = controller.topViewController;
vc.navigationItem.rightBarButtonItem = nil;

// Set the content size to iPhone-sized
vc.contentSizeForViewInPopover =
CGSizeMake(320.0f, 480.0f - 44.0f);

// Create and deploy the popover
popover = [[UIPopoverController alloc]
initWithContentViewController:controller];
[popover presentPopoverFromBarButtonItem:sender
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Recipe: Local Notifications

Local notifications alert the user when your application is not running. They offer a simple way to schedule an alert that presents itself at a specific date and time. Unlike push notifications, local notifications do not require any network access and do not communicate with remote servers. As their name suggests, they are handled entirely on a local level.

Local notifications are meant to be used with schedules, such as calendar and to-do list utilities. You can also use them with multitasking applications to provide updates when the application is not running in the foreground. For example, a location-based app might pop up a notification to let a user know that the app has detected that the user is nearby the local library and that books are ready to be picked up.

The system does not present local notifications when the application is active, only when it’s suspended or running in the background. Recipe 3-6 forces the app to quit as it schedules the notification for 5 seconds in the future to allow the notification to appear properly. Don’t ever do this in App Store applications, but if you don’t do it here, you’ll miss the notification.

As with push notifications, tapping the action button will relaunch the application, moving control back into the application:didFinishLaunchingWithOptions: method. If you retrieve the options dictionary, the notification object can be found via the UIApplicationLaunchOptionsLocalNotificationKey key.

Some developers have used this relaunching capability to add features to the notification center, with varied success. The idea works like this: By adding a local notification, a tap will launch the app to perform some task such as tweeting. Thus, you are essentially offering features “through” the notification center. Apple doesn’t always respond well to those who use its center in nonstandard ways; your success will most certainly vary as it adjusts its policies to these clever but unsanctioned uses.

Best Practices

Don’t spam your users. Just because local notifications don’t require opt-in doesn’t mean that you should abuse them for marketing. Here’s a rule of thumb: If your notification doesn’t deliver information that your user specifically requested, don’t send it. (This goes for push notifications as well. When users opt in, they’re not opting in for spam.)

An unsolicited notification is not the user experience you should be aiming for. When your notification arrives in the middle of dinner or at 3 in the morning, you fail to win hearts, reviews, and customers.

Excess notifications are wrong, regardless of whether users can switch on “do not disturb” features. Apple regularly refuses applications that send ads through push notifications; you should follow that for local notifications as well.

And, as a final point, make sure to spell-check your notifications.

Recipe 3-6. Scheduling Local Notifications

Click here to view code image

- (void) action: (id) sender
{
UIApplication *app = [UIApplication sharedApplication];

// Remove all prior notifications
NSArray *scheduled = [app scheduledLocalNotifications];
if (scheduled.count)
[app cancelAllLocalNotifications];

// Create a new notification
UILocalNotification* alarm =
[[UILocalNotification alloc] init];
if (alarm)
{
alarm.fireDate =
[NSDate dateWithTimeIntervalSinceNow:5.0f];
alarm.timeZone = [NSTimeZone defaultTimeZone];
alarm.repeatInterval = 0;
alarm.alertBody = @"Five Seconds Have Passed";
[app scheduleLocalNotification:alarm];

// Force quit. Never do this in App Store code.
exit(0);
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Alert Indicators

When your application accesses the Internet from behind the scenes, it’s polite to let your user know what’s going on. Rather than create a full-screen alert, Cocoa Touch provides a simple application property that controls a spinning network activity indicator in the status bar. Figure 3-5 shows this indicator in action, to the right of the Wi-Fi indicator and to the left of the current time display.

Figure 3-5. The network activity indicator is controlled by a UIApplication property.

The following snippet demonstrates how to access this property, doing little more than toggling the indicator on or off. In real-world use, you’ll likely perform your network activities on a secondary thread. Make sure you perform this property change on the main thread so the GUI can properly update itself:

Click here to view code image

- (void) action: (id) sender
{
// Toggle the network activity indicator
UIApplication *app = [UIApplication sharedApplication];
app.networkActivityIndicatorVisible =
!app.networkActivityIndicatorVisible;
}

You may want to keep count of network operations in your application and enable the indicator only when at least one is active.

Badging Applications

If you’ve used iOS for any time, you’ve likely seen the small, red badges that appear over applications on the home screen. These might indicate the number of missed phone calls or unread e-mails that have accumulated since the user last opened Phone or Mail.

To set an application badge from within the program itself, set the applicationIconBadgeNumber property to a positive integer. To hide badges, set applicationIconBadgeNumber to 0.

Remove your badges predictably, for example on opening the application. Users expect that opening an application will clear the badge on the SpringBoard.

Recipe: Simple Audio Alerts

Audio alerts “speak” directly to your users. They produce instant feedback—assuming users are not hearing impaired. Fortunately, Apple built basic sound playback into the Cocoa Touch SDK through System Audio services.

The alternatives include using Audio Queue calls or AVAudioPlayer. Audio Queue playback is expensive to program and involves much more complexity than simple alert sounds need. In contrast, you can load and play system audio with just a few lines of code. AVAudioPlayer also has its drawbacks. It interferes with iPod audio. In contrast, System Audio can perform a sound without interrupting any music that’s currently playing, although that may admittedly not be the result you’re looking for, as alerts can get lost in the music.

Alert sounds work best when kept short, preferably 30 seconds or shorter according to Apple. System Audio plays PCM and IMA audio only. That means limiting your sounds to AIFF, WAV, and CAF formats.

System Sounds

To build a system sound, call AudioServicesCreateSystemSoundID with a file URL pointing to the sound file. This call returns an initialized system sound object, which you can then play at will. Just call AudioServicesPlaySystemSound with the sound object. That single call does all the work:

AudioServicesPlaySystemSound(mySound);

The default implementation of system sounds allows them to be controlled by the Sound Effects preference in Settings. When effects are disabled, the sound will not play. To override this preference and always play the sound, you can set a property flag as follows:

Click here to view code image

// Identify it as a non UI Sound
AudioServicesCreateSystemSoundID(baseURL, &mysound);
AudioServicesPropertyID flag = 0;  // 0 means always play
AudioServicesSetProperty(
kAudioServicesPropertyIsUISound,
sizeof(SystemSoundID),
&mysound,
sizeof(AudioServicesPropertyID),
&flag);

When iPod audio is playing, the system sound generally plays back at the same volume, without fading, so users may miss your alert. You can check the current playback state by testing as follows. Make sure you include MediaPlayer/MediaPlayer.h and link to the MediaPlayer framework:

Click here to view code image
if ([MPMusicPlayerController iPodMusicPlayer].playbackState ==
MPMusicPlaybackStatePlaying)

Add an optional system sound completion callback to notify your program when a sound finishes playing by calling AudioServicesAddSystemSoundCompletion(). Unless you use short sounds that are chained one after another, this is a step you can generally skip.

Clean up your sounds by calling AudioServicesDisposeSystemSoundID with the sound in question. This frees the sound object and all its associated resources.

Note

To use these system sound services, make sure to include AudioToolbox/AudioServices.h in your code and link to the Audio Toolbox framework.

Vibration

As with audio sounds, vibration immediately grabs a user’s attention. What’s more, vibration works for nearly all users, including those who are hearing or visually impaired. Vibration is available, however, only on the iPhone platform at this time. Plus, it should be used sparingly. It puts a great drain on the device battery.

Using the same System Audio services, you can vibrate as well as play a sound. All you need is the following one-line call to accomplish it, as used in Recipe 3-7:

AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);

You cannot vary the vibration parameters. Each call produces a short 1-to-2-second buzz. On platforms without vibration support (such as the iPod touch and iPad), this call does nothing—but will not produce an error:

Click here to view code image

- (void) vibrate
{
// Vibrate only works on iPhones
AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);
}

Alerts

Audio Services provides a vibration/sound mashup called an alert sound, which is invoked as follows:

AudioServicesPlayAlertSound(mySound);

This call, which is also demonstrated in Recipe 3-7, plays the requested sound and, possibly, vibrates or plays a second alert. On iPhones, when the user has set Settings > Sound > Ring > Vibrate to ON, it vibrates the phone. Second-generation and later iPod touch units play the sound sans vibration (which is unavailable on those units) through the onboard speaker. First-generation iPod touches (see if you can find one these days!) play a short alert melody in place of the sound on the device speaker while playing the requested audio through to the headphones. iOS automatically lowers any currently playing music during alert playback.

Do not implement your application sound playback entirely through alerts. I can think of at least two games that did so, making those products completely impervious to user volume adjustments. The games may be great, but they are unplayable at the doctor’s office or library.

Delays

The first time you play back a system sound on iOS, you might encounter delays. You may want to play a silent sound on application initialization to avoid a delay on subsequent playback.

Note

When testing on iPhones, make sure you have not enabled the silent ringer switch on the left side of the unit. This oversight has tripped up many iPhone developers. If your alert sounds must always play, consider using the AVAudioPlayer class.

Disposing of System Sounds

Don’t forget to dispose of system sounds. Your dealloc method provides a natural place to wrap up matters at an object’s end of life. Always consider the life cycle of your sounds and find an approach to manage when the sound should be disposed of.

For many applications, a few sounds can persist for the duration of an object’s (or even an application’s) lifetime without placing a burden on memory. For others, you’ll want to clean up after yourself as soon as the sound is played. Make sure to design sound disposal into your applications, and ensure that you dispose of resources when you’re done with them.

To hear sounds, make sure to run this recipe on devices rather than testing in the simulator.

Recipe 3-7. Playing Sounds, Alerts, and Vibrations Using Audio Services

Click here to view code image

@implementation TestBedViewController

// Dispose of sound on completion
void _systemSoundDidComplete(SystemSoundID ssID, void *clientData)
{
AudioServicesDisposeSystemSoundID(ssID);
}

- (void) playAndDispose
{
// Load the sound
NSString *sndpath = [[NSBundle mainBundle]
pathForResource:@"knock3" ofType:@"wav"];
CFURLRef baseURL = (CFURLRef)CFBridgingRetain(
[NSURL fileURLWithPath:sndpath]);

SystemSoundID sysSound;
AudioServicesCreateSystemSoundID(baseURL, &sysSound);
CFRelease(baseURL);

// Add a completion callback
AudioServicesAddSystemSoundCompletion(sysSound,
NULL, NULL, _systemSoundDidComplete, NULL);

// Decide how best to play the sound
if ([MPMusicPlayerController iPodMusicPlayer].playbackState
==  MPMusicPlaybackStatePlaying)
AudioServicesPlayAlertSound(sysSound);
else
AudioServicesPlaySystemSound(sysSound);

}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Knock", @selector(playAndDispose));
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 3.

Summary

This chapter introduced ways for your application to reach out and interact directly with your user instead of the other way around. You learned how to build alerts—visual, auditory, and tactile—that grab your user’s attention and can request immediate feedback. Use these examples to enhance the interactive appeal of your programs and leverage some unique iPhone-only features. Here are a few thoughts to carry away from this chapter:

• Whenever any task will take a noticeable amount of time, be courteous to your user and display some kind of progress feedback. iOS offers many ways to do this, from HUDs to status bar indicators and beyond. You may need to divert the non-GUI elements of your task to a new thread to avoid blocking. It’s also courteous to provide a way for the user to cancel out of the operation, if possible.

• Alerts take users into the moment. They’re designed to elicit responses while communicating information. And, as you saw in this chapter, they’re almost always customizable and flexible. It’s possible to build entire applications around the simple UIAlertView.

• Don’t be afraid of the run loop. A modal response from an alert or action sheet lets you poll users for immediate choices without being dependent on asynchronous callbacks. Using run loops may startle some of your coworkers unless you evangelize heavily. If you run your own shop, you probably have more freedom to make executive decisions that include run loop implementations.

• Use local notifications sparingly. Never display them unless there’s a compelling reason why the user would want them to be displayed. It’s very easy to alienate your user and get your app kicked off the device by overusing local notification alerts.

• Blue-colored system-supplied features do not and should not match every application’s design needs. Whenever possible build custom alerts and menus to match your app using UIView instances and animation.

• Audio feedback, including beeps and vibration, can enhance your programs and make your interaction richer. Using system sound calls means that your sounds play nicely with iPod functionality and won’t ruin the ongoing listening experience. At the same time, don’t be obnoxious. Use alert sounds sparingly and meaningfully to avoid annoying your users.

4. Assembling Views and Animations

The UIView class and its subclasses populate the iOS device screens. This chapter introduces views from the ground up. You learn how to build, inspect, and break down view hierarchies and understand how views work together. You discover the role geometry plays in creating and placing views into your interface, and you read about animating views so they move and transform onscreen. This chapter covers everything you need to know to work with views from the lowest levels up.

View Hierarchies

A tree-based hierarchy orders what you see on your iOS screen. Starting with the main window, views are laid out in a specifically hierarchical way. All views may have children, called subviews. Each view, including the window, owns an ordered list of these subviews. Views might own many subviews; they might own none. Your application determines how views are laid out and who owns whom.

Subviews display in order, always from back to front. This works something like a stack of animation cels—those transparent sheets used to create cartoons. Only the parts of the sheets that have been painted show through. The clear parts allow any visual elements behind that sheet to be seen. Views, too, can have clear and painted parts, and can be layered to build a complex presentation.

Figure 4-1 shows a little of the layering used in a typical window. Here the window owns a UINavigationController-based hierarchy. The elements layer together. The window (represented by the empty, rightmost element) owns a navigation bar, which in turn owns two subview buttons (one left and one right). The window also owns a table with its own subviews. These items stack together to build the GUI.

Figure 4-1. Subview hierarchies combine to build complex GUIs.

Listing 4-1 shows the view hierarchy of the window in Figure 4-1. The tree starts at the top UIWindow and shows the classes for each of the child views. If you trace your way down the tree, you can see the navigation bar (at level 2) with its two buttons (each at level 3) and the table view (level 4) with its two cells (each at level 5). Some of the items in this listing are private classes, automatically added by the SDK when laying out views. For example, the UILayoutContainerView is never used directly by developers. It’s part of the software development kit (SDK) UIWindow implementation.

The only parts missing from this listing are the dozen or so line separators for the table, omitted for space considerations. Each separator is a UITableViewSeparatorView instance. They belong to the UITableView and would normally display at a depth of 5.

Listing 4-1. To-Do List View Hierarchy

Click here to view code image

--[ 1] UILayoutContainerView
----[ 2] UINavigationTransitionView
------[ 3] UIViewControllerWrapperView
--------[ 4] UITableView
----------[ 5] UITableViewCell
------------[ 6] UITableViewCellContentView
--------------[ 7] UILabel
------------[ 6] UIButton
--------------[ 7] UIImageView
------------[ 6] UIView
----------[ 5] UITableViewCell
------------[ 6] UITableViewCellContentView
--------------[ 7] UILabel
------------[ 6] UIButton
--------------[ 7] UIImageView
------------[ 6] UIView
----------[ 5] UIImageView
----------[ 5] UIImageView
----[ 2] UINavigationBar
------[ 3] UINavigationBarBackground
------[ 3] UINavigationItemView
------[ 3] UINavigationButton
--------[ 4] UIImageView
--------[ 4] UIButtonLabel
------[ 3] UINavigationButton
--------[ 4] UIImageView
--------[ 4] UIButtonLabel

Recipe: Recovering a View Hierarchy Tree

Each view knows both its parent (aView.superview) and its children (aView.subviews). A view tree, like the one shown in Listing 4-1, can be built by recursively walking through a view’s subviews. Recipe 4-1 builds a visual tree by noting the class of each view and increasing the indentation level every time it moves down from a parent view to its children. The results are stored into a mutable string and returned from the calling method.

The code shown in Recipe 4-1 was used to create the tree shown in Listing 4-1. You can use this method to duplicate the results of Listing 4-1, or you can copy it to other applications to view their hierarchies.

Recipe 4-1. Extracting a View Hierarchy Tree

Click here to view code image

// Recursively travel down the view tree, increasing the
// indentation level for children
- (void) dumpView: (UIView *) aView atIndent: (int) indent
into:(NSMutableString *) outstring
{
// Add the indentation dashes
for (int i = 0; i < indent; i++)
[outstring appendString:@"--"];

// Follow that with the class description
[outstring appendFormat:@"[%2d] %@\n", indent,
[[aView class] description]];

// Recurse through each subview
for (UIView *view in aView.subviews)
[self dumpView:view atIndent:indent + 1 into:outstring];
}

// Start the tree recursion at level 0 with the root view
- (NSString *) displayViews: (UIView *) aView
{
NSMutableString *outstring = [NSMutableString string];
[self dumpView:aView atIndent:0 into:outstring];
return outstring;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Exploring XIB and Storyboard Views

Many Xcode users create views and view controllers in Interface Builder (IB) using storyboards and XIB files rather than building them directly in code. The following snippet demonstrates how to use Recipe 4-1 to deconstruct views loaded from these resources. The sample code for this recipe includes sample XIB and storyboard files. You can edit them yourself and test out the view dumping code to see how the underlying structure matches the presentation you create in IB:

Click here to view code image

UIView *sampleView = [[[NSBundle mainBundle]
loadNibNamed:@"Sample" owner:self options:NULL] objectAtIndex:0];
if (sampleView)
{
NSMutableString *outstring = [NSMutableString string];
[self dumpView:sampleView atIndent:0 into:outstring];
NSLog(@"Dumping sample view: %@", outstring);
}

UIStoryboard *storyboard = [UIStoryboard
storyboardWithName:@"Sample" bundle:[NSBundle mainBundle]];
UIViewController *vc = [storyboard instantiateInitialViewController];
if (vc.view)
{
NSMutableString *outstring = [NSMutableString string];
[self dumpView:vc.view atIndent:0 into:outstring];
NSLog(@"Dumping sample storyboard: %@", outstring);
}

Recipe: Querying Subviews

Views store arrays of their children. Retrieve these arrays via the subviews property. The child views are always drawn after the parent, in the order that they appear in the subviews array. These views draw in order from back to front, and the subviews array mirrors that drawing pattern. Views that appear later in the array are drawn after views that appear earlier.

The subviews property returns just those views that are immediate children of a given view. At times, you may want to retrieve a more exhaustive list of subviews, including the children’s children. Recipe 4-2 introduces allSubviews(), a simple recursive function that returns a full list of subviews for any view. Call this function with a view’s window (via view.window) to return a complete set of views appearing in the UIWindow that hosts that view. This list proves useful when you want to search for a particular view, such as a specific slider or button.

Although it is not typical, iOS applications may include several windows, each of which can contain many views, some of which may be displayed on an external screen. Recover an exhaustive list of all application views by iterating through each available window. The allApplicationSubviews() function in Recipe 4-2 does exactly that. A call to [[UIApplication sharedApplication] windows] returns the array of application windows. The function iterates through these, adding their subviews to the collection.

In addition to knowing its subviews, each view knows the window it belongs to. The view’s window property points to the window that owns it. Recipe 4-2 also includes a simple function called pathToView() that returns an array of superviews, from the window down to the view in question. It does this by calling superview repeatedly until arriving at a window instance.

Views can also check their superview ancestry in another way. The isDescendantOfView: method determines whether a view lives within another view, even if that view is not its direct superview. This method returns a simple Boolean value. YES means the view descends from the view passed as a parameter to the method.

Recipe 4-2. Subview Utility Functions

Click here to view code image

// Return an exhaustive descent of the view's subviews
NSArray *allSubviews(UIView *aView)
{
NSArray *results = aView.subviews;
for (UIView *eachView in aView.subviews)
{
NSArray *subviews = allSubviews(eachView);
if (subviews)
results = [results arrayByAddingObjectsFromArray: subviews];
}
return results;
}

// Return all views throughout the application
NSArray *allApplicationViews()
{
NSArray *results = [[UIApplication sharedApplication] windows];
for (UIWindow *window in [[UIApplication sharedApplication]
windows])
{
NSArray *subviews = allSubviews(window);
if (subviews) results =
[results arrayByAddingObjectsFromArray: subviews];
}
return results;
}

// Return an array of parent views from the window down to the view
NSArray *pathToView(UIView *aView)
{
NSMutableArray *array = [NSMutableArray arrayWithObject:aView];
UIView *view = aView;
UIWindow *window = aView.window;
while (view != window)
{
view = [view superview];
[array insertObject:view atIndex:0];
}
return array;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Managing Subviews

The UIView class offers numerous methods that help build and manage views. These methods let you add, order, remove, and query the view hierarchy. Because this hierarchy controls what you see, updating the way that views relate to each other changes what you see on iOS. Here are some approaches for typical view-management tasks.

Adding Subviews

Call [parentView addSubview:child] to add new subviews to a parent. This method adds a subview frontmost within their parent view, placed above any existing views. To insert a subview into the view hierarchy at a particular location other than the front, the SDK offers a trio of utility methods:

• insertSubview:atIndex:

• insertSubview:aboveSubview:

• insertSubview:belowSubview:

These methods control where view insertion happens. That insertion can remain relative to another view, or it can move into a specific index of the subviews array. The above and below methods add subviews in front of or behind a given child, respectively. Insertion pushes other views forward and does not replace any views that are already there.

Reordering and Removing Subviews

Applications often reorder and remove views as users interact with the screen. The iOS SDK offers several easy ways to do this, allowing you to change the view order and contents:

• Use [parentView exchangeSubviewAtIndex:i withSubviewAtIndex:j] to exchange the positions of two views.

• Move subviews to the front or back using bringSubviewToFront: and sendSubviewToBack.

• To remove a subview from its parent, call [childView removeFromSuperview]. If the child view had been onscreen, it disappears.

When you reorder, add, or remove views, the screen automatically redraws to show the new view presentation.

View Callbacks

When the view hierarchy changes, callbacks can be sent to the views in question. The iOS SDK offers six callback methods. These callbacks may help your application keep track of views that are moving and changing parents:

• didAddSubview: is sent to a parent view after it has successfully added a child view via addSubview: or one of the other subview insertion methods listed earlier. It lets subclasses of UIView perform additional actions when new views are added.

• didMoveToSuperview: informs views that they’ve been reparented to a new superview. The view may want to respond to that new parent in some way. When the view was removed from its superview, the new parent is nil.

• willMoveToSuperview: is sent before the move occurs.

• didMoveToWindow: provides the callback equivalent of didMoveToSuperview but when the view moves to a new Window hierarchy instead of to just a new superview. You most typically use this when working with external displays with AirPlay.

• willMoveToWindow: is, again, sent before the move occurs.

• willRemoveSubview: informs the parent view that the child view is about to be removed.

I rarely use these methods, but when I do they’re almost always a lifesaver, allowing me to add behavior without having to know in advance what kind of subview or superview class is being used. The window callbacks are used primarily for displaying overlay views in a secondary window such as alerts and input views such as keyboards.

Tagging and Retrieving Views

The iOS SDK offers a built-in search feature that lets you retrieve subviews by tagging them. Tags are just numbers, usually positive integers, that identify a view. Assign them using the view’s tag property: for example, myView.tag = 101. In IB, you can set a view’s tag in the attributes inspector. As Figure 4-2 shows, you specify the tag in the View section.

Figure 4-2. Set the tag for any view in IB’s attributes inspector.

Tags are arbitrary. The only “reserved” tag is 0, which is the default property setting for all newly created views. It’s up to you to decide how you want to tag your views and which values to use. You can tag any instance that is a child of UIView, including windows and controls. So if you have many buttons and switches, adding tags helps tell them apart when users trigger them. You can add a simple switch statement to your callback methods that looks at the tag and determines how to react.

Apple rarely tags subviews. The only instance I have ever found of their view tagging has been in UIAlertViews where the buttons use tags of 1, 2, and so forth, and it has been several years since that happened. (I’m mostly convinced Apple left this tagging in there as a mistake.) If you worry about conflicting with Apple tags, start your numbering at 10 or 100, or some other number higher than any value Apple might use.

Using Tags to Find Views

Tags let you avoid passing user interface elements around your program by making them directly accessible from any parent view. The viewWithTag: method recovers a tagged view from a child hierarchy. The search is recursive, so the tagged item need not be an immediate child of the view in question. You can search from the window with [window viewWithTag:101] and find a view that is several branches down the hierarchy tree. When more than one view uses the same tag, viewWithTag: returns the first item it finds.

The only challenge about using viewWithTag: is that it returns a UIView object. This means you often have to cast it to the proper type before you can use it. For example, you can retrieve a label and set its text, like this:

Click here to view code image

UILabel *label = (UILabel *)[self.view.window viewWithTag:101];
label.text = @"Hello World";

Recipe: Naming Views by Object Association

Although tagging offers a handy approach to identifying views, some developers may prefer to work with names rather than numbers. Using names adds an extra level of meaning to your view identification schemes. Instead of referring to “the view with a tag of 101,” a switch named Ignition Switch describes its role and adds a level of self-documentation missing from a plain number:

Click here to view code image

// Toggle switch
UISwitch *s = (UISwitch *)[self.view viewNamed:@"Ignition Switch"];
[s setOn:!s.isOn];

It’s easy to extend UIView to add a nametag property and retrieve views by name. The secret lies in Objective-C’s runtime associated object functions. If you’ve ever written class categories, you may be thinking, “But if I add new storage, won’t I need to subclass?” Associated objects don’t require new instance variables. Instead, they provide a way to use key-value pairs outside of an object’s direct storage, associating that object with information stored elsewhere.

Recipe 4-3 creates a UIView name tag category. It consists of a single property (nametag), which is supported by associated objects and a method (viewNamed:) that works to find any subview by name. The method descends the view hierarchy with a recursive depth-first search and returns a subview whose name matches a search string.

Naming Views in Interface Builder

Using a named view approach allows you to retrieve subviews without having to declare IBOutlet instance variables. (Whether this is a net benefit to code readability and maintainability is a question that lies outside the scope of this section.) Consider the code you saw earlier in this section that toggles a switch from within an interface. You can add that name (Ignition Switch) as a custom runtime attribute in IB.

Figure 4-3 shows how you do this. Select any view and open the Identity Inspector (View > Utilities > Show Identity Inspector). Locate the User Defined Runtime Attributes section and click + to add a new attribute. Set the Key Path to nametag (to match the property defined in Recipe 4-3’s UIView class category), the type to String, and the value to the view’s new name. Save your changes.

Figure 4-3. Set the tag for any view in Interface Builder’s attributes inspector. You may assign User Defined Runtime Attributes for any key-value coded (KVC) object value. These values are set at the time the XIB file loads.

Upon doing so, you’ll be able to use the category’s viewNamed: method to retrieve the switch via code, and toggle its state.

Note

You can name a view’s layer directly, without associated objects. CALayer instances offer a name property, which helps identify layers when you’re working with them. To use layers, include the Quartz Core framework in your project and access each layer via view.layer.

Recipe 4-3. Naming Views

Click here to view code image

#import <objc/runtime.h>
@implementation UIView (NameExtensions)

// Static variable's address acts as the key
// Thanks, Oliver Drobnik
static const char nametag_key;

- (id) nametag
{
return objc_getAssociatedObject(self, (void *) &nametag_key);
}

- (void)setNametag:(NSString *) theNametag
{
objc_setAssociatedObject(self, (void *) &nametag_key,
theNametag, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (UIView *) viewWithNametag: (NSString *) aName
{
if (!aName) return nil;

// Is this the right view?
if ([self.nametag isEqualToString:aName])
return self;

// Recurse depth first on subviews
for (UIView *subview in self.subviews)
{
UIView *resultView = [subview viewNamed:aName];
if (resultView) return resultView;
}

// Not found
return nil;

- (UIView *) viewNamed: (NSString *) aName
{
if (!aName) return nil;
return [self viewWithNametag:aName];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

View Geometry

Geometry plays a fundamental role when working with views. Geometric properties define where each view appears, what their sizes are, and how they are oriented. Some issues become less critical as you work with the new autolayout mechanism introduced in iOS 6. Others remain fundamental, especially when crafting dynamic view systems that change and update during user interaction.

Autolayout allows you to build visual interfaces by defining a system of constraints. These constraints are basic rules that specify how your views relate to each other geometrically. However, constraint-based layouts are intended for fairly static interfaces. They are used to calculate how subviews should be set within their parents as their parents are created and displayed. (See Chapter 5, “View Constraints,” for a full discussion of this feature.)

When working with dynamic views, views with short life spans, and ones whose geometry changes during presentation, you need to step away from constraints and focus on immediate handling of the basic layout associated with each view. The UIView class provides two built-in properties that define these layout aspects.

Every view uses a frame to define its boundaries. The frame specifies the outline of the view, its location, width, and height, within the coordinate system of its parent view. The associated bounds and center properties respectively define the frame rectangle within the view’s own coordinate system and the geometric center of the frame in the parent’s coordinate system. These three properties are tightly integrated.

If you change a view’s frame, the view updates to match the new frame. Use a bigger width and the view stretches. Use a new location and the view moves. The view’s frame delineates each view’s outline. View sizes are not limited to the screen size. A view can be smaller than the screen or larger. It can also be smaller or larger than its parent.

Views also use a transform property that updates a view’s presentation, via affine transformations. These are mathematical equations that adjust a view’s 2D geometry. A view might be stretched or squashed by applying a transform, or it might be rotated away from vertical. Together the frame and transform fully define a view’s core geometry.

Frames

Frame rectangles refer to the outline of a view in terms of its parent’s coordinate system. Frames use a CGRect structure, which is defined as part of the Core Graphics framework as its CG prefix suggests. A CGRect is made up of an origin (a CGPoint, x and y) and a size (a CGSize, width and height). When you create views, you normally allocate them and initialize them with a frame. Here’s an example:

Click here to view code image

CGRect rect = CGRectMake(0.0f, 0.0f, 320.0f, 416.0f);
myView = [[UIView alloc] initWithFrame: rect];

Views provide two fundamental CGRect properties, which are closely tied together. The frame and the bounds. Frames are different from bounds in terms of their coordinate system. Frames are defined with respect to the parent’s system, bounds are defined with respect to the view’s own coordinate system. For that reason, a view’s bounds typically use a zero origin. Its coordinate system normally begins at its top-left corner. For some views, like scroll views, bounds may extend beyond their visual frame.

Rectangle Utility Functions

As you’ve seen, the CGRectMake() function creates its new rectangle using four parameters: the origin’s x and y locations, the width of the rectangle, and its height. This method provides a critical utility for creating frames. In addition to CGRectMake(), you may want to be aware of several other convenience functions that help you work with rectangles and frames:

• NSStringFromCGRect(aCGRect) converts a CGRect structure to a formatted string. This function makes it easy to log a view’s frame when you’re debugging.

• CGRectFromString(aString) recovers a rectangle from its string representation. It proves useful when you’ve stored a view’s frame as a string in user defaults and want to convert that string back to a CGRect.

• Although not a function, [NSValue valueWithCGRect:rect] returns a new Objective-C value object that stores the passed rectangle. You can then add the object to dictionaries and arrays as needed. The CGRectValue method retrieves the rectangular structure from the NSValue object. Variations on this approach exist for most CG types including points, sizes, and affine transforms.

• CGRectInset(aRect, xinset, yinset) enables you to create a smaller or larger rectangle that’s centered on the same point as the source rectangle. Use a positive inset for smaller rectangles, negative for larger ones.

• CGRectOffset(aRect, xoffset, yoffset) returns a rectangle that’s offset from the original rectangle by an x and y amount you specify. This is handy for moving frames around the screen and for creating easy drop-shadow effects.

• CGRectGetMidX(aRect) and CGRectGetMidY(aRect) recover the x and y coordinates in the center of a rectangle. These functions make it very convenient to recover the midpoints of bounds and frames.

• CGRectIntersectsRect(rect1, rect2) lets you know whether rectangle structures intersect. Use this function to know when two rectangular objects overlap. You can retrieve the actual intersection via CGRectIntersection(rect1, rect2). This returns the null rectangle if the two rects do not intersect. (Use CGRectIsNull(rect) to check.) The related CGRectContainsPoint(rect, point) returns true when a provided point is located within the (non-null) rectangle.

• Compare rectangles using CGRectEqualToRect(rect1, rect2). This function checks whether two rectangles are equal in both their size and their position. Similar methods include CGSizeEqualToSize(size1, size2) and CGPointEqualToPoint(point1, point2), which allow you to compare CGSize and CGPoint instances.

• Other handy utilities include CGRectDivide(), which splits a source rectangle into two components, and CGRectApplyAffineTransform(rect, transform), which applies an affine transform to a rectangle and returns the smallest rectangle that can contain the results.

• CGRectZero is a rectangle constant located at (0,0) whose width and height are zero. You can use this constant when you’re required to create a frame but are unsure what that frame size or location will be at the time of creation. Similar constants are CGPointZero and CGSizeZero.

Points and Sizes

The CGRect structure is made up of two substructures: CGPoint, which defines the rectangle’s origin, and CGSize, which defines its bounds. Points refer to locations defined with x and y coordinates; sizes have width and height. Use CGPointMake(x, y) to create points. CGSizeMake(width, height) creates sizes. Although these two structures appear to be the same (two floating-point values), the iOS SDK differentiates between them semantically. Points refer to locations. Sizes refer to extents. You cannot set myFrame.origin to a size.

As with rectangles, you can convert them to and from strings: NSStringFromCGPoint(), NSStringFromCGSize(), CGSizeFromString(), and CGPointFromString() perform these functions. You can also transform points and sizes to and from dictionaries.

Transforms

The iOS SDK includes affine transformations as part of its Core Graphics implementation. Affine transforms allow points in one coordinate system to transform into another coordinate system. These functions are widely used in both 2D and 3D animations. The version used with UIKit views uses a 3-by-3 matrix to define UIView transforms, making it a 2D-only solution. 3D transforms use a 4-by-4 matrix, and are the default for Core Animation layers. With affine transforms, you can scale, translate, and rotate your views in real time. You do so by setting the view’s transform property. Here’s an example:

Click here to view code image
float angle = theta * (PI / 100);
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
myView.transform = transform;

The transform is always applied with respect to the view’s center. So when you apply a rotation like this, the view rotates around its center. If you need to rotate around another point, you must first translate the view, then rotate, and then return from that translation. There are ways around this by working directly with the view’s layer property, but that approach lies outside the scope of this chapter.

To revert any changes, set the transform property to the identity transform. This restores the view back to the last settings for its frame:

myView.transform = CGAffineTransformIdentity;

Note

On iOS, the y coordinate starts at the top and increases downward. This is similar to the coordinate system in PostScript but opposite the Quartz coordinate system historically used on the Mac. On iOS, the origin is in the top-left corner, not the bottom left. iOS 6 continues to move many feature originally grounded in Quartz and Core Graphics into the UIKit world. This migration reduces the occasions you need to flip your coordinate system when laying out text or processing images.

Coordinate Systems

As mentioned earlier, views live in two worlds. Their frames and centers are defined in the coordinate system of their parents. Their bounds and subviews are defined in their own coordinate system. The iOS SDK offers several utilities that allow you to move between these coordinate systems so long as the views involved live within the same UIWindow. To convert a point from another view into your own coordinate system, use convertPoint:fromView:. Here’s an example:

myPoint = [myView convertPoint:somePoint fromView:otherView];

If the original point indicated the location of some object, the new point retains that location but gives the coordinates with respect to myView’s origin. To go the other way, use convertPoint:toView: to transform a point into another view’s coordinate system. Similarly, convertRect:toView: and convertRect:fromView: work with CGRect structures rather than CGPoint ones.

Be aware that the coordinate system for an iOS device may not match the pixel system used to display that system. The discrete 640-by-960-pixel Retina display on the iPhone 4S, for example, is addressed through a continuous 320-by-480 coordinate system in the SDK. Although you can supply higher-quality art to fill those pixels on newer units, any locations you specify in your code access the same coordinate system used for older, lower pixel-density units. The position (160.0, 240.0) remains approximately in the center of the 3.5″ iPhone or iPod touch screens, regardless of pixel density. That center point moves to (160.0, 284.0) on 4″ iPhones and iPod touches, all of which use Retina displays.

Note

The UIScreen class provides a property called scale that defines the relationship between a display’s pixel density and its point system. A screen’s scale is used to convert from the logical coordinate space of the view system (measured in points, and approximately equal to 1/160th of an inch) to the physical pixel coordinates. Retina displays use a scale of 2.0, non-Retina displays use a scale of 1.0.

Recipe: Working with View Frames

When you change a view’s frame, you update its size (that is, its width and height) and its location. For example, you might move a frame as follows. This code creates a subview located at (0.0, 0.0) and then moves it down to (0.0, 30.0):

Click here to view code image

CGRect initialRect = CGRectMake(0.0f, 0.0f, 100.0f, 100.0f);
myView = [[UIView alloc] initWithFrame:initialRect];
[topView addSubview:myView];
myView.frame = CGRectMake(0.0f, 30.0f, 100.0f, 100.0f);

This approach for moving views is fairly uncommon. The iOS SDK does not expect you to move views by changing frames. Instead, it focuses on a view’s position. The preferred way to do this is by setting the view’s center. This is a view property, which you can set directly:

myView.center = CGPointMake(160.0f, 55.0f);

Although you’d expect the SDK to offer a way to move a view by updating its origin, no such option exists. It’s easy enough to build your own view class category. Retrieve the view frame, set the origin to the requested point, and then update the frame with the change. This snippet creates a new origin property letting you retrieve and change the view’s origin:

- (void) setOrigin: (CGPoint) aPoint
{
CGRect newframe = self.frame;
newframe.origin = aPoint;
self.frame = newframe;
}

Because this extension uses such an obvious property name, if Apple eventually implements the features shown here, your code may break due to name overlap. In my examples in this book, I widely use obvious names. It makes code snippets easier to read and reduces any cognitive burden in recognizing what I’m demonstrating. Avoid doing so in your production code. Using your personal or company initials as a prefix helps distinguish in-house material.

When you move a view, you don’t need to worry about things such as rectangular sections that have been exposed or hidden. iOS takes care of the redrawing. This lets you treat your views like tangible objects and delegate rendering issues to Cocoa Touch.

Adjusting Sizes

In the simplest usage patterns, a view’s frame and bounds control its size. Frames, as you’ve already seen, define the location of a view in its parent’s coordinate system. If the frame’s origin is set to (0.0, 30.0), the view appears in the superview flush with the left side of the view and offset 30 points from the top. On older displays, this corresponds to 30 pixels down; on Retina displays, it is 60 pixels down.

Bounds define a view within its own coordinate system. That means the origin for a view’s bounds (that is, myView.bounds) is normally (0.0, 0.0). For most views, its size matches its normal extent, that is, the frame’s size property. (This isn’t always true for some classes like UIScrollView, whose extent may exceed their visual display.)

You can change a view’s size by adjusting either its frame or its bounds. In practical terms, you’re updating the size component of those structures. As with moving origins, it’s simple to create your own view utility method to do this directly:

- (void) setSize: (CGSize) aSize
{
CGRect newbounds = self.bounds;
newbounds.size = aSize;
self.bounds = newbounds;
}

When a displayed view’s size changes, the view itself updates live. Depending on how the elements within the view are defined and the class of the view itself, subviews may shrink or move to fit or they may get cropped depending on a number of flags and whether views are participating in the new autolayout system (see Chapter 5):

• The autoresizesSubviews property determines whether a view automatically resizes its subviews when it updates its bounds.

• A views autoresizingMask defines how a view reacts to changes in its parent’s bounds. If a view participates in a constraint system, this mask is ignored and the view will be adjusted by iOS’s autolayout system.

• The clipsToBounds flag determines whether subviews are visible outside of a view’s bounds. When clipped, only material within the parent’s bounds are shown. You can tell a view to sizeToFit, so it resizes to enclose all its subviews.

• The contentMode is related to other view resizing properties, but specifies how a view’s layer (its content bitmap) adjusts when its bounds update. This property, which can be set to a number of scaling, centering, and fitting choices, is best seen when working with image views.

Note

Bounds are affected by a view’s transform, a mathematical component that changes the way the view appears. Do not manipulate a view’s frame when working with transforms because it may not produce expected results. (Some workarounds follow later in this chapter.) For example, after a transform, the frame’s origin may no longer correspond mathematically to origin of the bounds. The normal order of updating a view is to set its frame or bounds, then set its center, and then set its transforms if applicable.

Sometimes, you need to resize a view before adding it to a new parent. For example, you might have an image view to place into an alert view. To fit that view into place without changing its aspect ratio, you might use a method like this to ensure that both the height and width scale appropriately:

Click here to view code image

- (void) fitInSize: (CGSize) aSize
{
CGFloat scale;
CGRect newframe = self.frame;

if (newframe.size.height > aSize.height)
{
scale = aSize.height / newframe.size.height;
newframe.size.width *= scale;
newframe.size.height *= scale;
}

if (newframe.size.width >= aSize.width)
{
scale = aSize.width / newframe.size.width;
newframe.size.width *= scale;
newframe.size.height *= scale;
}
self.frame = newframe;
}

CGRects and Centers

As you’ve seen, UIView instances use CGRect structures composed of an origin and a size to define their frames. This structure contains no references to a center point. At the same time, UIViews depend on their center property to update a view’s position when you move a view to a new point. Unfortunately, Core Graphics doesn’t use centers as a primary rectangle concept. As far as centers are concerned, Core Graphics’ built-in utilities are limited to recovering a rectangle’s midpoint along the x- or y-axis.

You can bridge this gap by constructing functions that coordinate between the origin-based CGRect struct and center-based UIView objects. This function retrieves the center from a rectangle by building a point from the x and y midpoints. It takes one argument, a rectangle, and returns its center point:

Click here to view code image

CGPoint CGRectGetCenter(CGRect rect)
{
CGPoint pt;
pt.x = CGRectGetMidX(rect);
pt.y = CGRectGetMidY(rect);
return pt;
}

Moving a rectangle by its center point is another function that may prove helpful, and one that mimics the way UIViews work. Suppose, for example, that you need to move a view to a new position but need to keep it inside its parent’s frame. To test before you move, you could use a function like this to offset the view frame to a new center. You could then test that offset frame against the parent (use CGRectContainsRect()) and ensure that the view won’t stray outside its container:

Click here to view code image

CGRect CGRectMoveToCenter(CGRect rect, CGPoint center)
{
CGRect newrect = CGRectZero;
newrect.origin.x = center.x-CGRectGetMidX(rect);
newrect.origin.y = center.y-CGRectGetMidY(rect);
newrect.size = rect.size;
return newrect;
}

Often you need to center one view in another. Here’s how you can retrieve a frame that corresponds to a centered subrectangle. Pass the outer view’s bounds when adding a subview (the subview coordinate system needs to start with 0,0), or its frame when adding a view to the outer view’s parent:

Click here to view code image

CGRect CGRectCenteredInRect(CGRect rect, CGRect mainRect)
{
CGFloat xOffset = CGRectGetMidX(mainRect)-CGRectGetMidX(rect);
CGFloat yOffset = CGRectGetMidY(mainRect)-CGRectGetMidY(rect);
return CGRectOffset(rect, xOffset, yOffset);
}

Other Geometric Elements

As you’ve seen, it’s convenient to use a view’s origin and size as well as its center property, allowing you to work more natively with Core Graphics calls. You can build on this idea to expose other properties of the view, including its width and height, as well as basic geometry such as its left, right, top, and bottom points.

In some ways, this breaks Apple’s design philosophy. This exposes items that normally fall into structures without reflecting the structures. At the same time, it can be argued that these elements are true view properties. They reflect fundamental view characteristics and deserve to be exposed as properties.

Recipe 4-4 provides a full view frame utility category for UIView, letting you make the choice of whether to expose these properties. These properties do not take transforms into account.

Recipe 4-4. UIView Frame Geometry Category

Click here to view code image

@interface UIView (ViewFrameGeometry)
@property CGPoint origin;
@property CGSize size;

@property (readonly) CGPoint midpoint;

// topLeft is synonymous with origin, so not included here
@property (readonly) CGPoint bottomLeft;
@property (readonly) CGPoint bottomRight;
@property (readonly) CGPoint topRight;

@property CGFloat height;
@property CGFloat width;
@property CGFloat top;
@property CGFloat left;
@property CGFloat bottom;
@property CGFloat right;

- (void) moveBy: (CGPoint) delta;
- (void) scaleBy: (CGFloat) scaleFactor;
- (void) fitInSize: (CGSize) aSize;
@end

@implementation UIView (ViewGeometry)
// Retrieve and set the origin
- (CGPoint) origin
{
return self.frame.origin;
}

- (void) setOrigin: (CGPoint) aPoint
{
CGRect newframe = self.frame;
newframe.origin = aPoint;
self.frame = newframe;
}

// Retrieve and set the size
- (CGSize) size
{
return self.frame.size;
}

- (void) setSize: (CGSize) aSize
{
CGRect newframe = self.frame;
newframe.size = aSize;
self.frame = newframe;
}

// Query other frame locations

- (CGPoint) midpoint
{
// midpoint is with respect to a view's own coordinate system
// versus its center, which is with respect to its parent
CGFloat x = CGRectGetMidX(self.bounds);
CGFloat y = CGRectGetMidY(self.bounds);
return CGPointMake(x, y);
}

- (CGPoint) bottomRight
{
CGFloat x = self.frame.origin.x + self.frame.size.width;
CGFloat y = self.frame.origin.y + self.frame.size.height;
return CGPointMake(x, y);
}

- (CGPoint) bottomLeft
{
CGFloat x = self.frame.origin.x;
CGFloat y = self.frame.origin.y + self.frame.size.height;
return CGPointMake(x, y);
}

- (CGPoint) topRight
{
CGFloat x = self.frame.origin.x + self.frame.size.width;
CGFloat y = self.frame.origin.y;
return CGPointMake(x, y);
}

// Retrieve and set height, width, top, bottom, left, right
- (CGFloat) height
{
return self.frame.size.height;
}

- (void) setHeight: (CGFloat) newheight
{
CGRect newframe = self.frame;
newframe.size.height = newheight;
self.frame = newframe;
}

- (CGFloat) width
{
return self.frame.size.width;
}

- (void) setWidth: (CGFloat) newwidth
{
CGRect newframe = self.frame;
newframe.size.width = newwidth;
self.frame = newframe;
}

- (CGFloat) top
{
return self.frame.origin.y;
}

- (void) setTop: (CGFloat) newtop
{
CGRect newframe = self.frame;
newframe.origin.y = newtop;
self.frame = newframe;
}

- (CGFloat) left
{
return self.frame.origin.x;
}

- (void) setLeft: (CGFloat) newleft
{
CGRect newframe = self.frame;
newframe.origin.x = newleft;
self.frame = newframe;
}

- (CGFloat) bottom
{
return self.frame.origin.y + self.frame.size.height;
}

- (void) setBottom: (CGFloat) newbottom
{
CGRect newframe = self.frame;
newframe.origin.y = newbottom - self.frame.size.height;
self.frame = newframe;
}

- (CGFloat) right
{
return self.frame.origin.x + self.frame.size.width;
}

- (void) setRight: (CGFloat) newright
{
CGFloat delta = newright —
(self.frame.origin.x + self.frame.size.width);
CGRect newframe = self.frame;
newframe.origin.x += delta;
self.frame = newframe;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Retrieving Transform Information

Affine transforms enable you to change an object’s geometry by mapping that object from one view coordinate system into another. The iOS SDK fully supports standard affine 2D transforms. With them, you can scale, translate, rotate, and skew your views however your heart desires and your application demands.

Transforms are defined in Core Graphics and consist of calls such as CGAffineTransformMakeRotation() and CGAffineTransformScale(). These build and modify 3-by-3 transform matrices. After these are built, use UIView’s transform property to assign 2D affine transformations to UIView objects.

For example, you might apply a rotation transform directly. This removes any existing transforms and replaces it with a simple rotation. The functions with make in their name create new transforms:

theView.transform = CGAffineTransformMakeRotation(radians);

Or, you might add a scaling transform onto whatever transformations have already been applied to the view. The functions without the word make take a transform as their first parameter and return an updated transform after applying a transformation according to the function arguments:

Click here to view code image

CGAffineTransform scaled = CGAffineTransformScale(theView.transform,
degree, degree);
theView.transform = scaled;

Retrieving Transform Properties

When working with transforms, iOS can provide an affine representation of the transform associated with a view. This representation will not, however, tell you exactly how much the view has been scaled or rotated. Recipe 4-5 addresses this problem by calculating these via a simple UIView category.

An affine matrix is stored in iOS as a structure of six fields: a, b, c, d, tx, and ty. Figure 4-4 shows how these values relate to their positions in the standard affine matrix. Simple math allows you to derive scaling and rotation from these, as is shown in Recipe 4-5. Note how you can retrieve the tx and ty values directly from the transform.

Figure 4-4. The CGAffineTransform structure holds an affine transformation matrix by defining six key values in its fields (left). After applying an affine transform, a view’s origin may no longer coincide with its frame’s origin (right).

In addition to asking, “what is the view’s current rotation,” and “by how much is it scaled,” you often need to perform math that relates its current geometry post-transform to the parent coordinate system. To do this, you need to be able to specify where elements appear onscreen.

A view’s center makes the transition from pre-transform to post-transform without incident. The value may change, especially after scaling, but the property remains meaningful regardless of whatever transform has been applied. The center property always refers to the geometric center of the view’s frame within the parent’s coordinate system.

The frame is not so resilient. After rotation, a view’s origin may be completely decoupled from the view. Look at Figure 4-4 (right). It shows a rotated view on top of its original frame (the smaller of the two outlines) and the updated frame (the larger outline). The circles indicate the view’s origin before and after rotation.

After the transform is applied, the frame updates to the minimum bounding box that encloses the view. Its new origin (the top-left corner of the outside view) has essentially nothing to do with the updated view origin (the circle at the top-middle). iOS does not provide a way to retrieve that adjusted point.

Recipe 4-5 introduces several methods that perform that math for you. It establishes properties that return a transformed view’s corners: top left, top right, bottom left, and bottom right. These coordinates are defined in the parent view; so if you want to add a new view on top of the top circle in Figure 4-4 (right), you place its center at theView.transformedTopLeft.

The recipe also offers an originalFrame method, which returns the inner (original) frame shown in Figure 4-4, even when a transform has been applied. It does so in a rather ham-fisted way, but it works.

Testing for View Intersection

By reader request, Recipe 4-5 adds code to check whether two transformed views intersect. The code also works with views that have not been transformed so that you can use it with any two views, although it’s a bit pointless to do so. (You can use the CGRectIntersectsRect() function for simple untransformed frames.) This custom intersection method works best for views whose frames do not represent their underlying geometry, like the one shown in Figure 4-4.

The intersectsView: method applies an axis separation algorithm for convex polygons. For each edge of each view, it tests whether all the points in one view fall on one side of the edge, and whether all the points of the other view fall on the other side. This test is based on the half plane function, which returns a value indicating whether a point is on the left or right side of an edge.

As soon as it finds an edge that satisfies this condition, the intersectsView : method returns NO. The views cannot geometrically intersect if there’s a line that separates all the points in one object from all the points in the other.

If all eight tests fail (four edges on the first view, four edges on the second), the method concludes that the two views do intersect. It returns YES.

Recipe 4-5. Retrieving Transform Values

Click here to view code image

@implementation UIView (Transform)
- (CGFloat) xscale
{
CGAffineTransform t = self.transform;
return sqrt(t.a * t.a + t.c * t.c);
}

- (CGFloat) yscale
{
CGAffineTransform t = self.transform;
return sqrt(t.b * t.b + t.d * t.d);
}

- (CGFloat) rotation
{
CGAffineTransform t = self.transform;
return atan2f(t.b, t.a);
}

- (CGFloat) tx
{
CGAffineTransform t = self.transform;
return t.tx;
}

- (CGFloat) ty
{
CGAffineTransform t = self.transform;
return t.ty;
}

// The following three methods move points into and out of the
// transform coordinate system whose origin is at the view center

- (CGPoint) offsetPointToParentCoordinates: (CGPoint) aPoint
{
return CGPointMake(aPoint.x + self.center.x,
aPoint.y + self.center.y);
}

- (CGPoint) pointInViewCenterTerms: (CGPoint) aPoint
{
return CGPointMake(aPoint.x - self.center.x, aPoint.y - self.center.y);
}

- (CGPoint) pointInTransformedView: (CGPoint) aPoint
{
CGPoint offsetItem = [self pointInViewCenterTerms:aPoint];
CGPoint updatedItem = CGPointApplyAffineTransform(
offsetItem, self.transform);
CGPoint finalItem =
[self offsetPointToParentCoordinates:updatedItem];
return finalItem;
}

// Return the original frame without transform
- (CGRect) originalFrame
{
CGAffineTransform currentTransform = self.transform;
self.transform = CGAffineTransformIdentity;
CGRect originalFrame = self.frame;
self.transform = currentTransform;

return originalFrame;
}

// These four methods return the positions of view elements
// with respect to the current transform

- (CGPoint) transformedTopLeft
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
return [self pointInTransformedView:point];
}

- (CGPoint) transformedTopRight
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.x += frame.size.width;
return [self pointInTransformedView:point];
}

- (CGPoint) transformedBottomRight
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.x += frame.size.width;
point.y += frame.size.height;
return [self pointInTransformedView:point];
}

- (CGPoint) transformedBottomLeft
{
CGRect frame = self.originalFrame;
CGPoint point = frame.origin;
point.y += frame.size.height;
return [self pointInTransformedView:point];
}

// Determine if two views intersect, with respect to any
// active transforms

// After extending a line, determine which side of the half
// plane defined by that line, a point will appear
BOOL halfPlane(CGPoint p1, CGPoint p2, CGPoint testPoint)
{
CGPoint base = CGPointMake(p2.x - p1.x, p2.y - p1.y);
CGPoint orthog = CGPointMake(-base.y, base.x);
return (((orthog.x * (testPoint.x - p1.x)) +
(orthog.y * (testPoint.y - p1.y))) >= 0);
}

// Utility test for testing view points against a proposed line
BOOL intersectionTest(CGPoint p1, CGPoint p2, UIView *aView)
{
BOOL tlTest = halfPlane(p1, p2, aView.transformedTopLeft);
BOOL trTest = halfPlane(p1, p2, aView.transformedTopRight);
if (tlTest != trTest) return YES;

BOOL brTest = halfPlane(p1, p2, aView.transformedBottomRight);
if (tlTest != brTest) return YES;

BOOL blTest = halfPlane(p1, p2, aView.transformedBottomLeft);
if (tlTest != blTest) return YES;

return NO;
}

// Determine whether the view intersects a second view
// with respect to their transforms
- (BOOL) intersectsView: (UIView *) aView
{
if (!CGRectIntersectsRect(self.frame, aView.frame)) return NO;

CGPoint A = self.transformedTopLeft;
CGPoint B = self.transformedTopRight;
CGPoint C = self.transformedBottomRight;
CGPoint D = self.transformedBottomLeft;

if (!intersectionTest(A, B, aView))
{
BOOL test = halfPlane(A, B, aView.transformedTopLeft);
BOOL t1 = halfPlane(A, B, C);
BOOL t2 = halfPlane(A, B, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(B, C, aView))
{
BOOL test = halfPlane(B, C, aView.transformedTopLeft);
BOOL t1 = halfPlane(B, C, A);
BOOL t2 = halfPlane(B, C, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(C, D, aView))
{
BOOL test = halfPlane(C, D, aView.transformedTopLeft);
BOOL t1 = halfPlane(C, D, A);
BOOL t2 = halfPlane(C, D, B);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(D, A, aView))
{
BOOL test = halfPlane(D, A, aView.transformedTopLeft);
BOOL t1 = halfPlane(D, A, B);
BOOL t2 = halfPlane(D, A, C);
if ((t1 != test) && (t2 != test)) return NO;
}

A = aView.transformedTopLeft;
B = aView.transformedTopRight;
C = aView.transformedBottomRight;
D = aView.transformedBottomLeft;

if (!intersectionTest(A, B, self))
{
BOOL test = halfPlane(A, B, self.transformedTopLeft);
BOOL t1 = halfPlane(A, B, C);
BOOL t2 = halfPlane(A, B, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(B, C, self))
{
BOOL test = halfPlane(B, C, self.transformedTopLeft);
BOOL t1 = halfPlane(B, C, A);
BOOL t2 = halfPlane(B, C, D);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(C, D, self))
{
BOOL test = halfPlane(C, D, self.transformedTopLeft);
BOOL t1 = halfPlane(C, D, A);
BOOL t2 = halfPlane(C, D, B);
if ((t1 != test) && (t2 != test)) return NO;
}
if (!intersectionTest(D, A, self))
{
BOOL test = halfPlane(D, A, self.transformedTopLeft);
BOOL t1 = halfPlane(D, A, B);
BOOL t2 = halfPlane(D, A, C);
if ((t1 != test) && (t2 != test)) return NO;
}

return YES;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Display and Interaction Traits

In addition to physical screen layout, the UIView class provides properties that control how your view appears and whether users can interact with it. Every view uses an opaqueness factor (alpha) that ranges between opaque and transparent. Adjust this by issuing [myView setAlpha:value] or setting the myView.alpha property where the alpha values fall between 0.0 (fully transparent) and 1.0 (fully opaque). This is a great way to fade views in and out. (Use the hidden property to hide views entirely.)

You can assign a color to the background of any view. For example, the following property colors your view red:

myView.backgroundColor = [UIColor redColor]

This property affects different view classes in different ways, depending on whether those views contain subviews that block the background. Create a transparent background by setting the view’s background color to clear, as shown here:

myView.backgroundColor = [UIColor clearColor];

Every view offers a background color property, regardless of whether you can see the background. Using bright, contrasting background colors is a great way to visualize the true extents of views. When you’re new to iOS development, coloring in views provides you a concrete sense of what is and is not onscreen and where each component is located.

Not all colors are solid tints. The UIColor class lets you use tiled patterns just as you would a solid color. The colorWithPatternImage: method returns a UIColor instance built from a pattern image you supply. This method helps build textures that you can use to color views.

The userInteractionEnabled property controls whether users can touch and interact with a given view. For most views, this property defaults to YES. For UIImageView, it defaults to NO, which can cause a lot of grief among beginning developers. They often place a UIImageView as their backsplash and don’t understand why their switches, text entry fields, and buttons do not work. Make sure to enable the property for any view that needs to accept touches, whether for itself or for its subviews, which may include buttons, switches, pickers, and other controls. If you’re experiencing trouble with items that seem unresponsive to touch, check the userInteractionEnabled property value for that item and for its parents.

Disable this property for any display-only view you layer over your interaction area. To show a noninteractive overlay clock, for example, via a transparent full-screen view, unset its interaction by assigning its userInteractionEnabled flag to NO. This allows touches to pass through the view and fall below to the actual interaction area of your application. To create a please-wait-style blocker, make sure to enable user interaction for your overlay. This catches user taps and prevents users from accessing your primary interface behind that overlay.

You may also want to disable the property during transitions, to ensure that user taps do not trigger actions as views are being animated. Unwanted touches can be a problem particularly for games and puzzles.

UIView Animations

UIView animation provides one of the odd but lovely perks of working with iOS as a development platform. It enables you to create a moving expression of visual changes when updating views, producing smooth animated results that enhance the user experience. Best of all, this all occurs without you having to do much work.

UIView animations are perfect for building a visual bridge between a view’s current and changed states. With them, you emphasize visual change and create an animation that links those changes together. Animatable changes include the following:

• Changes in location—Moving a view around the screen by updating its center

• Changes in size—Updating the view’s frame and bounds

• Changes in stretching—Updating the view’s content stretch regions

• Changes in transparency—Altering the view’s alpha value

• Changes in color—Updating a view’s background color

• Changes in rotation, scaling, and translation—Basically, any affine transforms you apply to a view

Animations underwent a profound redesign between the 3.x and 4.x SDKs. Starting with the 4.x SDK, developers were offered a way to use the new Objective-C blocks paradigm to simplify animation tasks. Although you can still work with the original animation transaction techniques, the new alternatives provide a much easier approach and the Apple documentation specifically discourages the old-style approach.

Note

Most Apple-native animations last about a third or a half a second. When working with helper views (playing supporting roles that are similar to Apple’s keyboard or alerts), you may want to match your animation durations to these timings. Call [UIApplication statusBarOrientationAnimationDuration] to retrieve a standard time interval.

Building Animations with Blocks

Blocks constructs simplify creating basic animation effects in your code. Consider the following snippet. It produces a fade-out effect for a view with a single statement in an embedded block:

Click here to view code image

[UIView animationWithDuration: 1.0f
animations:^{contentView.alpha = 0.0f;}];

Adding a completion block lets you tidy up once your animation finishes. The following snippet fades out the content view and then removes it from its superview upon completing the animation:

Click here to view code image

[UIView animationWithDuration: 1.0f
animations:^{contentView.alpha = 0.0f;}
completion:^(BOOL done){[contentView removeFromSuperview];}];

If you’ve moved from the old approach into the blocks approach, you’ll recognize the simple elegance of the newer implementations. Should you need to add further options to your animations, a full-service blocks-based method (animateWithDuration:delay:options:animations:completion:) provides both a way to pass animation options (as a mask) and to delay the animation (allowing a simple approach to animation “chaining”).

When working with animation constants, be sure to use the modern UIViewAnimationOptions varieties, which have the word option in their names. Older constants like UIViewAnimationCurveEaseInOut will not work with post-iOS 4.x calls.

Recipe: Fading a View In and Out

At times, you want to add information to your screen that overlays your view but does not of itself do anything. For example, you might show a top-scores list or some instructions or provide a context-sensitive tooltip. Recipe 4-6 demonstrates how to use a UIView animation block to fade a view into and out of sight. This recipe follows the most basic animation approach. It creates a view animation block that sets the alpha property.

Note how this code controls the behavior of the right bar button item. When tapped, it is immediately disabled until the animation concludes. The animation’s completion block reenables the button and flips the button text and callback selector to the opposite state. This allows the button to toggle the animation from on to off, and from off to back on.

Recipe 4-6. Animating Transparency Changes to a View’s Alpha Property

Click here to view code image

- (void) fadeOut: (id) sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
// Here's where the actual fade out takes place
imageView.alpha = 0.0f;
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Fade In", @selector(fadeIn:));
}];
}

- (void) fadeIn: (id) sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
// Here's where the fade in occurs
imageView.alpha = 1.0f;
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Fade Out", @selector(fadeOut:));
}];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Swapping Views

The UIView animation block doesn’t limit you to a single change. Place as many animation differences as needed in the animations block. Recipe 4-7 combines size transformations with transparency changes to create a more compelling animation. It does this by adding several directives simultaneously to the animation block. This recipe performs five actions at a time. It zooms and fades one view into place while zooming out and fading away another and then exchanges the two in the subview array list.

You’ll want to prepare the back object for its initial animation by shrinking it and making it transparent. When the swap: method first executes, that view will be ready to appear and zoom to size. As with Recipe 4-6, the completion block reenables the bar button on the right, allowing successive presses.

Recipe 4-7. Combining Multiple View Changes in Animation Blocks

Click here to view code image

@implementation TestBedViewController
- (void) swap: (id) sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView animateWithDuration:1.0f
animations:^{
frontObject.alpha = 0.0f;
backObject.alpha = 1.0f;
frontObject.transform = CGAffineTransformMakeScale(0.25f, 0.25f);
backObject.transform = CGAffineTransformIdentity;
[self.view exchangeSubviewAtIndex:0
withSubviewAtIndex:1];
}
completion:^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;

// Swap the view references
UIImageView *tmp = frontObject;
frontObject = backObject;
backObject = tmp;
}];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Flipping Views

Transitions extend UIView animation blocks to add even more visual flair. Several transition styles do just what their names suggest. You can flip views to their backs, and curl views up and down in the manner of the Maps application. Recipe 4-10 demonstrates how to include these transitions in your interfaces.

Here’s a list of the current set of transitions, as of iOS 6.0. You can see these include four flips, two curls, a cross dissolve, and a “do nothing” no-op choice:

• UIViewAnimationOptionTransitionNone

• UIViewAnimationOptionTransitionFlipFromLeft

• UIViewAnimationOptionTransitionFlipFromRight

• UIViewAnimationOptionTransitionCurlUp

• UIViewAnimationOptionTransitionCurlDown

• UIViewAnimationOptionTransitionCrossDissolve

• UIViewAnimationOptionTransitionFlipFromTop

• UIViewAnimationOptionTransitionFlipFromBottom

Recipe 4-8 uses the block-based API for transitionFromView:toView:duration:options:completion:. This method replaces a view by removing it from its superview and adding the new view to the initial view’s parent. It animates this over the supplied duration using the transition specified in the options flags. Recipe 4-8 uses a flip-from-left transition, although you can use any of the other four transitions as desired.

The related transitionWithView:duration:options:animations:completion: method provides even more flexibility than this call, because it takes an animations block as a parameter. You can use it to create shrink/grow, flip, and other very complex view transition animations.

Superviews normally retain the views added to them and release them when they are removed. You must be careful to retain the view that has been removed if you plan to be able to add it back at a later time.

If you use constraints (see Chapter 5), you must redefine them as well. Removing a subview invalidates and removes all constraints that refer to that view from the superview.

Recipe 4-8. Using Transitions with UIView Animations

Click here to view code image

- (void) flip: (id) sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;
[UIView transitionFromView: fromPurple ? purple : maroon
toView: fromPurple ? maroon : purple
duration: 1.0f
options: UIViewAnimationOptionTransitionFlipFromLeft
completion: ^(BOOL done){
self.navigationItem.rightBarButtonItem.enabled = YES;
fromPurple = !fromPurple;
}];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Using Core Animation Transitions

In addition to UIView animations, iOS supports Core Animation as part of its Quartz Core framework. The Core Animation API offers highly flexible animation solutions for your iOS applications. Specifically, it offers built-in transitions that provide the same kind of view-to-view changes you’ve been reading about in the previous recipe, as well as a vast wealth of other fundamental animation possibilities that are beyond the scope of this chapter.

Core Animation transitions expand your UIView animation vocabulary with just a few small differences in implementation. CATransitions work on layers rather than on views. Layers are the Core Animation rendering surfaces associated with each UIView. When working with Core Animation, you apply CATransitions to a view’s default layer (myView.layer) rather than the view itself.

With these transitions, you don’t set your parameters through UIView the way you do with UIView animation. Create a Core Animation object, set its parameters, and then add the parameterized transition to the layer:

Click here to view code image

CATransition *animation = [CATransition animation];
animation.delegate = self;
animation.duration = 1.0f;
animation.type = kCATransitionMoveIn;
animation.subtype = kCATransitionFromTop;

// Perform some kind of view exchange or removal here

[myView.layer addAnimation:animation forKey:@"move in"];

Animations use both a type and a subtype. The type specifies the kind of transition used. The subtype sets its direction. Together the type and subtype tell how the views should act when you apply the animation to them.

Core Animation transitions are distinct from the UIViewAnimationTransitions discussed in previous recipes. Cocoa Touch offers four types of Core Animation transitions, which are highlighted in Recipe 4-9. These available types include cross-fades, pushes (one view pushes another offscreen), reveals (one view slides off another), and covers (one view slides onto another). The last three types enable you to specify the direction of motion for the transition using their subtypes. For obvious reasons, cross-fades do not have a direction and they do not use subtypes.

Because Core Animation is part of the Quartz Core framework, you must add the Quartz Core framework to your project and import <QuartzCore/QuartzCore.h> into your code when using these features.

Note

Apple’s Core Animation features 2D and 3D routines built around Objective-C classes. These classes provide graphics rendering and animation for your iOS and Macintosh applications. Core Animation avoids many low-level development details associated with, for example, direct Open GL while retaining the simplicity of working with hierarchical view layers.

Recipe 4-9. Animating Transitions with Core Animation

Click here to view code image

- (void) animate: (id) sender
{
// Set up the animation
CATransition *animation = [CATransition animation];
animation.delegate = self;
animation.duration = 1.0f;

switch ([(UISegmentedControl *)self.navigationItem.titleView
selectedSegmentIndex])
{
case 0:
animation.type = kCATransitionFade;
break;
case 1:
animation.type = kCATransitionMoveIn;
break;
case 2:
animation.type = kCATransitionPush;
break;
case 3:
animation.type = kCATransitionReveal;
default:
break;
}
animation.subtype = kCATransitionFromLeft;

// Perform the animation
[self.view exchangeSubviewAtIndex:0 withSubviewAtIndex:1];
[self.view.layer addAnimation:animation forKey:@"animation"];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Bouncing Views as They Appear

Apple often uses two animation blocks, one called after another finishes, to add bounce to their animations. For example, they might zoom into a view a bit more than needed and then use a second animation to bring that enlarged view down to its final size. Bounces add a little more life to your animation sequences, providing an extra physical touch.

When calling one animation after another, be sure that the animations do not overlap. The previous edition of this book covered blocking modal animations; however, since iOS introduced completion block support, it is now much easier to use a nested set of animation blocks with chained animations in the completion blocks. Recipe 4-10 uses this approach to bounce views slightly larger than their end size and then shrink them back down to the desired frame.

This recipe uses two simple typedefs to simplify the declaration of each animation and completion block. Notice that the animation block stages that do the work of scaling the view in question are defined in order. The first block shrinks the view, the second one zooms it extra large, and the third restores it to its original size.

The completion blocks go the opposite way. Because each block depends on the one before it, you must create them in reverse order. Start with the final side effects and work your way back to the original. In Recipe 4-10, bounceLarge depends on shrinkBack, which in turn depends on reenable. This reverse definition can be a bit tricky to work with, but it certainly beats laying out all your code in nested blocks.

The sample project for this recipe contains an additional helper class (AnimationHelper), which wraps the behavior you see in Recipe 4-10 in a slightly less-awkward package. As Recipe 4-10 demonstrates, trying to lay out an animation sequence backward, so each bit can be referenced by the item that called it, gets very clumsy, very fast.

The helper class builds the whole block sequence and returns animation blocks similar to Recipe 4-10’s, which are ready to be executed and contain embedded completion blocks.

Note

If you’d rather dive into Core Animation instead of depending on UIView animations, a simple CAKeyframeAnimation can do all the work of Recipe 4-10 with a lot less effort.

Recipe 4-10. Bouncing Views

Click here to view code image

typedef void (^AnimationBlock)(void);
typedef void (^CompletionBlock)(BOOL finished);

- (void) bounce: (id) sender
{
self.navigationItem.rightBarButtonItem.enabled = NO;

// Define the three stages of the animation in forward order
AnimationBlock makeSmall = ^(void){
bounceView.transform = CGAffineTransformMakeScale(0.01f, 0.01f);};
AnimationBlock makeLarge = ^(void){
bounceView.transform = CGAffineTransformMakeScale(1.15f, 1.15f);};
AnimationBlock restoreToOriginal = ^(void) {
bounceView.transform = CGAffineTransformIdentity;};

// Create the three completion links in reverse order
CompletionBlock reenable = ^(BOOL finished) {
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Start", @selector(bounce:));};
CompletionBlock shrinkBack = ^(BOOL finished) {
[UIView animateWithDuration:0.2f
animations:restoreToOriginal completion: reenable];};
CompletionBlock bounceLarge = ^(BOOL finished){
[NSThread sleepForTimeInterval:0.5f]; // wee pause
[UIView animateWithDuration:0.2
animations:makeLarge completion:shrinkBack];};

// Start the animation
[UIView animateWithDuration: 0.1f
animations:makeSmall completion:bounceLarge];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Recipe: Image View Animations

In addition to displaying static pictures, the UIImageView class supports built-in animation sequences. After loading an array of image cels, you can tell instances to animate them. Recipe 4-11 shows you how.

Start by creating an array populated by individual images loaded from files and assign this array to the UIImageView instance’s animationImages property. Set the animationDuration to the total loop time for displaying all the images in the array. Finally, begin animating by sending the startAnimating message. (There’s a matching stopAnimating method available for use as well.)

After you add the animating image view into your interface, you can place it into a single location, or you can animate it just as you would animate any other UIView instance.

Recipe 4-11. Using UIImageView Animation

Click here to view code image

NSMutableArray *butterflies = [NSMutableArray array];

// Load the butterfly images
for (int i = 1; i <= 17; i++)
[butterflies addObject:[UIImage imageWithContentsOfFile:
[[NSBundle mainBundle]
pathForResource: [NSString stringWithFormat:@"bf_%d", i]
ofType:@"png"]]];

// Create the view
UIImageView *butterflyView = [[UIImageView alloc]
initWithFrame:CGRectMake(40.0f, 300.0f, 60.0f, 60.0f)];

// Set the animation cells, and duration
butterflyView.animationImages = butterflies;
butterflyView.animationDuration = 0.75f;
[butterflyView startAnimating];

// Add the view to the parent
[self.view addSubview:butterflyView];

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 4.

Summary

UIViews provide the components your users see and interact with. As this chapter showed, even in their most basic form, they offer incredible flexibility and power. You discovered how to use views to build up elements on a screen, retrieve views by tag or name, and introduce eye-catching animation. Here’s a collection of thoughts about the recipes you saw in this chapter that you might want to think about before moving on:

• When you’re dealing with multiple views, hierarchy should always remain in your mind. Use your view hierarchy vocabulary to take charge of your views and always present the proper visual context to your users.

• Don’t let the Core Graphics framework/UIKit center dichotomy stand in your way. Use functions that help you move between these structures to produce the results you need, especially when you’re working with simple views that don’t use transforms.

• Make friends with tags, whether numeric or custom name tags. They provide immediate access to views in the same way that your program’s symbol table provides access to variables. They are not evil or wrong and can play a useful role in your development vocabulary.

• Take control of transforms. They’re just math. Transforms shouldn’t keep you from retrieving information about your views, whether determining the current rotation or scaling value, or the position of your view’s corners. Transforms provide incredible power in so many iOS development arenas, and the recipes in this chapter add tweaks that ensure the information and control you need are ready to use when you need them.

• Blocks are wonderful. Use them to simplify your life, your code, and your animations.

• Animate everything. Animations don’t have to be loud, splashy, or bad design. The iOS SDK’s strong animation support enables you to add smooth transitions between user tasks. The essence of the iOS experience is subtle, smooth transitions. Short, smooth, focused changes are iOS’s bread and butter.

• This chapter focused on direct view hierarchies and placement, for when you take charge of view layout yourself. Read about system-driven constraints and autolayout in Chapter 5.

5. View Constraints

The iOS 6 software development kit (SDK) has revolutionized view layout. The days of sizing masks, struts, and springs are over. If you aren’t familiar with those, don’t worry. If you are familiar with them, you’re about to learn something wonderful, fresh, and new. Apple’s layout features will make your life easier and your interfaces more consistent, regardless of device geometry and orientation. This chapter introduces code-level constraint development. You’ll discover how to create relations between on-screen objects and specify the way iOS automatically arranges your views. The outcome is a set of robust rules that adapt to screen geometry.

What Are Constraints?

Constraints are rules that allow you to describe your layout to iOS. Supported only by iOS 6 and later, they limit how things relate to each other, restricting how they may be laid out. With constraints, you can say “these items are always lined up in a horizontal row” or “this item resizes itself to match the height of that item.” Constraints provide a layout language that you add to views to describe visual relationships.

iOS takes charge of meeting those demands via a constraint satisfaction system. The rules must make sense. A view cannot be both to the left and the right of another view. So, one of the key challenges when working with constraints is ensuring that the rules are rigorously consistent. When your rules fail, they fail loudly. Xcode provides you with verbose updates explaining what might have gone wrong.

Another key challenge is making sure your rules are specific enough. An underconstrained interface can create random results when faced with many possible layout solutions. You might request that one view lies to the right of the other, but unless you tell the system otherwise, you might end up with the right view at the top of the screen and the left view at the bottom. That one rule doesn’t say anything about vertical orientation.

Constraints allow you to design resolution independent apps. I worked on a constraint-based iOS 6 application prior to the iPhone 5 introduction. This app required no code updates to work on the new device. All I had to add was a Default-568h@2x.png file and my app was ready to ship on all device aspects. Constraints ensured my screens were laid out as well on a 4″ screen as they were on a 3.5″ one. If you’re new to iPhone 5 development, make sure your window stretches to all sides, especially if you use Interface Builder (IB).

The iOS 6.x SDK enables you to lay out constraints both visually in Interface Builder and programmatically in your application source code. The IB approach is simple to use and easy to lay out. This chapter focuses on code. It offers code-centered examples that help you craft common view constraints in Objective-C.

Note

Constraints are an iOS 6 feature. Should you attempt to run them on an earlier version of iOS, you will encounter exceptions. To write backward-compatible applications that leverage constraints, make sure to implement two versions of your layout routines: one for iOS 6 and later, and one with autoresizing for earlier deployments. iOS 6 still supports autoresizing layouts. If you use these exclusively, your code will work on earlier systems, but you will miss out on the power of constraints. As Apple continues to introduce new hardware configurations and aspect ratios, you will quickly realize why constraints are the future of layout.

Alignment Rectangles

Say goodbye to frames, at least for the moment. The world of constraints doesn’t focus on frames, the layout rectangles introduced in Chapter 4, “Assembling Views and Animations.” Frames say where to place views on the screen and how big those views will be. Instead, constraints use a geometric element called an alignment rectangle.

As developers create complex views, they may introduce visual ornamentation such as shadows, exterior highlights, reflections, and engraving lines. As they do, these features usually become attached as subviews or sublayers. As a consequence, a view’s frame, its full extent, grows as items are added.

The alignment rectangle, in contrast, is limited to a core visual. Its size remains unaffected as new items join the primary view. Consider Figure 5-1 (left). It depicts a switch with an attached shadow, which is placed behind and offset from the main switch elements. When laying out this shadowed switch, you want to focus on aligning just the switch.

Figure 5-1. A view’s alignment rectangle (center) refers strictly to the core visual element to be aligned, without embellishments.

The central image indicates the switch’s alignment rectangle. This rectangle excludes all ornamentation, including the drop shadow that appears offset to its bottom and right. Instead, it includes the core views that make up the functional part of the switch. Contrast this with the view’s frame, shown in the right image of Figure 5-1.

This frame encompasses all the view’s visual elements, including the shadow. The frame, therefore, is much larger than the switch itself. This shadow throws off the view’s alignment features (for example, its center, bottom, and right). The frame’s x- and y-midpoints are slightly too far to the right and bottom to properly use them for a visually pleasing alignment. The same goes for the right and bottom edges, which don’t touch the core switch.

By working with alignment rectangles instead of frames, iOS’s constraints system ensures that key features like a view’s edges and center are properly considered during layout.

Declaring Alignment Rectangles

When building ornamented views, you have the responsibility to report geometry details to auto layout. Implement alignmentRectForFrame:. This method allows your views to declare accurate alignment rectangles when they use ornamentation such as shadows or reflections.

This method takes one argument, a frame. This argument refers to the destination frame that the view will inhabit; think the rectangle on the right in Figure 5-1. That frame will encompass the entire view, including any ornamentation attached to the view. It’s up to you to provide an accurate representation of the alignment rectangle with respect to that destination frame and your view’s embedded elements.

Your method returns a CGRect value that specifies the rectangle for your view’s core visual geometry, as the center rectangle in Figure 5-1 does. This is typically the main visual object’s frame and excludes any ornamentation views you have added as subviews or into your view’s layer as sublayers.

When planning for arbitrary transformations, make sure to implement frameForAlignmentRect: as well. This method describes the inverse relationship, producing the resulting fully ornamented frame (for example, Figure 5-1, right image) when passed constrained alignment rectangle (for example, Figure 5-1, center image). You extend the bounds to include any ornamentation items in your view, scaling them to the alignment rectangle passed to this method.

Constraint Attributes

Constraints work with a limited geometric vocabulary. Attributes are the “nouns” of the constraint system, describing positions within a view’s alignment rectangle. Relations are “verbs,” specifying how the attributes compare to each other.

The attribute nouns speak to physical geometry. iOS constraints offer the following view attribute vocabulary:

• left, right, top, and bottom—The edges of a view’s alignment rectangle on the left, right, top, and bottom of the view.

• leading and trailing—The leading and trailing edges of the view’s alignment rectangle. In left-to-right (English-like) systems, these correspond to “left” (leading) and “right” (trailing). In right-to-left linguistic environments like Arabic or Hebrew, these roles flip; right is trailing, and left is leading.

• width and height—The width and height of the view’s alignment rectangle.

• centerX and centerY—The x-axis and y-axis centers of the views’ alignment rectangle

• baseline—The alignment rectangle’s baseline, typically a set offset above its bottom attribute.

The relation verbs compare values. Constraint math is limited to three relations: setting equality or setting lower and upper bounds for comparison. The layout relations you can use are:

• Less-than inequality—NSLayoutRelationLessThanOrEqual

• Equality—NSLayoutRelationEqual

• Greater-than inequality—NSLayoutRelationGreaterThanOrEqual

This might not sound like a lot expressively. When you consider the math you’re building, however, these three cover all the ground needed for equality and inequality relationships.

Constraint Math

All constraints, regardless of how they are created, are essentially equations of the form:

y (relation) m * x + b

If you have a math background, you may have seen a form more like this, with R referring to the relation between y and the computed value on the right side:

y R m * x + b

Y and x are view attributes of the kind you just read about, such as width or centerY or top. M is a constant scaling factor, and b is a constant offset. For example, you might say, “View 2’s left side should be placed 15 points to the right of View 1’s right side.” The relation equation that results is something like this:

View 2’s left = View 1’s right + 15

Here, the relation is equality, the constant offset (b) is 15 and the scaling factor or multiplier (m) is 1. I’ve taken care here, to keep the equation above from looking like code because, as you’ll see, this is not how you declare your constraints in Objective-C.

Constraints do not have to use strict equalities. You can use inequality relations as well. For example, you might say, “View 2’s left side should be place at least 15 points to the right of View 1’s right side,” or

View 2’s left >= View 1’s right + 15

Offsets let you place fixed gaps between items, and the multipliers let you scale. Scaling proves especially useful when laying out grid patterns, letting you multiply off the height of a view, not just adding a fixed distance to the next view.

The Laws of Constraints

Although you can think of constraints as hard “math,” they’re actually just preferences. iOS finds layouts solution that best match your constraints; this solution may not always be unique. Here are a few basic facts about constraints:

• Constraints are relationships, not directional. You don’t have to solve the right side to calculate the left side.

• Constraints have priorities. Priorities range numerically from 0 to 1000. Higher priorities are always satisfied before those with lower priorities. The highest priority you can assign is “required,” (that is, UILayoutPriorityRequired) (value: 1000), which is also the default.

A required priority should be satisfied exactly; for example, this button is exactly this size. So, when you assign a different priority, you’re actually attenuating that constraint’s sway within the overall layout system.

Even required priorities may be overridden when constraints come into conflict. Don’t be shocked if your 100x100 view ends up being presented at 102x107 if your constraints aren’t perfectly balanced. Table 5-1 details several priority presets and their values.

Table 5-1. Priority Constraints

The autolayout system uses priorities to sort constraints. A priority of 99 is always considered after a priority of 100. During layout, the system iterates through any constraints you have added, attempting to satisfy them all. Priorities come into play when deciding which constraint has less sway. The 99 priority constraint will be broken in favor of the 100 priority constraint should the two come into conflict.

• Constraints don’t have any natural “order” outside of priorities. All constraints of the same priority are considered at the same time. If you need some constraint to take precedence, assign it a higher priority.

• Constraints can be approximated. Optional constraints try to optimize their results. Consider the constraint “View 2’s top edge should be at the same position as View 1’s bottom edge.” The constraint system attempts to squeeze these two together by minimizing their distance. Should other constraints prevent them from touching, the system places them as close it can, minimizing the absolute distance between the two attributes.

• Constraints can have cycles. So long as all items are satisfied, it doesn’t matter which elements refer to which. Don’t sweat the cross-references. In this declarative system, circular references are okay, and you will not encounter infinite looping issues.

• Constraints are not completely animatable. In the iOS 6 6gm release, UIView animation or Core Animation can adjust elements as constraints update. Call layoutIfNeeded on the superview or window. This support is not yet universal. Some constrained elements may jump instead of animate to their new positions.

• Constraints can cross hierarchies. You can normally align the center point of one view’s subview with the center point of an entirely different view as long as both views belong to the same parent. For example, you might create a complex text entry view and align its rightmost button’s right attribute with the right attribute of an embedded image view below it. There’s just one limitation here, which follows next.

• Constraints cannot cross bounds systems. You cannot cross into and out of scroll views and table views for alignment. If there’s some sort of content view with its own bounds system, you can’t hop out of that to an entirely different bounds system in another view.

• Constraints can fail at runtime. If your constraints cannot be resolved (there are examples for you at the end of this chapter), and they come into conflict, the runtime system chooses which constraints to disregard to present whatever view layout it can. This is usually ugly and nearly always not the visual presentation you intended. iOS sends exhaustive descriptions of what went wrong to your Xcode console. Use these reports to fix your constraints and bring them into harmony with each other.

• Badly formed constraints may interrupt application execution. Unlike constraint conflicts, which produce error messages but allow your application to continue running, some constraint calls may actually crash your application. For example, if you pass a constraint format string such as @"V[view1]-|" (it is missing a colon after the letter V) to a constraint creation method, you’ll encounter a runtime exception. This error cannot be detected during compilation; you must carefully check your format strings by hand. Designing constraints in IB helps avoid bad-typo scenarios:

Click here to view code image

Terminating app due to uncaught exception 'NSInvalidArgumentException',
reason: 'Unable to parse constraint format'

Note

Working with constraints adds all sorts of wonderful and new ways to crash your apps at runtime. Take extreme care when developing and testing your constraint-based application and build tests to ensure that you check their operations in as many scenarios as possible. Let’s hope that someone builds a constraint validator to catch, at least, simple typos like @"H:[myView", a visual format constraint that is missing a closing square bracket.

Creating Constraints

The NSLayoutConstraint class lets you create constraints in two ways. You can use a rather long method call to constrain one item’s attribute to another, explaining how these attributes relate, or you can apply a rather nifty visual formatting language to specify how items are laid out along vertical and horizontal lines.

This section demonstrates both approaches, allowing you to see what they look like and how they are used. Remember this: Regardless of how you build your constraints, they all produce “y relation mx + b” results. All constraints are members of the NSLayoutConstraint class, no matter how you create them.

Basic Constraint Declarations

The NSLayoutConstraint’s class method constraintWithItem:attribute:relatedBy: toItem:attribute:multiplier:constant: (gesundheit) creates a single constraint at a time. These constraints relate one item to another.

The creation method produces a strict view.attribute R view.attribute * multiplier + constant relation, where R is one of equal-to (==), greater-than-or-equal-to (>=), or less-than-or-equal-to <= relations.

Consider the following example:

[self.view addConstraint:
[NSLayoutConstraint
constraintWithItem: self.view
attribute: NSLayoutAttributeCenterX
relatedBy: NSLayoutRelationEqual
toItem: textfield
attribute: NSLayoutAttributeCenterX
multiplier: 1.0f
constant: 0.0f]];

This call adds a new constraint to a view controller’s view (self.view). It horizontally center-aligns a text field within this view. It does this by setting an equality relation (NSLayoutRelationEqual) between the two view’s horizontal centers (NSLayoutAttributeCenterX attributes). The multiplier here is 1, and the offset constant is 0. This relates to the following equation:

[self.view]’s centerX = ([textfield]’s centerX * 1) + 0.

It basically says, please ensure that my view’s center and the text field’s center are co-aligned at their X positions.

The UIView’s addConstraint: method adds that constraint to the view, where it is stored with any other constraints in the view’s constraints property.

Visual Format Constraints

The preceding section showed you how to create single constraint relations. A second NSLayoutConstraint class method builds constraints using a text-based visual format language. Think of it as ASCII art for Objective-C nerds. Here’s a simple example:

Click here to view code image

[self.view addConstraints: [NSLayoutConstraint
constraintsWithVisualFormat:@"V:[leftLabel]-15-[rightLabel]"
options:0
metrics:nil
views:NSDictionaryOfVariableBindings(leftLabel, rightLabel)]];

This request creates all the constraints that satisfy the relation or relations specified in the visual format string. These strings, which you will see in many examples in following sections, describe how views relate to each other along the horizontal (H) or vertical (V) axis. This example basically says, “Ensure that the right label appears 15 points below the left label.”

You want to note several things about how this constraints formatting example is created:

• The axis is specified first as a prefix, either H: or V:.

• The variable names for views appear in square brackets in the strings.

• The fixed spacing appears between the two as a number constant, -15-.

• This example does not use any format options (the options parameter), but here is where you would specify whether the alignment is done left to right, right to left, or according to the leading-to-trailing direction for a given locale discussed earlier in this chapter.

• The metrics dictionary, also not used in this example, lets you supply constant numbers into your constraints without having to create custom formats. For example, if you want to vary the spacing between these two text labels, you could replace 15 with a metric name (for example, labelOffset, or something like that) and pass that metric’s value in a dictionary. Set the name as the key, the value as a NSNumber. Passing dictionaries (for example, @{@"labelOffset", @15}) proves to be a lot easier than creating new format NSString instances for each width you might use.

• The views: parameter does not, despite its name, pass an array of views. It passes a dictionary of variable bindings. This dictionary associates variable name strings with the views they represent. This indirection allows you to use developer-meaningful symbols like "leftLabel" and "rightLabel" in your format strings.

Building constraints with formats strings always creates an array of results. Some format strings are quite complex, others simple. It’s not always easy guessing how many constraints will be generated from each string. Be aware that you will need to add the entire collection of constraints to satisfy the format string that you processed.

Variable Bindings

When working with visual constraints, the layout system needs to be able to associate view names like "leftLabel" and "rightLabel" with the actual views they represent. Enter variable bindings, via a handy macro defined in NSLayoutConstraint.h, which is part of the UIKit headers.

The NSDictionaryOfVariableBindings() macro accepts an arbitrary number of local variable arguments. As you can see in the earlier example, these need not be terminated with a nil. The macro builds a dictionary from the passed variables, using the variable names as keys and the actual variables as values. For example, the function call

NSDictionaryOfVariableBindings(leftLabel, rightLabel)

builds this dictionary:

@{@"leftLabel":leftLabel, @"rightLabel":rightLabel}.

If you’d rather not use the variable bindings macro, you can easily create a dictionary by hand and pass it to the visual format constraints builder.

Format Strings

The format strings you pass to create constraints follow a basic grammar, which is specified as follows. The question marks refer to optional items, the asterisk to an item that may appear zero or more times:

(<orientation>:)? (<superview><connection>)? <view>(<connection><view>)*
(<connection><superview>)?

Although daunting to look at, these strings are actually quite easy to construct. The next sections offer an introduction to format string elements and provide copious examples of their use.

Orientation

You start with an optional orientation, either H: for horizontal or V: for vertical alignment. This specifies whether the constraint applies left and right or top and down. Consider this constraint: "H:[view1][view2]". It says to place View 2 directly to the right of View 1. The H specifies the alignment the constraint follows. Figure 5-2 (left) shows an interface that uses this rule.

Figure 5-2. Possible layout results for "H:[view1][view2]" (left) and "V:[view1]-20-[view2]-20-[view3]" (right). The left sides of views 1 through 3 were aligned in the image on the right to better show the gaps between each view.

The following is an example of a vertical layout: "V:[view1]-20-[view2]-20-[view3]". It leaves a gap of 20 points below View 1 before placing View 2, followed again by a 20 point gap before View 3. Figure 5-2 (right) shows what this might look like.

The two screenshots in Figure 5-2 depict just two possible layouts for these rules. These views are severely under constrained. Although View 2 appears to the direct right of View 1 in the left image, its vertical placement is basically random. The same kind of underspecification is shown in the image to the right, where the vertical distances are constrained, but not their starting point.

Here are the two constraints that were used in separate executions to create the images shown in Figure 5-2:

Click here to view code image

[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:[view1][view2]"
options:0 metrics:nil
views:NSDictionaryOfVariableBindings(view1, view2)]];
and

Click here to view code image

[self.view addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[view1]-20-[view2]-20-[view3]"
options:0 metrics:nil
views:NSDictionaryOfVariableBindings(view1, view2, view3)]];

Note that I manually aligned the left sides of the second example so that you could better see the gaps between each of the three views. Without that intervention, the views could have appeared anywhere along the horizontal span of their parent view.

Note

During debugging, you can use the constraintsAffectingLayoutForAxis: view method to retrieve all the constraints that affect either the horizontal or vertical layout access. Do not ship code with this method. It is not intended for deployment use, and Apple makes it clear that it is not App Store safe.

View Names

As the two examples you just saw demonstrate, views names are encased in square brackets. For example, you might work with "[view1]" and "[view2]". The view name refers to the local variable name of your view. So if you’ve declared

UIButton *button1;
your format string can refer to "[button1]". To make this work, you pass a dictionary of variable bindings to the constraint creation method, which allows you to use the names in this fashion.

You can create this dictionary in two ways. The easiest approach involves using the NSDictionaryOfVariableBindings() macro to create the mapping from variable instances. You can also build the dictionary yourself. This is especially helpful when building formats from arrays of views, as is done in the following code snippet. Once populated, this viewDictionary can be passed as the variable bindings parameter for the formatString:

Click here to view code image

NSMutableDictionary *viewDictionary = [NSMutableDictionary dictionary];
int i = 1;

for (UIView *view in viewArray)
{
NSString *viewName = [NSString stringWithFormat:@"view%0d", i++];
[formatString appendFormat:@"[%@]", viewName];
[viewDictionary setObject:view forKey:viewName];
}

A special character, the vertical pipe (|) always refers to the superview. You see it only at the beginning or ending of format strings. At the beginning, it appears just after the horizontal or vertical specifier ("V:|..." or "H:|..."). At the end, it appears just before the terminal quote ("...|").

Connections

Place connections between view names to specify the way a layout flows. An empty connection (in other words, one that has been omitted) means “follow on directly.”

The first constraint you saw for Figure 5-2, "H:[view1][view2]" used an empty connection. There’s nothing specified between the square brackets of View 1 and the brackets of View 2. This tells the constraint to place View 2 directly to the right of View 1. Figure 5-3 shows what that relationship might look like in use.

Figure 5-3. "H:[view1][view2]" uses an empty connection, placing both views directly next to each other.

A hyphen (-) represents a small fixed space. The constraint "H:[view1]-[view2]" uses a hyphen connection. This constraint leaves a small gap between View 1 and View 2, as shown in Figure 5-4.

Figure 5-4. "H:[view1]-[view2]" adds a spacer connection.

Place a numeric constant between hyphens to set an exact gap size. The constraint "H:[view1]-30-[view2]" adds a 30-point gap between the two views, as shown in Figure 5-5. This is visibly wider than the small default gap produced by the single hyphen.

Figure 5-5. "H:[view1]-30-[view2]" uses a fixed-size gap of 30 points, producing a noticeably wider space.

In constraints specifications, the pipe character always refers to the superview. The pipe can appear at the start/end of the format string. The following example uses both.

The format "H:|[view1]-[view2]|" specifies a horizontal layout that starts with the superview. The superview is immediately followed by the first view, then a spacer, the second view, and then the superview, which you can see in Figure 5-6.

Figure 5-6. "H:|[view1]-[view2]|" tells both views to hug the edges of their superview. With a fixed-size gap between them, at least one of the views must resize to satisfy the constraint.

This constraint left-aligns View 1 and right-aligns View 2 with the superview. To accomplish this, something has to give. Either the left view or the right view must resize to meet these constraints. When I ran the test app, it happened to be View 2 that adjusted, which is what you see in Figure 5-6. It could just as easily have been View 1.

Often, you don’t want to bang up right against the superview edges. A similar constraint, "H:|-[view1]-[view2]-|", leaves an edge inset between the edges of the superview and the start of View 1 and end of View 2 (see Figure 5-7).

Figure 5-7. "H:|-[view1]-[view2]-|" introduces edge insets between the views and their superviews.

These follow standard Interface Builder/Cocoa Touch layout rules and have not yet been exposed in specifics via the iOS API. The inset gaps at the edges are normally slightly larger than the default view-to-view gaps. You can see that in the following layout created from this constraint.

If your goal is to add a flexible space between views, there’s a way to do that too. Add a relation rule between the two views (for example, "H:|-[view1]-(>=0)-[view2]-|") to allow the two views to retain their sizes and separate while maintaining gaps at their edges with the superview, as shown in Figure 5-8. This rule, which equates to “at least 0 points distance,” provides a more flexible way to let the views spread out. I recommend using a small number here so that you don’t inadvertently interfere with a view’s other geometry rules.

Figure 5-8. "H:|-[view1]-(>=0)-[view2]-|" uses a flexible space between the two views, allowing them to separate, while maintaining their sizes.

These constraints are not, of course, limited to just one or two views. You can easily stick in a third, fourth, or more. Consider this constraint: "H:|-[view1]-[view2]-(>=5)-[view3]-|". It adds a third view, separated from the other two views by a flexible space. Figure 5-9 shows what that might look like.

Figure 5-9. "H:|-[view1]-[view2]-(>=5)-[view3]-|" demonstrates a rule that includes three views.

Predicates

The last two examples in the previous section used relationship rules with comparisons. These are also called predicates, an affirmation of the way a relation works between view elements. Predicates appear in parentheses. For example, you might specify the size of a view is at least 50 points using the following format:

[view1(>=50)]

This predicate relates to a single view. Notice that it is included within the view’s square brackets, rather than as part of the connections between views. You’re not limited to a single request. For example, you might use a similar approach to let a view’s size range between 50 and 70 points. When adding compound predicates, separate your rule with commas:

[view1(>=50, <=70)]

Relative relation predicates allow your views to grow. If you want your view to expand across a superview, tell it to size itself to some value greater than zero. The following rule stretches a view horizontally across its superview, allowing only for edge insets at each side:

H:|-[view1(>=0)]-|

Figure 5-10 shows what that constraint looks like when rendered.

Figure 5-10. "H:|-[view1(>=0)]-|" adds a flexibility predicate to the view, letting it stretch across its parent. Edge insets offset it slightly from the superview’s sides.

When you are not actually comparing two things, you can skip the double-equals in your format predicates. For example [view1(==120)] is equivalent to [view1(120)], and [view1]-(==50)-[view2] is the same as [view1]-50-[view2].

Metrics

When you don’t know a constant’s value (like 120 or 50) a priori, use a metric dictionary to provide that value. This dictionary is passed as one of the parameters to the constraint creation method. Here is an example of a format string using a metric stand-in: [view1(>=minwidth)]

The minwidth stand-in must map to an NSNumber value in the passed metric dictionary. For more examples of metric use, refer to Recipe 5-3’s constrainSize: method. It demonstrates how to use metrics, using values from an associated dictionary in its constraints.

View-to-View Predicates

Predicates aren’t limited to numeric constants. You can relate a view’s size to another view, ensuring that it’s no bigger than that view in the layout. This example limits View 2’s extent to no bigger than that of View 1, along whichever axis the constraint is currently using: [view2(<=view1)]

You can’t do a lot more with format strings and view-to-view comparisons. If you want to establish more complex relationships, like those between centers, tops, and heights, skip the visual format strings and use the item constraint constructor instead.

Priorities

Each constraint may specify an optional priority by adding an at sign (@) and a number or metric. For example, you can say that you want a view to be 500 points wide, but that the request has a relatively low priority:

[view1(500@10)]

You place priorities after predicates. Here’s an example of a layout format string with an embedded priority:

[view1]-(>=50@30)-[view2]

Format String Summary

Table 5-2 summarizes format string components used to create constraints for automating view layout.

Table 5-2. Visual Format Strings

Storing and Updating Constraints

All constraints belong to the NSLayoutConstraint class, regardless of how they are created. When working with constraints, you can add them to your views either one by one, using addConstraint: as shown in the previous section, or in arrays by using the addConstraints: instance method (notice the s at the end of the name). In day-to-day work, you often deal with collections of constraints, stored in arrays.

Constraints can be added and removed at any time. The two methods, removeConstraint: and removeConstraints:, enable you to remove one or an array of constraints from a given view. Because these methods work on objects, they might not do what you expect when you attempt to remove constraints.

Suppose, for instance, that you build a center-matching constraint and add it to your view. You cannot then build a second version of the constraint with the same rules and expect to remove it using a standard removeConstraint: call. They are equivalent constraints, but they are not the same constraint. Here’s an example of this conundrum:

Click here to view code image

[self.view addConstraint:
[NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f]];
[self.view removeConstraint:
[NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f]];

Executing these two method calls ends up as follows. The self.view instance contains the original constraint, and the attempt to remove the second constraint is ignored. Removing a constraint not held by the view has no effect.

You have two choices for resolving this. First, you can hold onto the constraint when it’s first added by storing it in a local variable. Here’s what that would look like, more or less:

Click here to view code image

NSLayoutConstraint *myConstraint =
NSLayoutConstraint constraintWithItem:textField
attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.view
attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f]];
[self.view addConstraint:myConstraint];

// later
[self.view removeConstraint:myConstraint];

Or, you can use a method (see Recipe 5-1) that compares constraints and removes one that numerically matches the one you pass.

Knowing whether your constraints will be static, used for the lifetime of your view, or dynamic, updated as needed, helps you decide which approach you need. If you think you might need to remove a constraint in the future, either hold on to it via a local variable so that you can later remove it from your view or use workarounds like the one detailed in Recipe 5-1.

Here are some basic points you need to know about managing constraints:

• You can add constraints to and remove constraints from view instances. The core methods are addConstraint: (addConstraints:), removeConstraint: (removeConstraints:), and constraints. The last of these returns an array of constraints stored by the view.

• Constraints are not limited to container views. Nearly any view can store constraints. (A class method, requiresConstraintBasedLayout, specifies whether classes depend on constraints to operate properly.)

• If you want to code a subview with constraints, switch off the subview’s translatesAutoresizingMaskIntoConstraints property. You’ll see this in action in the sample code for this chapter and it’s discussed further in the “Debugging Your Constraints” section toward the end of this chapter.

Recipe: Comparing Constraints

All constraints use a fixed structure of the following form, along with an associated priority:

view1.attribute (relation) view2.attribute * multiplier + constant

Each element of this equation is exposed through a constraint’s object properties, namely priority, firstItem, firstAttribute, relation, secondItem, secondAttribute, multiplier, and constant. These properties make it easy to compare two constraints.

Views store and remove constraints as objects. If two constraints are stored in separate memory locations, they’re considered unequal, even if they describe the same conditions. To allow your code to add and remove constraints on-the-fly without storing those items locally, use comparisons.

Recipe 5-1 introduces three methods. The constraint:matches: method compares the properties in two constraints, determining whether they match. Note that only the equation is considered, the priority is not (although you can easily add this if you want), because I thought that two constraints describing the same conditions were essentially equivalent regardless of whatever priority a developer had assigned to them.

The two other methods (constraintMatchingConstraint: and removeMatchingConstraint:) respectively help locate the first matching constraint stored within a view and remove that matching constraint.

Note

Recipe 5-1 implements a UIView class category. This category is used throughout this chapter to provide a set of utility methods you can expand for use in your own applications.

Recipe 5-1. Comparing Constraints

Click here to view code image

@implementation UIView (BasicConstraints)
// This ignores any priority, looking only at y (R) mx + b
- (BOOL) constraint: (NSLayoutConstraint *) constraint1
matches: (NSLayoutConstraint *) constraint2
{
if (constraint1.firstItem != constraint2.firstItem) return NO;
if (constraint1.secondItem != constraint2.secondItem) return NO;
if (constraint1.firstAttribute != constraint2.firstAttribute) return NO;
if (constraint1.secondAttribute != constraint2.secondAttribute) return NO;
if (constraint1.relation != constraint2.relation) return NO;
if (constraint1.multiplier != constraint2.multiplier) return NO;
if (constraint1.constant != constraint2.constant) return NO;

return YES;
}

// Find first matching constraint. (Priority, Archiving ignored)
- (NSLayoutConstraint *) constraintMatchingConstraint:
(NSLayoutConstraint *) aConstraint
{
for (NSLayoutConstraint *constraint in self.constraints)
if ([self constraint:constraint matches:aConstraint])
return constraint;

for (NSLayoutConstraint *constraint in self.superview.constraints)
if ([self constraint:constraint matches:aConstraint])
return constraint;
return nil;
}

// Remove constraint
- (void) removeMatchingConstraint: (NSLayoutConstraint *) aConstraint
{
NSLayoutConstraint *match =
[self constraintMatchingConstraint:aConstraint];
if (match)
{
[self removeConstraint:match];
[self.superview removeConstraint:match];
}
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Recipe: Describing Constraints

When developing and debugging constraints, you may find it useful to produce human-readable descriptions of arbitrary NSLayoutConstraints. Recipe 5-2 builds parsimonious strings that describe constraints as equations, as follows:

• [UILabel:6bb32a0].right <= [self].right;

• [self].width == ([self].height * 1.778);

• [UILabel:6bb32a0].leading == ([UILabel:6ed2e70].trailing + 60.000)

This recipe transforms constraint instances into these textual presentations. It does so within the context of the view whose constraints are being considered (and hence the references to self and superview, in addition to specific subviews that are listed by class and memory address).

Notice that not all constraints include two items. Constraints that only refer to themselves (such as the second example, which sets its width as a multiplier of its height) may occur. In these cases, the item2 property is invariably nil.

Recipe 5-2. Describing Constraints

Click here to view code image

@implementation UIView (BasicConstraints)

// Return a string that describes an attribute
- (NSString *) nameForLayoutAttribute: (NSLayoutAttribute) anAttribute
{
switch (anAttribute)
{
case NSLayoutAttributeLeft: return @"left";
case NSLayoutAttributeRight: return @"right";
case NSLayoutAttributeTop: return @"top";
case NSLayoutAttributeBottom: return @"bottom";
case NSLayoutAttributeLeading: return @"leading";
case NSLayoutAttributeTrailing: return @"trailing";
case NSLayoutAttributeWidth: return @"width";
case NSLayoutAttributeHeight: return @"height";
case NSLayoutAttributeCenterX: return @"centerX";
case NSLayoutAttributeCenterY: return @"centerY";
case NSLayoutAttributeBaseline: return @"baseline";
default: return @"not-an-attribute";
}
}

// Return a name that describes a layout relation
- (NSString *) nameForLayoutRelation: (NSLayoutRelation) aRelation
{
switch (aRelation)
{
case NSLayoutRelationLessThanOrEqual: return @"<=";
case NSLayoutRelationEqual: return @"==";
case NSLayoutRelationGreaterThanOrEqual: return @">=";
default: return @"not-a-relation";
}
}

// Describe a view in its own context
- (NSString *) nameForItem: (id) anItem
{
if (!anItem) return @"nil";
if (anItem == self) return @"[self]";
if (anItem == self.superview) return @"[superview]";
return [NSString stringWithFormat:@"[%@:%d]", [anItem class], (int) anItem];
}

// Transform the constraint into a string representation
- (NSString *) constraintRepresentation: (NSLayoutConstraint *) aConstraint
{
NSString *item1 = [self nameForItem:aConstraint.firstItem];
NSString *item2 = [self nameForItem:aConstraint.secondItem];
NSString *relation = [self nameForLayoutRelation:aConstraint.relation];
NSString *attr1 = [self nameForLayoutAttribute:aConstraint.firstAttribute];
NSString *attr2 = [self nameForLayoutAttribute:aConstraint.secondAttribute];

NSString *result;

if (!aConstraint.secondItem)
{
result = [NSString stringWithFormat:@"(%4.0f) %@.%@ %@ %0.3f",
aConstraint.priority, item1, attr1,
relation, aConstraint.constant];
}
else if (aConstraint.multiplier == 1.0f)
{
if (aConstraint.constant == 0.0f)
result = [NSString stringWithFormat:@"(%4.0f) %@.%@ %@ %@.%@",
aConstraint.priority, item1, attr1, relation, item2, attr2];
else
result = [NSString stringWithFormat:
@"(%4.0f) %@.%@ %@ (%@.%@ + %0.3f)",
aConstraint.priority, item1, attr1, relation,
item2, attr2, aConstraint.constant];
}
else
{
if (aConstraint.constant == 0.0f)
result = [NSString stringWithFormat:
@"(%4.0f) %@.%@ %@ (%@.%@ * %0.3f)",
aConstraint.priority, item1, attr1, relation,
item2, attr2, aConstraint.multiplier];
else
result = [NSString stringWithFormat:
@"(%4.0f) %@.%@ %@ ((%@.%@ * %0.3f) + %0.3f)",
aConstraint.priority, item1, attr1, relation,
item2, attr2, aConstraint.multiplier, aConstraint.constant];
}

return result;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Recipe: Creating Fixed-Size Constrained Views

When working with constraints, start thinking about your views in a new way. You don’t just set a frame and expect the view to stay where and how big you left it. Constraint layout uses an entirely new set of assumptions.

Here’s how you might have written a utility method to create a label before iOS 6:

Click here to view code image

- (UILabel *) createLabelTheOldWay: (NSString *) aTitle
{
UILabel *aLabel = [[UILabel alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
aLabel.textAlignment = NSTextAlignmentCenter;
aLabel.text = aTitle;

return aLabel;
}

With Cocoa Touch’s new autolayout system, you approach code-level view creation in a new way. Your code adds constraints that adjust the item’s size and position instead of building a fixed frame and setting its center.

Disabling Autosizing Constraints

When you move into the constraints world, you start by disabling a view property that automatically translates autoresizing masks into constraints. As a rule, you either enable this, allowing the view to participate in the constraint system via its autoresizing mask, or you disable it entirely and manually add your own constraints.

Autoresizing refers to the struts and springs layout tools used in IB and to the autoresizing flags like UIViewAutoresizingFlexibleWidth used in code. When you lay out a view’s resizing behavior with these approaches, that view should not be referred to in any constraints you define.

The constraints-specific property in question is translatesAutoresizingMaskIntoConstraints. Setting this to NO ensures that you can add constraints without conflicting with the automated system. This is pretty important. If you fail to disable the property and start using constraints, you’ll generate constraint conflicts. The autoresizing constraints won’t coexist peacefully with ones you write directly. Here’s an example of a runtime error message that results:

Click here to view code image

2012-06-24 15:34:54.839 HelloWorld[64834:c07] Unable to simultaneously satisfy
constraints.
Probably at least one of the constraints in the following list is one you don't
want. Try this: (1) look at each constraint and try to figure out which you don't
expect; (2) find the code that added the unwanted constraint or constraints and
fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't
understand, refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0x6ec9430 H:[UILabel:0x6ec5210(100)]>",
"<NSAutoresizingMaskLayoutConstraint:0x6b8e2a0
h=--& v=--& H:[UILabel:0x6ec5210(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x6ec9430 H:[UILabel:0x6ec5210(100)]>
Break on objc_exception_throw to catch this in the debugger.

Choosing between autoresizing layout and constraints layout forms an important part of your coding work.

Starting within View Bounds

The first method in Recipe 5-3, constrainWithinSuperviewBounds, requests that a view is placed entirely within its superview’s bounds. It creates four constraints that ensure this. One requires that the view’s left side is at or to the right of the superview’s left side, another that the view’s top is at or below the superview’s top, and so forth.

The reason for creating this method is that in a loosely constrained system it’s entirely possible that your views will disappear offscreen with negative origins. This method basically says, “Please respect the (0,0) origin and the size of the superview when placing my subviews.”

In most real-world development, this set of constraints is not normally necessary. They are particularly useful, however, when you’re first getting started and want to explore constraints from code. They allow you to test out small constraint systems while ensuring the views you’re exploring remain visible so that you can see how they relate to each other.

In addition, as you get up to speed with constraints, you’ll probably want to add some sort of debugging feedback letting you know where your views ended up once your primary view loads and your constraints fire. Consider adding the following loop to your viewDidAppear: method:

Click here to view code image

- (void) viewDidAppear:(BOOL)animated
{
for (UIView *subview in self.view.subviews)
NSLog(@"View (%d) location: %@",
[self.view.subviews indexOfObject:subview],
NSStringFromCGRect(subview.frame));
}

Constraining Size

Recipe 5-3’s second method constrainSize: fixes a view’s extent to the CGSize you specify. This is a common task when working with constraints. You cannot just set the frame the way you’re used to. And, again, remember that your constraints are requests, not specific layouts. If your constraints are not well formed, your 100-point-wide text field may end up 107-points wide in deployment, or worse.

You can define constraints that request a specific width or height for a given view, but the sizes for the two constraints in this method can’t be known ahead of time. The method is meant for use across a wide variety of views. Therefore, the sizes are passed to the constraint as metrics. Metrics basically act as numeric constraint variables.

These particular constraints use two metric names: "theHeight" and "theWidth". The names are completely arbitrary. As a developer, you specify the strings, which correspond to keys in the metrics: parameter dictionary. You pass this dictionary as an argument in the constraint creation call. When working with metrics, each key must appear in the passed dictionary, and its value must be an NSNumber.

The two constraints in this method set the desired horizontal and vertical sizes for the view. The format strings ("H:[self(theWidth)]" and "V:[self(theHeight)]") tell the constraint system how large the view should be along each axis.

A third method, constrainPosition:, builds constraints that fix the origin of a view within its superview. Note the use of the constant to create offsets in this method.

Recipe 5-3. Basic Size Constraints

Click here to view code image

@implementation UIView (BasicConstraints)
- (void) constrainWithinSuperviewBounds
{
if (!self.superview) return;

// Constrain the top, bottom, left, and right to
// within the superview's bounds
[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeLeft
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.superview attribute:NSLayoutAttributeLeft
multiplier:1.0f constant:0.0f]];
[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationGreaterThanOrEqual
toItem:self.superview attribute:NSLayoutAttributeTop
multiplier:1.0f constant:0.0f]];
[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.superview attribute:NSLayoutAttributeRight
multiplier:1.0f constant:0.0f]];
[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeBottom
relatedBy:NSLayoutRelationLessThanOrEqual
toItem:self.superview attribute:NSLayoutAttributeBottom
multiplier:1.0f constant:0.0f]];
}

- (void) constrainSize:(CGSize)aSize
{
NSMutableArray *array = [NSMutableArray array];

// Fix the width
[array addObjectsFromArray:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:[self(theWidth@750)]"
options:0 metrics:@{@"theWidth":@(aSize.width)}
views:NSDictionaryOfVariableBindings(self)]];

// Fix the height
[array addObjectsFromArray:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[self(theHeight@750)]"
options:0 metrics:@{@"theHeight":@(aSize.height)}
views:NSDictionaryOfVariableBindings(self)]];

[self addConstraints:array];
}

- (void) constrainPosition: (CGPoint)aPoint
{
if (!self.superview) return;

NSMutableArray *array = [NSMutableArray array];

// X position
[array addObject:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeLeft
multiplier:1.0f constant:aPoint.x]];

// Y position
[array addObject:[NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual
toItem:self attribute:NSLayoutAttributeTop
multiplier:1.0f constant:aPoint.y]];

[self.superview addConstraints:array];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Recipe: Centering Views

To center views, associate their center properties (centerX and centerY) with the corresponding properties in their container. Recipe 5-4 introduces a pair of methods that retrieves a view’s superview and applies the equality relation between their centers.

Notice how these constraints are added to a parent view and not the child view. That’s because constraints cannot reference views outside their own subtree. Here’s the error generated if you attempt to do otherwise:

Click here to view code image

2012-06-24 16:09:14.736 HelloWorld[65437:c07] *** Terminating app due to uncaught
exception 'NSGenericException', reason: 'Unable to install constraint on view.
Does the constraint reference something from outside the subtree of the view?
That's illegal. constraint:<NSLayoutConstraint:0x6b6ebf0 UILabel:0x6b68e40.centerY
== UIView:0x6b64a00.centerY> view:<UILabel: 0x6b68e40; frame = (0 0; 0 0); text =
'View 1'; clipsToBounds = YES; userInteractionEnabled = NO; layer = <CALayer:
0x6b67220>>'
libc++abi.dylib: terminate called throwing an exception

Here are a couple of simple rules:

• When creating constraints, add them to the superview when the superview is mentioned as either the first or second item.

• When working with format strings, add to the superview when the string contains the superview vertical pipe symbol anywhere.

Recipe 5-4. Centering Views with Constraints

Click here to view code image

@implementation UIView (BasicConstraints)
- (void) centerHorizontallyInSuperview
{
if (!self.superview) return;

[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeCenterX
relatedBy:NSLayoutRelationEqual
toItem:self.superview attribute:NSLayoutAttributeCenterX
multiplier:1.0f constant:0.0f]];
}

- (void) centerVerticallyInSuperview
{
if (!self.superview) return;

[self.superview addConstraint:[NSLayoutConstraint
constraintWithItem:self attribute:NSLayoutAttributeCenterY
relatedBy:NSLayoutRelationEqual
toItem:self.superview attribute:NSLayoutAttributeCenterY
multiplier:1.0f constant:0.0f]];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Recipe: Setting Aspect Ratio

Constraint multipliers, the m of the y = m * x + b equation, can help set aspect ratios for your views. Recipe 5-5 demonstrates how to do this by relating a view’s height (y) to its width (x), and setting the m value to the aspect. The equation translates to view constraints through the constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant: method. Recipe 5-5 builds an NSLayoutRelationEqual relationship between the width and height of a view, using the aspect ratio as the multiplier.

The recipe applies its aspect updates by managing a fixed constraint, which it stores locally into a NSLayoutConstraint variable called aspectConstraint. Each time the user toggles the aspect from 16:9 to 4:3 or back, this recipe removes the previous constraint and creates and then stores another one. It builds this new constraint by setting the appropriate multiplier and then adds it to the view.

To allow the view’s sides some flexibility, while keeping the view reasonably large, the createLabel: method in this recipe does two things. First, it uses width and height predicates. These request that each side exceeds 300 points in length. Second, it prioritizes its requests. These priorities are high (750) but not required (1000), so the constraint system retains the power to adjust them as needed.

The outcome is a system that can change aspects in real time, and dynamically changing its layout definition at runtime.

Recipe 5-5. Creating Aspect Ratio Constraints

Click here to view code image

- (UILabel *) createLabel: (NSString *) aTitle
{
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectZero];
aLabel.font = [UIFont fontWithName:@"Futura" size:24.0f];
aLabel.textAlignment = NSTextAlignmentCenter;
aLabel.textColor = [UIColor darkGrayColor];
aLabel.text = aTitle;

aLabel.translatesAutoresizingMaskIntoConstraints = NO;
[aLabel addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"H:[aLabel(>=theWidth@750)]"
options:0 metrics:@{@"theWidth":@300.0}
views:NSDictionaryOfVariableBindings(aLabel)]];
[aLabel addConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[aLabel(>=theHeight@750)]"
options:0 metrics:@{@"theHeight":@300.0}
views:NSDictionaryOfVariableBindings(aLabel)]];

return aLabel;
}

- (void) toggleAspect: (id) sender
{
// Remove any existing aspect constraint
if (aspectConstraint)
[self.view removeConstraint:aspectConstraint];

// Create the new aspect constraint
if (useFourToThree)
aspectConstraint = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
toItem:view1 attribute:NSLayoutAttributeHeight
multiplier:(4.0f / 3.0f) constant:0.0f];
else
aspectConstraint = [NSLayoutConstraint constraintWithItem:view1
attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual
toItem:view1 attribute:NSLayoutAttributeHeight
multiplier:(16.0f / 9.0f) constant:0.0f];

// Add it to the view
[self.view addConstraint:aspectConstraint];
useFourToThree = !useFourToThree;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Aligning Views and Flexible Sizing

It is supremely easy to align views with constraints.

• The four format strings "H:|[self]", "H:[self]|", "V:|[self]", and "V:[self]|" respectively produce left, right, top, and bottom alignment.

• Add a predicate with a sizing relation and these format strings become stretch to left, stretch to right, and so on: "H:|[self(>0)]", "H:[self(>0)]|", "V:|[self(>0)]", and "V:[self(>0)]|".

• A second vertical pipe adds full-axis resizing, allowing views to stretch from left to right or top to bottom: "H:|[self(>0)]|" or "V:|[self(>0)]|".

• Add edge indicators to inset the stretches: "H:|-[self(>0)]-|" or "V:|-[self(>0)]-|".

Why You Cannot Distribute Views

What you cannot do, sadly, is distribute views along an axis under the current constraint system for equal spacing unless you know their positions in advance. Adding a "[A]-[B]-[C]" constraint or even a "[A]-(>=0)-[B]-(>=0)-[C]" constraint does not distribute the views along an axis with equal spacing the way you might expect. There are simply too many variables involved in setting up the spacing.

The first of these two constraint attempts ends up adding fixed insets between each view, the second adds random ones. The problem is that in no case does the first spacer have any fixed relation to the second spacer. Although I think Apple could eventually extend its system to introduce relations between spacing, it does not offer that feature in the current autolayout system.

In an imaginary world, the math for distribution would have to be the distance from A’s center to B’s center is equal to the distance from B’s center to C’s center, something like this:

Some distance such that A.center + distance = B.center and B.center + distance = C.center

There is simply no way to express that in the y R mx + b format used by the current constraint system, where the b offset must be known and relations are only between view attributes. Neither simultaneous equations

A. center == B. center + spacer1;
B. center == C. center + spacer2;
spacer1 == spacer2
nor a format string approach

[A]-spacer-[B]-spacer-[C]

are legal under the current system. Autolayout cannot handle the equations produced by this distribution. The best you can do is to calculate how far apart you want each item to be and then use a multiplier and offset to manually space out each view’s position.

If you have only three views to work with, you could pin two views to the left and right of a parent and center a third view between them. This approach breaks down when you introduce a fourth subview.

Recipe: Responding to Orientation Changes

A device’s screen geometry may influence how you want to lay out interfaces. For example, a landscape aspect ratio may not provide enough vertical range to fit in all your content. Consider Figure 5-11. The portrait layout places the iTunes album art on top of the album name, the artist name, and a Buy button with the album price. The landscape moves the album art to the left, centering it vertically, and places the album name, artist, and Buy button in the lower-right corner.

Figure 5-11. Same content in portrait and landscape layouts

To accomplish this, your layout constraints must be orientation aware. View controllers provide a specific place to do this. The updateViewConstraints method enables you to change your constraints during runtime. Call the method when the device is ready to rotate but before it actually does in willRotateToInterfaceOrientation:duration:. This creates the smoothest visual update by adapting the constraints before the new orientation appears.

This recipe calls the update method before the orientation change occurs. That means the recipe must store the new orientation into a local variable (newOrientation) to know how to lay out the updated presentation. It’s a pity that Apple doesn’t offer a more orientation-aware method like updateViewConstraintsForOrientation:.

Always call the superclass implementation of updateViewConstraints before adding your own specialization code. Skipping this step provides a spectacular way to crash your app at runtime.

Recipe 5-6 uses a number of constraint macros, which are detailed in the next section.

Recipe 5-6. Updating View Constraints

Click here to view code image

- (void) updateViewConstraints
{
[super updateViewConstraints];
[self.view removeConstraints:self.view.constraints];

NSDictionary *bindings = NSDictionaryOfVariableBindings(
imageView, titleLabel, artistLabel, button);

if (IS_IPAD ||
UIDeviceOrientationIsPortrait(newOrientation) ||
(newOrientation == UIDeviceOrientationUnknown))
{
for (UIView *view in @[imageView,
titleLabel, artistLabel, button])
CENTER_VIEW_H(self.view, view);
CONSTRAIN_VIEWS(self.view, @"V:|-[imageView]-30-\
[titleLabel(>=0)]-[artistLabel]-15-[button]-(>=0)-|",
bindings);
}
else
{
// Center image view on left
CENTER_VIEW_V(self.view, imageView);

// Lay out remaining views
CONSTRAIN(self.view, imageView, @"H:|-[imageView]");
CONSTRAIN(self.view, titleLabel, @"H:[titleLabel]-15-|");
CONSTRAIN(self.view, artistLabel, @"H:[artistLabel]-15-|");
CONSTRAIN(self.view, button, @"H:[button]-15-|");
CONSTRAIN_VIEWS(self.view, @"V:|-(>=0)-[titleLabel(>=0)]\
-[artistLabel]-15-[button]-|", bindings);

// Make sure titleLabel doesn't overlap
CONSTRAIN_VIEWS(self.view,
@"H:[imageView]-(>=0)-[titleLabel]", bindings);
}
}

// Catch rotation changes
- (void) willRotateToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation
duration:(NSTimeInterval)duration
{
newOrientation = toInterfaceOrientation;
[self updateViewConstraints];
}

// Store the initial orientation
- (void) viewDidAppear:(BOOL)animated
{
newOrientation = self.interfaceOrientation;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 5.

Constraint Macros

During my time developing this book, I have become more and more dependent on using constraints in my code. Constraints provide reliable components for view layout, and I love the way they work. That said, in their native form constraints are both overly verbose and fundamentally redundant. I end up implementing the same complex hard-to-read calls over and over again.

Debugging constraints is a real pain. Simple typos cost too much effort, and constraints tend to be the same from app to app. I soon found that a repository of predefined macros saved me time and increased the readability and reliability of my view layout sections. Instead of centering a view inside another view and having to debug that layout each time I implemented it, a single CENTER_VIEW macro does the job consistently each time.

Creating macros, as shown in Listing 5-1, shifts the work from producing exact constraint definitions to ensuring that constraints are consistent and sufficient across each entire view. These two conditions should form the focus of your view layout work

Consistent Constraints

A consistent set of constraints ensures that the iOS automatic layout system does not have to break any constraints to satisfy them all. An example of an inconsistent set of constraints might be this, using the macros in Listing 5-1:

Click here to view code image
for (UIView *subview in self.view.subviews)
ALIGN_VIEW_TOP(self.view, subview);
CONSTRAIN_ORDER_V(self.view, view1, view2);

This snippet constrains each view to the top of its parent, and orders View 2 below View 1. These constraints are inconsistent because View 2 cannot be constrained below View 1 and aligned to the top of the parent view at the same time. One of these conditions must break.

Sufficient Constraints

A sufficient set of constraints keeps views from random placement. The following snippet defines an insufficient constraint for View 2:

Click here to view code image

ALIGN_VIEW_LEFT(self.view, view1);
ALIGN_VIEW_TOP(self.view, view1);
CONSTRAIN_ORDER_H(self.view, view1, view2);
CONSTRAIN_ORDER_V(self.view, view1, view2);

Although View 1 is pinned at the top-left of the superview, View 2 may appear at any position below and to the right of View 1 when using the flexible-order macros defined in Listing 5-1. How it is positioned depends on the whims of autolayout. Unfortunately, Apple does not yet provide a test to determine when views are under constrained and to report their specifics.

Macros

Listing 5-1 shows the current state of my macro definitions. I’ve tested these as much as time allows. They remain, however, a work in progress. Constraints are new enough that I’m still kicking the tires on my definitions and adjusting them as needed. If you find any errors, or think of any useful expansions, drop me a note and I’ll update the GitHub repository.

Listing 5-1. Constraint Macros

Click here to view code image

// Prepare Constraint Compliance
#define PREPCONSTRAINTS(VIEW) \
[VIEW setTranslatesAutoresizingMaskIntoConstraints:NO]

// Add a visual format constraint
#define CONSTRAIN(PARENT, VIEW, FORMAT) \
[PARENT addConstraints:[NSLayoutConstraint \
constraintsWithVisualFormat:(FORMAT) options:0 metrics:nil \
views:NSDictionaryOfVariableBindings(VIEW)]]
#define CONSTRAIN_VIEWS(PARENT, FORMAT, BINDINGS) \
[PARENT addConstraints:[NSLayoutConstraint  \
constraintsWithVisualFormat:(FORMAT) options:0 metrics:nil \
views:BINDINGS]]

// Stretch across axes
#define STRETCH_VIEW_H(PARENT, VIEW) \
CONSTRAIN(PARENT, VIEW, @"H:|["#VIEW"(>=0)]|")
#define STRETCH_VIEW_V(PARENT, VIEW) \
CONSTRAIN(PARENT, VIEW, @"V:|["#VIEW"(>=0)]|")
#define STRETCH_VIEW(PARENT, VIEW) \
{STRETCH_VIEW_H(PARENT, VIEW); STRETCH_VIEW_V(PARENT, VIEW);}

// Center along axes
#define CENTER_VIEW_H(PARENT, VIEW) \
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeCenterX \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeCenterX \
multiplier:1.0f constant:0.0f]]
#define CENTER_VIEW_V(PARENT, VIEW) \
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeCenterY \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeCenterY \
multiplier:1.0f constant:0.0f]]
#define CENTER_VIEW(PARENT, VIEW) \
{CENTER_VIEW_H(PARENT, VIEW); CENTER_VIEW_V(PARENT, VIEW);}

// Align to parent
#define ALIGN_VIEW_LEFT(PARENT, VIEW) \
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeLeft \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeLeft \
multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_RIGHT(PARENT, VIEW)
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeRight \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeRight \
multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_TOP(PARENT, VIEW)
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeTop \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeTop \
multiplier:1.0f constant:0.0f]]
#define ALIGN_VIEW_BOTTOM(PARENT, VIEW) \
[PARENT addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute: NSLayoutAttributeBottom \
relatedBy:NSLayoutRelationEqual \
toItem:PARENT attribute:NSLayoutAttributeBottom \
multiplier:1.0f constant:0.0f]]

// Set Size
#define CONSTRAIN_WIDTH(VIEW, WIDTH) \
CONSTRAIN(VIEW, VIEW, @"H:["#VIEW"(=="#WIDTH")]")
#define CONSTRAIN_HEIGHT(VIEW, HEIGHT) \
CONSTRAIN(VIEW, VIEW, @"V:["#VIEW"(=="#HEIGHT")]")
#define CONSTRAIN_SIZE(VIEW, HEIGHT, WIDTH) \
{CONSTRAIN_WIDTH(VIEW, WIDTH); CONSTRAIN_HEIGHT(VIEW, HEIGHT);}

// Set Aspect
#define CONSTRAIN_ASPECT(VIEW, ASPECT) \
[VIEW addConstraint:[NSLayoutConstraint \
constraintWithItem:VIEW attribute:NSLayoutAttributeWidth \
relatedBy:NSLayoutRelationEqual \
toItem:VIEW attribute:NSLayoutAttributeHeight \
multiplier:(ASPECT) constant:0.0f]]

// Item ordering
#define CONSTRAIN_ORDER_H(PARENT, VIEW1, VIEW2) \
[PARENT addConstraints: [NSLayoutConstraint \
constraintsWithVisualFormat: (@"H:["#VIEW1"]->=0-["#VIEW2"]")\
options:0 metrics:nil \
views:NSDictionaryOfVariableBindings(VIEW1, VIEW2)]]
#define CONSTRAIN_ORDER_V(PARENT, VIEW1, VIEW2) \
[PARENT addConstraints:[NSLayoutConstraint \
constraintsWithVisualFormat:(@"V:["#VIEW1"]->=0-["#VIEW2"]")
options:0 metrics:nil
views:NSDictionaryOfVariableBindings(VIEW1, VIEW2)]]

Debugging Your Constraints

The most common problems you encounter when adding constraints programmatically are ambiguous and unsatisfiable layouts. Expect to spend a lot of time at the Xcode debugging console, and don’t be surprised when you see a large dump of information that starts with the phrase “Unable to simultaneously satisfy constraints.”

iOS does its best during runtime to let you know which constraints could not be satisfied, and which constraints it has to break to proceed. Often, it suggests a list of constraints that you should evaluate to see which item is causing the problem.

This usually looks something like this:

Click here to view code image

Probably at least one of the constraints in the following list is one you don't
want. Try this: (1) look at each constraint and try to figure out which you don't
expect; (2) find the code that added the unwanted constraint or constraints and
fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't
understand, refer to the documentation for the UIView property
translatesAutoresizingMaskIntoConstraints)
(
"<NSAutoresizingMaskLayoutConstraint:0x6e5bc90 h=-&- v=-&-
UILayoutContainerView:0x6e540f0.height == UIWindow:0x6e528a0.height>",
"<NSAutoresizingMaskLayoutConstraint:0x6e5a5e0 h=-&- v=-&-
UINavigationTransitionView:0x6e55650.height ==
UILayoutContainerView:0x6e540f0.height>",
"<NSAutoresizingMaskLayoutConstraint:0x6e592f0 h=-&- v=-&-
UIViewControllerWrapperView:0x6bb90d0.height ==
UINavigationTransitionView:0x6e55650.height - 64>",
"<NSAutoresizingMaskLayoutConstraint:0x6e57b90 h=-&- v=-&-
UIView:0x6baef20.height == UIViewControllerWrapperView:0x6bb90d0.height>",
"<NSAutoresizingMaskLayoutConstraint:0x6e5cd40 h=--- v=---
V:[UIWindow:0x6e528a0(480)]>",
"<NSAutoresizingMaskLayoutConstraint:0x6bbe890 h=--& v=--&
UILabel:0x6bb3730.midY ==>",
"<NSLayoutConstraint:0x6bb8cc0 UILabel:0x6bb3730.centerY ==
UIView:0x6baef20.centerY>"
)

Most likely, you have forgotten to switch off translatesAutoresizingMaskIntoConstraints for one of your views. If you see an NSAutoresizingMaskLayoutConstraint listed, and it’s associated with, for example, a UILabel that you’re laying out (as is the case here), that’s a big hint about where your problem lies. I’ve highlighted the two constraints that are causing the issue.

In other cases, you might have required constraints that are simply in conflict with each other because they contradict what the other one is saying. In the following dump, I required that a view be both center-aligned and left-aligned. To keep going, the layout system had to make a choice. It decided to cancel the y-centering requirement, allowing the view to align with the top of its parent.

Click here to view code image

Will attempt to recover by breaking constraint

<NSLayoutConstraint:0x6e94250 UILabel:0x6b90e00.centerY ==
UIView:0x6b8ca50.centerY>

Although constraint dumps can be scary, certain strategies lend a hand. First, when working in code, develop your constraints a little at a time. This helps you determine when things start to break. Second, consider using the macros from Listing 5-1. There’s no reason to clutter up your code with “align with superview” and “set size to n by m” over and over again. Finally, if you’re not required to set your constraints in code, consider using the IB tools provided to make your visual layout life easier.

Summary

This chapter provided an introduction to iOS’s autolayout features. Before you move on to the next chapter, here are a few thoughts to take along with you:

• IB provides an excellent set of layout tools. However, don’t feel that you cannot create constraint-based interfaces in code. The layout system offers you excellent control over your views regardless of whether you specify your constraints visually or programmatically.

• One of the great things about working with constraints is that you move away from specific-resolution solutions for your interfaces. Yes, your user experience on a tablet is likely to be quite different from that on a member of the iPhone family, but at the same time these new tools let you design for different window (and possibly screen) sizes within the same mobile family. There’s a lot of flexibility and power hidden within these simple rules.

• Start incorporating visual ornaments like shadows into your regular design routine. Alignment rectangles ensure that your user interfaces will set up properly, regardless of any secondary view elements you add to your frames.

• Reserve visual format strings for general view layout, and use view-to-view relations for detail specifics. Both approaches play an important role, and neither should be omitted from your design playbook.

• The constraint-based autolayout system is an iOS 6-only feature. If you want to create code that supports iOS 4/5, be sure to add conditional coding that uses view autosizing instead.

• Move away from thinking in terms of struts, springs, and flexible sizes. Apple’s new layout system offers better control, with more extensible tools. I like them a lot.

6. Text Entry

Some might disparage the utility of text on a family of touch-based devices. After all, users can convey so much information already using simple gestures. However, text plays an important role, especially as mobile users move away from the office and home for their daily computing interactions. Users have many reasons they need to enter and read characters onscreen. Text allows users to sign in to accounts, view and reply to e-mail, specify URLs and read the Web pages they refer to, and more. Apple’s brilliant predictive keyboard transforms text entry into a simple and fairly reliable process; its classes and frameworks offer powerful ways to present and manipulate text from your applications. This chapter introduces text recipes that support a wide range of solutions. You’ll read about controlling keyboards, making onscreen elements “text aware,” scanning text, formatting text, and so forth. This chapter provides handy recipes for common problems that you’ll encounter while working with text entry.

Recipe: Dismissing a UITextField Keyboard

A commonly asked question about smaller devices and the UITextField control is “How do I dismiss the keyboard after the user finishes typing?” There’s no built-in way to automatically detect that a user has stop typing and then respond. Yet, when users finish editing the contents of a UITextField, the keyboard really should go away. The iPad offers a keyboard-dismissal button, but the iPhone or iPod touch do not offer one.

Fortunately, it takes little work to respond to the end of text field edits, regardless of platform. You do so by allowing users to tap Done and then resign first responder status. Resigning first responder moves the keyboard out of sight, as Recipe 6-1 shows. Here are a few key points about implementing this approach:

• Setting the return key type to UIReturnKeyDone replaces the word Return with the word Done. You can do this in Interface Builder’s attributes inspector or by assignment to the text field’s returnKeyType property. Using a Done-style return key tells the user how to finish editing, rather than just relying on the fact that users have used a similar approach on nonmobile systems. Figure 6-1 shows a keyboard with a Done key style.

Figure 6-1. Setting the name of the Return key to Done (left) tells your user how to finish editing the field. Specify this directly in code or use Interface Builder’s text field attributes inspector to customize the way the text field looks and acts.

• Be the delegate. You set the text field’s delegate property to your view controller either in code or in Interface Builder (IB) by right-clicking the text field and making the assignment there. Make sure your view controller declares and implements the UITextFieldDelegate protocol.

• Implement the textFieldShouldReturn: method. This method catches all Return key presses, no matter how they are named. Use this method to resign first responder hiding the keyboard until the user touches another text field or text view.

Note

You can also use textFieldShouldReturn: to perform an action when the Return key is pressed in addition to dismissing the keyboard.

Your code needs to handle each of these points to create a smooth interaction process for your UITextField instances.

Preventing Keyboard Dismissal

Just as you can take charge of keyboard dismissal, your code can also block that action as well. View controllers can force keyboards to remain onscreen when the current responder does not support text. To do this, override the disablesAutomaticKeyboardDismissal method. The method returns a Boolean value, which allows or disallows keyboard dismissal.

Text Trait Properties

Text fields implement the UITextInputTraits protocol. This protocol provides eight properties that you set to define the way the field handles text input. Those traits are as follows:

• autocapitalizationType—Defines the text autocapitalization style. Available styles use sentence capitalization, word capitalization, all caps, and no capitalization. Avoid capitalizing when entering user names and passwords. Use word capitalization for proper names and street address entry fields.

• autocorrectionType—Specifies whether the text is subject to iOS’s autocorrect feature. When this property is enabled (set to UITextAutocorrectionTypeYes), iOS suggests replacement words to the user. Most developers disable autocorrection for user name and password fields, so iOS doesn’t accidentally correct myFacebookAccount to, for example, myofacial count.

• spellCheckingType—Determines whether to enable spell checking as the user types. Enable it with UITextSpellCheckingTypeYes, or disable it with UITextSpellCheckingTypeNo. Spell-checking is different from auto-correction, which updates items in-place as users type. Spell-checking detects and underlines misspelled items in text views, providing a visual hint for corrective replacement. By default, spellchecking is enabled whenever autocorrection is active.

• keyboardAppearance—Provides two keyboard presentation styles: the default style and a style meant to be used with an alert panel.

• keyboardType—Lets you specify the keyboard that appears when a user interacts with a field or text view. iOS provides nearly a dozen varieties. These types include standard ASCII, numbers and punctuation, PIN-based number entry (0–9), phone number entry (0–9, #, *), decimal number entry (0–9, and .), URL-optimized (prominent ., /, and .com), Email-optimized (prominent @ and .), and Twitter-optimized (prominent @ and #).

Each keyboard has its advantages and disadvantages in terms of the mix of characters it presents. The e-mail keyboard, for example, is meant to support address entry. It includes the @ symbol, along with text. The Twitter keyboard offers easy access to the hash tag (#) symbol as well as the user ID (@) symbol.

• enablesReturnKeyAutomatically—Helps control whether the Return key is disabled when there’s no text in an entry field or view. If you set this property to YES, the Return key becomes enabled after the user types in at least one character.

• returnKeyType—Specifies the text shown on the keyboard’s Return key. You can choose from the default (Return), Go, Google, Join, Next, Route, Search, Send, Yahoo, Done, and Emergency Call. Choose a value that matches the action the user performs when completing a task.

• secureTextEntry—Toggles a text-hiding feature meant to provide more secure text entry. When this property is enabled, you can see the last character typed, but all other characters are shown as a series of dots. Switch on this feature for password text fields.

Other Text Field Properties

In addition to the standard text traits, text fields offer other properties that control how the field is presented. Here are ones you should know about:

• Placeholder—Figure 6-2 shows a field’s placeholder text. This text appears in light gray when the text field is empty. It provides a user prompt, describing the target content for that field. Use the placeholder to provide usage hints such as User Name or E-mail address, as demonstrated in Figure 6-2.

Figure 6-2. Placeholder text appears inside text fields in a light gray color when the field is empty. Any text added to the field obscures the placeholder. You can set this text using IB’s text field attributes inspector or by editing the placeholder property for the field object.

• Border style—Text fields allow you to control the type of borderStyle displayed around the text area. You can choose from a simple line, a bezel (used in Figure 6-2), and a rounded rectangle presentation. These are best seen in IB, where the attributes inspector lets you toggle between each style.

• Clear button—The text field clear button appears as an X at the right side of the entry area. Set the clearButtonMode to specify if and when this button appears: always, never, when editing, or unless editing is ongoing. Always gives the greatest control to the user.

Recipe 6-1. Using the Done Key to Dismiss a Text Field Keyboard

Click here to view code image

// Dismiss the keyboard when the user taps Done
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}

- (void) viewDidLoad
{
// Update all text fields, including those defined in IB,
// setting delegate, return key type, and other useful traits
for (UIView *view in self.view.subviews)
{
if ([view isKindOfClass:[UITextField class]])
{
UITextField *aTextField = (UITextField *)view;
aTextField.delegate = self;

aTextField.returnKeyType = UIReturnKeyDone;
aTextField.clearButtonMode =
UITextFieldViewModeWhileEditing;

aTextField.borderStyle = UITextBorderStyleRoundedRect;
aTextField.contentVerticalAlignment =
UIControlContentVerticalAlignmentCenter;
aTextField.autocorrectionType =
UITextAutocorrectionTypeNo;

aTextField.font =
[UIFont fontWithName:@"Futura" size:12.0f];
aTextField.placeholder = @"Placeholder";
}
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Dismissing Text Views with Custom Accessory Views

Custom accessory views allow you to present material whenever the keyboard is shown onscreen. Common uses include adding custom buttons and other controls such as font and color pickers that affect text as the user types. Recipe 6-2 adds two buttons: one that clears already-typed text and another that dismisses the keyboard. The keyboard with these add-ons is shown in Figure 6-3.

Figure 6-3. Accessory input views allow you to add custom view elements to standard iOS keyboard presentations. Here, a pair of buttons augment iPad and iPhone keyboards.

Each accessory view is associated with a given responder (a descendent of the UIResponder class), such as a text field or text view. Add accessories by setting the inputAccessoryView property for the view. Recipe 6-2 uses a simple toolbar as its accessory view, providing extra functionality with minimal coding.

Adding a Done button to the toolbar provides the same kind of user control for text views (large, scrolling, multiline text editing views) as Recipe 6-1 offered for text fields (one-line text-input controls). The difference is that this approach allows text views to continue using the Return key to add carriage returns to text for paragraph breaks.

Note

One of this book’s tech reviewers writes that he can never remember which is a text view and which is a text field. To this, I reply, “A view is two, a field is sealed.” Text views can use any number of lines (including two or more). Text fields are single-line text entry controls, limited to a styled bounding border.

iOS developer Phil Mills offers a more amusing mnemonic: Take my text field... please. Text fields are, as he points out, one-liners.

Recipe 6-2’s Done button resigns first-responder status in its callback method. This button is not required for iPad users whose keyboard automatically includes a dismiss button, but it does no harm as used here. If you want to filter out the Done button when a universal application is run on the iPad, check the current user interface idiom. The following macro provides a simple way to test for an iPad:

#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

Always be aware that Apple may introduce new iOS device form factors, with more space or less space available to users, so try to code accordingly, especially when working with screen-consuming features like accessory views. There’s really not much you can do on that account with the current two idioms (iPhone and iPad), but it’s worth inserting notes into code in places that could see changes in the future.

Recipe 6-2. Adding Custom Buttons to Keyboards

Click here to view code image

@interface TestBedViewController : UIViewController
{
UITextView *textView;
UIToolbar *toolBar;
}
@end

@implementation TestBedViewController
// Remove text from text view
- (void) clearText
{
[textView setText:@""];
}

// Dismiss keyboard by resigning first responder
- (void) leaveKeyboardMode
{
[textView resignFirstResponder];
}

- (UIToolbar *) accessoryView
{
// Create toolbar with Clear and Done
toolBar = [[UIToolbar alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, self.view.frame.size.width, 44.0f)];
toolBar.tintColor = [UIColor darkGrayColor];

// Set up the items as Clear – flexspace - Done
NSMutableArray *items = [NSMutableArray array];
[items addObject:BARBUTTON(@"Clear", @selector(clearText))];
[items addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[items addObject:BARBUTTON(@"Done", @selector(leaveKeyboardMode))];
toolBar.items = items;

return toolBar;
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];

// Create text view and add the custom accessory view
textView = [[UITextView alloc] initWithFrame:self.view.bounds];
textView.font = [UIFont fontWithName:@"Georgia"
size:(IS_IPAD) ? 24.0f : 14.0f];
textView.inputAccessoryView = [self accessoryView];

// Use constraints to fill application bounds
[self.view addSubviewAndConstrainToBounds:textView];
[textView fitToHeightWithInset:0.0f];
[textView fitToWidthWithInset:0.0f];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Adjusting Views Around Keyboards

By necessity, iOS keyboards are large. They occupy a good portion of the screen whenever they are in use. Because of that you’ll want to adjust your text fields and text views so the keyboard does not block them when it appears onscreen. Figure 6-4 demonstrates this problem.

Figure 6-4. Keyboards occupy a large portion of the iOS device screen. If you do not force views to resize themselves and/or shift up on the screen when a keyboard appears, the keyboards will obscure onscreen material that should remain visible. You cannot see the bottom text in the last image because the text view extends behind the keyboard and reaches all the way to the bottom of the screen.

The top image shows the source text view, before it becomes first responder. The middle image demonstrates what users expect to happen—namely that the entire view remains accessible by touch even when the keyboard is onscreen. The bottom image demonstrates what happens when you do not resize views. In this case, roughly one-third of a screen of text view material becomes inaccessible. Users cannot see the final line of text, let alone edit it in any meaningful manner. The keyboard prevents any touches from getting through to the last paragraph or so of text.

Mitigate the keyboard’s presence by allowing views to resize around it. When the keyboard appears, views that continue to require interaction should adjust themselves out of the way so that they don’t overlap. To accomplish this, your application must subscribe to keyboard notifications.

iOS offers several notifications that are transmitted using the standard NSNotificationCenter, as follows:

• UIKeyboardWillShowNotification

• UIKeyboardDidShowNotification

• UIKeyboardWillChangeFrameNotification

• UIKeyboardWillHideNotification

• UIKeyboardDidHideNotification

Listen for these by adding your class as an observer. The following snippet listens for the “will hide” notification, and uses a target-selector callback. You can also handle notification updates via a new blocks-based API:

Click here to view code image

[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];

The two notifications you’ll usually want to listen for are “did show” and “will hide,” which offer opportune times for you to react to the keyboard arriving onscreen or preparing to leave. Each notification provides a userInfo dictionary that supplies the bounds for the keyboard, using the UIKeyboardBoundsUserInfoKey key. Sadly, there are no objects passed with the notification. You are not granted direct access to the keyboard itself.

Retrieving the keyboard bounds lets you resize views to adapt them to the keyboard’s presence. Recipe 6-3 adjusts its text view’s constraints to accommodate the height of the keyboard. As an alternative approach, you might want to update the text view’s bottom content inset. This provides a smoother presentation without animation side effects, and is left as an exercise for the reader.

Standard keyboards always appear at the bottom of the screen, generally when a text-capable view becomes first responder. Custom keyboards are not limited to that style of presentation. For that reason, when working with any nonstandard keyboard, use UIKeyboardFrameEndUserInfoKey instead of the keyboard bounds.

Recipe 6-3 has been tested with both soft and hardware keyboards, on devices and in the simulator. It has one limitation: It does not work consistently with accessory views and hardware keyboards. This specific issue is addressed in Recipe 6-4.

Recipe 6-3. Resizing a Text View to Make Way for a Keyboard

Click here to view code image

@implementation TestBedViewController
- (void) leaveKeyboardMode
{
// Dismiss keyboard by resigning first responder
[textView resignFirstResponder];
}

// Set constraints that leave an inset at the bottom of the text view
// Use "0.0" to stretch all the way to the bottom.
- (void) adjustToBottomInset: (CGFloat) offset
{
// Remove any vertical constraints currently applied
if (currentVerticalConstraints)
[self.view removeConstraints:currentVerticalConstraints];

// Create vertical constraints that apply the new bottom margin
currentVerticalConstraints =[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[textView(>=0)]-bottomMargin-|"
options:0 metrics:@{@"bottomMargin":@(offset)}
views:@{@"textView":textView}];
[self.view addConstraints:currentVerticalConstraints];
}

// When keyboard appears, adjust the text view's bottom margin
- (void) keyboardDidShow: (NSNotification *) notification
{
if (currentVerticalConstraints)
[self.view removeConstraints:currentVerticalConstraints];

// Retrieve the keyboard bounds via the notification's
//  userInfo dictionary
CGRect kbounds;
[(NSValue *)[notification.userInfo
objectForKey:@"UIKeyboardBoundsUserInfoKey"] getValue:&kbounds];
[self adjustToBottomInset:kbounds.size.height];
}

// Upon dismissing the keyboard, readjust the text view down
- (void) keyboardWillHide: (NSNotification *) notification
{
[self adjustToBottomInset:0.0f];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];

// Establish a text view
textView = [[UITextView alloc] initWithFrame:self.view.bounds];
textView.font = [UIFont fontWithName:@"Georgia"
size:(IS_IPAD) ? 24.0f : 14.0f];

// Add it into the primary view and stretch it to fit
[self.view addSubviewAndConstrainToBounds:textView];
[textView fitToWidthWithInset:0.0f];

// Set up initial full-height constraint
[self adjustToBottomInset:0.0f];

// Listen for keyboard hiding/showing
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(keyboardDidShow:)
name:UIKeyboardDidShowNotification object:nil];

// Add button to dismiss keyboard
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Done", @selector(leaveKeyboardMode));

}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Adjusting Views Around Accessory Views

Input accessory views are always shown onscreen whenever their text-handling view is first responder. This happens whether hardware keyboards are being used or not. Figure 6-5 shows Recipe 6-2, the recipe with accessory views, when used with and without a hardware keyboard.

Figure 6-5. When iOS uses a hardware keyboard, it dismisses the keyboard but not the accessory view, which remains onscreen at all times that its owner view holds first responder.

When it came to early iOS support of hardware keyboards and notification updates, the rule of thumb was this: Sometimes it worked, and sometimes it did not. As of iOS 6, the situation is a bit better, but not yet perfect. Basic keyboards (that is, without accessory views) function properly when users work with the onscreen keyboard, a hardware keyboard, or a mix of both. Ejecting the hardware keyboard properly sends a keyboard-dismissal notice.

The trouble arises with custom accessory views, like in Recipe 6-2. Depending on the situation (namely when the keyboard appears or disappears, or a hardware keyboard is attached or removed), the notifications and size information can prove quirky. The perfect storm occurs when the accessory view remains onscreen, the rest of the keyboard hides and a state-change notification isn’t properly sent. This causes no end of trouble for those who implement content adjustment in their applications.

Apple introduced a way to listen for the transfer between onscreen and hardware keyboards in iOS 5.0. The UIKeyboardDidChangeFrameNotification fires whenever the onscreen keyboard changes geometry. Recipe 6-4 listens for this notification to catch frame changes. In response, it updates its text view constraints and matches them to the top of the accessory view.

This approach is still not as clean as you might wish (the code checks the toolbar’s superview), but it works. The actual frame data sent out, especially when you reorient a device, may arrive out of order or just simply wrong.

Testing for Hardware Keyboards

The following method tests whether your iOS device is using a hardware keyboard. It checks the accessory view’s origin. Pass it the bounds rectangle returned by any of the keyboard notifications:

Click here to view code image

- (BOOL) isUsingHardwareKeyboard: (CGRect) kbounds
{
// Check the start of the toolbar's parent frame
CGFloat startPoint = toolBar.superview.frame.origin.y;

// Check where the bounds end
CGFloat endHeight = startPoint + kbounds.size.height;

// Determine the height of the window's frame
CGFloat viewHeight = self.view.window.frame.size.height;

// If the keyboard bounds go beyond the view height, then
// the keyboard is currently offscreen
BOOL usingHardwareKeyboard = endHeight > viewHeight;
return usingHardwareKeyboard;
}

This method has been reliably working for several updates of the firmware now, but be aware that it’s a hack.

Recipe 6-4. Handling Hardware Keyboard Changes by Resizable Text Views

Click here to view code image

- (void) adjustToBottomInset: (CGFloat) offset
{
// Remove any existing vertical constraint
if (currentVerticalConstraints)
[self.view removeConstraints:currentVerticalConstraints];

// Create a vertical constraint using the new bottom margin
currentVerticalConstraints =[NSLayoutConstraint
constraintsWithVisualFormat:@"V:|[textView(>=0)]-bottomMargin-|"
options:0 metrics:@{@"bottomMargin":@(offset)}
views:@{@"textView":textView}];
[self.view addConstraints:currentVerticalConstraints];
}

// Respond to keyboard frame update notifications by
// updating the text view
- (void) updateTextViewBounds: (NSNotification *) notification
{
if (![textView isFirstResponder])     // no keyboard
{
[self adjustToBottomInset:0.0f];
return;
}

// Fetch the keyboard's bounds
CGRect kbounds;
[(NSValue *)[notification.userInfo
objectForKey:@"UIKeyboardBoundsUserInfoKey"] getValue:&kbounds];

// Adjust the bottom inset with respect to hardware use
BOOL isUsingHardware = [self isUsingHardwareKeyboard:kbounds];
[self adjustToBottomInset: (isUsingHardware) ?
toolBar.bounds.size.height: kbounds.size.height];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];

// Create a full-view textView instance
textView = [[UITextView alloc] initWithFrame:self.view.bounds];
textView.inputAccessoryView = [self accessoryView];
[self.view addSubviewAndConstrainToBounds:textView];
[textView fitToWidthWithInset:0.0f];

// Set up initial full-height constraint
[self adjustToBottomInset:0.0f];

// Start watching (only) for frame changes
[[NSNotificationCenter defaultCenter]
addObserver:self selector:@selector(updateTextViewBounds:)
name:UIKeyboardDidChangeFrameNotification object:nil];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Creating a Custom Input View

Custom input views replace the keyboard with a view of your design whenever a text view or text field becomes first responder. You can add custom input views to nontext views as well as text ones. Recipe 6-5 focuses on the text scenario.

When you set a responder’s inputView property, the view is assigned to that property replaces the system keyboard. The easiest way to demonstrate this feature is to create a colored view and assign it to the inputView property. Consider the following code snippet. It creates two text fields. The code assigns the second field’s inputView property to a basic UIView instance with a purple background:

Click here to view code image

// Create two standard text fields
UITextField *textField1 = [[UITextField alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 30.0f)];
textField1.center = CGPointMake(self.view.frame.size.width / 2.0f, 30.0f);
textField1.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview: textField1];

UITextField *textField2 = [[UITextField alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, 200.0f, 30.0f)];
textField2.center = CGPointMake(self.view.frame.size.width / 2.0f, 80.0f);
textField2.borderStyle = UITextBorderStyleRoundedRect;
[self.view addSubview: textField2];

// Create a purple view to be used as the input view
UIView *purpleView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, self.view.
frame.size.width, 120.0f)];
purpleView.backgroundColor = COOKBOOK_PURPLE_COLOR;

// Assign the input view
textField2.inputView = purpleView;

Figure 6-6 shows this snippet’s results. When the first text field becomes first responder, the system-supplied keyboard scrolls onscreen; when the second field is selected, the purple view appears instead.

Figure 6-6. Otherwise identical, these two text fields produce different results upon becoming first responder. The top field (left image) presents a standard keyboard. The solid-color view assigned to the bottom field’s inputView property (right image) replaces the system keyboard.

Because the purple view offers no interactive elements, there’s not much you can do. You cannot enter text; you cannot dismiss the “keyboard.” You can only marvel at the functionality of displaying a custom view. Reselect the top text field to switch back to the standard keyboard.

For the most part, custom input views are not used for text input in real-life coding. Although input views play an important role in other design patterns, especially gaming, their utility for text is fairly limited. That’s because the inputAccessoryView property expands keyboard options without sacrificing built-in keys. Further, the range of keyboard options now includes numeric and decimal entry (added in iOS 4.1). These represented the prevailing requirements for designing custom keyboards in early iOS releases.

Where do custom input views make sense when working with text? For anyone willing to spend the time and effort developing their own keyboards, taking into account the various platforms and orientations, not to mention Shift modifier keys, input views provide complete control over the user experience. You create a fully customized skinnable input element that replaces the system keyboard with a look and feel uniquely suited to your design. It requires a huge amount of work, at many levels.

Recipe 6-5 provides a bare-bones example of a custom text-input view. Instead of character entry, it offers two buttons: One types Hello, and the other types World (see Figure 6-7). When tapped, each button inserts the word into its attached text view.

Figure 6-7. The custom keyboard attached as this text view’s input view allows users to enter Hello and World, and that’s pretty much all.

The challenge in creating a custom text-input view like this lies in how the text changes propagate back to the first responder. iOS offers no direct link or property that tells a custom input view who its owner is, nor can you use simple superview properties. Because of this challenge, you might want to implement a simple class extension to UIView to recover the current first responder.

Note

In this code snippet, the class method, currentResponder, is named to marginally avoid conflict with private APIs. firstResponder is an actual unpublished method. When adding category methods to Apple’s classes in production code (rather than sample code, which this is), a good rule of thumb is to prefix all method names with your initials, your company’s initials, or some other unique identifier. This ensures your method names do not overlap with Apple’s or (importantly) with any methods Apple might add in the future. This book does not follow the advice it preaches. This choice enhances readability and recognition of method names in samples.

Click here to view code image

@interface UIView (FirstResponderUtility)
+ (UIView *) currentResponder;
@end

@implementation UIView (FirstResponderUtility)
- (UIView *) findFirstResponder
{
if ([self isFirstResponder]) return self;

for (UIView *view in self.subviews)
{
UIView *responder = [view findFirstResponder];
if (responder) return responder;
}

return nil;
}

+ (UIView *) currentResponder
{
return [[[UIApplication sharedApplication] keyWindow]
findFirstResponder];
}
@end

Recipe 6-5 builds a custom UIToolbar as an input view. This displays two options (Hello and World). When tapped, the toolbar inserts a string into the first responder’s text. It retrieves the first responder if this has not yet been set. Then it checks that the responder is a kind of UITextView. Only then does it insert the new text.

Certain truths are universally acknowledged regarding input views. First, the owner of a presented input view is always first responder. Second, that owner is a subview of the application’s key window. You may leverage these facts in code, although you’ll probably want to expand the minimal error condition checking shown in Recipe 6-5, particularly with regard to the reuse of the responderView instance variable.

Recipe 6-5. Creating a Custom Input View

Click here to view code image

@interface InputToolbar : UIToolbar
{
UIView *responderView;
}
@end

@implementation InputToolbar
- (void) insertString: (NSString *) string
{
if (!responderView || ![responderView isFirstResponder])
{
responderView = [UIView currentResponder];
if (!responderView) return;
}

if ([responderView isKindOfClass:[UITextView class]])
{
UITextView *textView = (UITextView *) responderView;
NSMutableString *text =
[NSMutableString stringWithString:textView.text];
NSRange range = textView.selectedRange;
[text replaceCharactersInRange:range withString:string];
textView.text = text;
textView.selectedRange =
NSMakeRange(range.location + string.length, 0);
}
else
NSLog(@"Cannot insert %@ in unknown class type (%@)",
string, [responderView class]);
}

// Perform the two insertions
- (void) hello: (id) sender {[self insertString:@"Hello "];}
- (void) world: (id) sender {[self insertString:@"World "];}

// Initialize the bar buttons on the toolbar
- (id) initWithFrame: (CGRect) aFrame
{
if (!(self = [super initWithFrame: aFrame])) return self;

NSMutableArray *theItems = [NSMutableArray array];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"Hello", @selector(hello:))];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"World", @selector(world:))];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
self.items = theItems;

return self;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Making Text-Input-Aware Views

Many months ago, I spent some time updating the open-source iOS BOCHS emulator build to allow it to work with keyboard entry. The default code allowed touch-based “mouse” interactions but didn’t offer any keyboard support. After a little investigation, I discovered and then implemented the UIKeyInput protocol. This simple protocol, when added to a little first responder manipulation, allows you to update any view to offer text input.

Recipe 6-6 illustrates how to transform a standard UIToolbar into a view that accepts keyboard entry, letting users type text directly into the toolbar, as shown in Figure 6-8. As the user types, the toolbar text updates, even properly handling the Delete key.

Figure 6-8. Adding the UIKeyInput protocol to a toolbar transforms the view into one that can accept and display keyboard input, including deletions.

This recipe requires several features. First, the toolbar must declare the UIKeyInput protocol. This protocol announces that the view implements simple text entry and can display the system keyboard (or a custom keyboard, if so desired) when it becomes first responder.

Second, the toolbar must retain state—namely, the current string it is storing. Saving the string as a retained mutable property allows the toolbar to know what text it is currently working with and to display that text to the user.

Next, the toolbar must be able to become first responder. It does so in two ways: by implementing canBecomeFirstResponder (returning YES) and by catching touches to detect when it should assume that role. Adding a touch handler allows Recipe 6-6 to become first responder when a user touches the view.

Finally, it must implement the three required UIKeyInput protocol methods, namely hasText, insertText:, and deleteBackwards. These methods do exactly what their names imply. The hasText method returns YES whenever the view has any text available. The other two methods insert text at the current insertion point (always at the end for this recipe) and delete a character at a time from the end of the displayed text.

By declaring the protocol, becoming first responder, and handling both the string state and the input callbacks, Recipe 6-6 provides a robust way to add basic text entry to standard UIView elements. You can extend these same text features to many other classes, including labels, navigation bars, buttons, and so forth, to use in your applications as needed.

Recipe 6-6. Adding Keyboard Input to Nontext Views

Click here to view code image

@interface KeyInputToolbar: UIToolbar <UIKeyInput>
{
NSMutableString *string;
}
@end

@implementation KeyInputToolbar

// Is there text available that can be deleted
- (BOOL) hasText
{
if (!string || !string.length) return NO;
return YES;
}

// Reload the toolbar with the string
- (void) update
{
NSMutableArray *theItems = [NSMutableArray array];
[theItems addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(string, @selector(becomeFirstResponder))];
[theItems addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];

self.items = theItems;
}

// Insert new text into the string
- (void)insertText:(NSString *)text
{
if (!string) string = [NSMutableString string];
[string appendString:text];
[self update];
}

// Delete one character
- (void)deleteBackward
{
// Super caution, even if hasText reports YES
if (!string)
{
string = [NSMutableString string];
return;
}

if (!string.length)
return;

// Remove a character
[string deleteCharactersInRange:NSMakeRange(string.length - 1, 1)];
[self update];
}

// When becoming first responder, send out a notification to that effect
- (BOOL) becomeFirstResponder
{
BOOL result = [super becomeFirstResponder];
if (result)
[[NSNotificationCenter defaultCenter]
postNotification:[NSNotification notificationWithName:
@"KeyInputToolbarDidBecomeFirstResponder" object:nil]];
return result;
}

- (BOOL)canBecomeFirstResponder
{
return YES;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;
{
[self becomeFirstResponder];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Adding Custom Input Views to Nontext Views

Although custom input views can be applied to text views and text fields, they are more valuable in other use-cases. Input doesn’t have to be about text. In fact, by taking the system keyboard out of the equation, custom input views can range to whatever kind of scenario you need.

Think of input views as context-sensitive graphical menus that appear only when a particular view class becomes first responder. When you tap a warrior, perhaps a set of weapons scrolls onscreen, including a bow, a mace, and a sword. The user can select the kind of attack the warrior should apply. Or think of a graphics layout program. When a circle, square, or line is tapped, maybe an onscreen palette is revealed that lets users set the stroke width, the stroke color, and the fill. The only limit to the utility of custom input is your imagination.

Recipe 6-7 demonstrates how a custom input view can affect a nontext view. It combines the code from Recipes 6-5 and 6-6, creating both an input-aware view (ColorView), which can become first responder with a touch, and an input view (InputToolbar) that affects the display of that primary view. In this example, the base view’s role is limited to displaying a color. The toolbar controls what color that is.

Because there’s no other way to transfer first responder control, the input view also offers a Done button, allowing the user to dismiss the keyboard, thus resigning first responder from the big color view.

Adding Input Clicks

Use the UIDevice class to add input clicks to your custom input accessory views. The playInputClick method plays the standard system keyboard click and can be called when you respond to user input taps.

Adopt the UIInputViewAudioFeedback protocol in the accessory input class and add an enableInputClicksWhenVisible delegate method that always returns YES. This defers audio playback to the user’s preferences, which are set in Settings > Sounds. To hear these clicks, the user must have enabled keyboard click feedback. If the user has not done so, your calls to playInputClick are simply ignored.

Recipe 6-7. Creating a Custom Input Controller for a Nontext View

Click here to view code image

@interface ColorView : UIView <UIKeyInput>
@property (retain) UIView *inputView;
@end

#pragma mark Key Input Aware View
@implementation ColorView
@synthesize inputView;

// UITextInput protocol skeleton
- (BOOL) hasText {return NO;}
- (void)insertText:(NSString *)text {}
- (void)deleteBackward {}

// First responder support
- (BOOL)canBecomeFirstResponder {return YES;}
- (void)touchesBegan:(NSSet *)touches
withEvent:(UIEvent *)event {[self becomeFirstResponder];}

// Initialize with user interaction allowed
- (id) initWithFrame:(CGRect)aFrame
{
if (!(self = [super initWithFrame:aFrame])) return self;
self.backgroundColor = COOKBOOK_PURPLE_COLOR;
self.userInteractionEnabled = YES;
return self;
}
@end

#pragma mark Color Input Toolbar
@interface InputToolbar : UIToolbar <UIInputViewAudioFeedback>
@end

@implementation InputToolbar
- (BOOL) enableInputClicksWhenVisible
{
return YES;
}

- (void) updateColor: (UIColor *) aColor
{
[UIView currentResponder].backgroundColor = aColor;
[[UIDevice currentDevice] playInputClick];
}

// Color updates
- (void) light: (id) sender {
[self updateColor:[COOKBOOK_PURPLE_COLOR
colorWithAlphaComponent:0.33f]];}
- (void) medium: (id) sender {
[self updateColor:[COOKBOOK_PURPLE_COLOR
colorWithAlphaComponent:0.66f]];}
- (void) dark: (id) sender {
[self updateColor:COOKBOOK_PURPLE_COLOR];}

// Resign first responder on pressing Done
- (void) done: (id) sender
{
[[UIView currentResponder] resignFirstResponder];
}

// Create a toolbar with each option available
- (id) initWithFrame: (CGRect) aFrame
{
if (!(self = [super initWithFrame: aFrame])) return self;

NSMutableArray *theItems = [NSMutableArray array];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"Light", @selector(light:))];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"Medium", @selector(medium:))];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"Dark", @selector(dark:))];
[theItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
[theItems addObject:BARBUTTON(@"Done", @selector(done:))];
self.items = theItems;

return self;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Building a Better Text Editor (Part I)

Undo support and persistence help create better text editors in your application. These features ensure that your users can reverse mistakes and pick up their work from where they left off. To accomplish this requires surprisingly little programming, as is demonstrated in Recipe 6-8.

Text views provide built-in support that works hand in hand with select, cut, copy, and paste. The undo manager understands these actions, so possible user messages might include Undo Paste, Redo Cut, and so forth. All the view controller needs to do is instantiate an undo manager; it leaves the rest of the work to the built-in objects.

This recipe adds Undo and Redo buttons to the keyboard accessory view. These buttons must be updated each time the text view contents change. To accomplish this, the view controller becomes the text view’s delegate and implements the textViewDidChange: delegate method. Buttons are enabled or disabled accordingly.

This recipe uses persistence to store the text contents between application launches. It archives its contents to file in the performArchive method. The application delegate calls this method right before the application is due to suspend and also each time the text view resigns first responder status. This better ensures that the data remains fresh and up to date between application sessions:

Click here to view code image

- (void) applicationWillResignActive:(UIApplication *)application
{
[tbvc archiveData];
}

On launch, any data in that file is read in to initialize the text view instance during the view controller setup.

Recipe 6-8. Adding Undo Support and Persistence to Text Views

Click here to view code image

#define SYSBARBUTTON(ITEM, SELECTOR) [[UIBarButtonItem alloc] \
initWithBarButtonSystemItem:ITEM target:self action:SELECTOR]
#define SYSBARBUTTON_TARGET(ITEM, TARGET, SELECTOR) \
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:ITEM \
target:TARGET action:SELECTOR]

// Store data out to file
- (void) archiveData
{
[textView.text writeToFile:DATAPATH atomically:YES
encoding:NSUTF8StringEncoding error:nil];
}

// Update the undo and redo button states
- (void)textViewDidChange:(UITextView *)textView
{
[self loadAccessoryView];
}

// Choose which items to enable and disable on the toolbar
- (void) loadAccessoryView
{
NSMutableArray *items = [NSMutableArray array];
UIBarButtonItem *spacer =
SYSBARBUTTON(UIBarButtonSystemItemFixedSpace, nil);
spacer.width = 40.0f;

BOOL canUndo = [textView.undoManager canUndo];
UIBarButtonItem *undoItem = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemUndo, textView.undoManager, @selector(undo));
undoItem.enabled = canUndo;
[items addObject:undoItem];
[items addObject:spacer];

BOOL canRedo = [textView.undoManager canRedo];
UIBarButtonItem *redoItem = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemRedo, textView.undoManager, @selector(redo));
redoItem.enabled = canRedo;
[items addObject:redoItem];
[items addObject:spacer];

[items addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[items addObject:BARBUTTON(@"Done", @selector(leaveKeyboardMode))];

toolbar.items = items;
}

// Return a plain accessory view
- (UIToolbar *) accessoryView
{
toolbar = [[UIToolbar alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 44.0f)];
toolbar.tintColor = [UIColor darkGrayColor];
return toolbar;
}

- (void) loadView
{
[super loadView];

// Load any existing string
if ([[NSFileManager defaultManager] fileExistsAtPath:DATAPATH])
{
NSString *string =
[NSString stringWithContentsOfFile:DATAPATH
encoding:NSUTF8StringEncoding error:nil];
textView.text = string;
}

// Subscribe to keyboard changes
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(updateTextViewBounds:)
name:UIKeyboardDidChangeFrameNotification object:nil];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Building a Better Text Editor (Part II)

Starting with iOS 6, text views and text fields can now work with attributed text strings (that is, strings that support styles, not just plain-text ones). This allows you to create highly featured text views and fields with multiple fonts, styles, and colors. Quite a bit of this styled functionality (like using color, for example) is tied up in Core Text, which is discussed in The Advanced iOS 6 Developer’s Cookbook. For simple text editors, however, it takes very little work to add support for basic styles: bold, italics, and underlines.

Enabling Attributed Text

To handle style requests, you must change a flag that lets your text view work with attributed (in other words styled) text. Set the allowsEditingTextAttributes property to YES. Upon doing so, several things happen:

• The text view begins updating its attributedText property. This property enables you to retrieve the text view’s contents as an attributed string.

• The view begins responding to a series of special UIResponder methods that toggle bold face, italics, and underlining for selected text. These methods are detailed in the next section.

• The view’s interactive user-interface menu starts to show new options, allowing users to style the current selection using bold, italics, and underlining.

Controlling Attributes

In iOS 6, NSObject offers new methods to control several text attributes. These methods are intended for use by UIResponder subclasses and are part of the UIResponderStandardEditActions informal protocol. This protocol declares common editing commands for the iOS user interface.

The methods of interest include toggleBoldFace:, toggleItalics:, and toggleUnderline:. These three methods apply styles to current text selections or, if the styles have already been applied, remove them.

To allow these updates, you just tell the responder (in this case, a text view) to enable text attribute editing. The text view or text field in question implements all the heavy lifting. You can implement these calls with nothing more than bar button actions.

Recipe 6-9 demonstrates how to build these features in to your iOS application. Figure 6-9 shows the interface built by this recipe.

Figure 6-9. The UIResponderStandardEditActions protocol defines common text editing commands, which you wrap into your user interface. The keyboard accessory view offers one-button access in addition to the BIU options that automatically appear in the system menu. The accessory view allows you to select all (Sel), or apply (or remove) bolding (B), italics (I), and underlining (U).

Other Responder Functionality

Notice the Sel option on the accessory bar, to the left of the B/I/U bold, italics, and underlining choices. This bar button adds a Select All feature via by the same UIRespondersStandardEditActions protocol as style toggles. Editing methods include the following:

• copy:, cut:, delete: and paste: for basic edits

• select: and selectAll: for selections

• toggleBoldFace:, toggleItalics:, and toggleUnderline: for style updates

This protocol also lets you control the direction of writing through the makeTextWritingDirectionLeftToRight: and makeTextWritingDirectionRightToLeft: methods.

Recipe 6-9. Enhanced Text Editor

Click here to view code image

// Handy bar button macros
#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] \
initWithTitle:TITLE style:UIBarButtonItemStylePlain \
target:self action:SELECTOR]
#define BARBUTTON_TARGET(TARGET, TITLE, SELECTOR) \
[[UIBarButtonItem alloc] initWithTitle:TITLE \
style:UIBarButtonItemStylePlain target:TARGET action:SELECTOR]
#define SYSBARBUTTON(ITEM, SELECTOR) [[UIBarButtonItem alloc] \
initWithBarButtonSystemItem:ITEM target:self action:SELECTOR]

// Choose which items to enable and disable on the toolbar
- (void) loadAccessoryView
{
NSMutableArray *items = [NSMutableArray array];
UIBarButtonItem *spacer = SYSBARBUTTON(UIBarButtonSystemItemFixedSpace, nil);
spacer.width = 20.0f;

BOOL canUndo = [textView.undoManager canUndo];
UIBarButtonItem *undoItem = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemUndo, textView.undoManager, @selector(undo));
undoItem.enabled = canUndo;
[items addObject:undoItem];

BOOL canRedo = [textView.undoManager canRedo];
UIBarButtonItem *redoItem = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemRedo, textView.undoManager, @selector(redo));
redoItem.enabled = canRedo;
[items addObject:redoItem];

// Add select all
[items addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[items addObject:BARBUTTON_TARGET(textView, @"Sel", @selector(selectAll:))];

// Add style buttons
[items addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[items addObject:BARBUTTON_TARGET(textView,
@"B", @selector(toggleBoldface:))];
[items addObject:BARBUTTON_TARGET(textView,
@"I", @selector(toggleItalics:))];
[items addObject:BARBUTTON_TARGET(textView,
@"U", @selector(toggleUnderline:))];

[items addObject:SYSBARBUTTON(UIBarButtonSystemItemFlexibleSpace, nil)];
[items addObject:BARBUTTON(@"Done", @selector(leaveKeyboardMode))];

toolbar.items = items;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Text-Entry Filtering

At times you want to ensure a user enters only a certain subset of characters. For example, you might want to create a numeric-only text field that does not handle letters. Although you can use predicates to test the final entry against a regular expression (the NSPredicate class’s MATCH operator supports regex values, and is demonstrated in Recipe 6-11), for filtered data it’s easier to check each new character as it is typed against a legal set.

A UITextField delegate can catch those characters as they are typed and decide whether to add the character to the active text field. The optional textField:shouldChangeCharactersInRange:replacementString: delegate method returns either YES, allowing the newly typed characters, or NO, disallowing it (or them). In practice, this works on a character-by-character basis, being called after each user keyboard tap. However, with iOS’s pasteboard support, the replacement string could theoretically be longer when text is pasted to a text field.

Recipe 6-10 looks for any disallowed characters within the new string. When it finds them, it rejects the entry, leaving the text field unedited. So, a paste of mixed allowed and disallowed text would be rejected entirely.

This recipe considers four scenarios: alphabetic text entry only, numeric, numeric with an allowed decimal point, and a mix of alphanumeric characters. You can adapt this example to any set of legal characters you want.

The third entry type, numbers with a decimal point, uses a little trick to ensure that only one decimal point gets typed. Once it finds a period character in the associated text field, it switches the characters it accepts from a set with the period to a set without it.

Users can sneak around this by using paste. Even if you feel that it’s unlikely for users to do so, design against the possibility. Disallow pasting by overriding your text field’s canPerformAction:withSender: method to specifically exclude this action.

The following snippet ensures that users cannot paste into a text field. It returns NO when queried about the paste: action. Similar guards offer selection (select and select all) when the field has text to select (hasText). The cut and copy options mandate that the user selection includes a valid nonempty selection range:

Click here to view code image

@interface LimitedTextField : UITextField
@end
@implementation LimitedTextField
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
UITextRange *range = self.selectedTextRange;
BOOL hasText = self.text.length > 0;

if (action == @selector(cut:)) return !range.empty;
if (action == @selector(copy:)) return !range.empty;
if (action == @selector(select:)) return hasText;
if (action == @selector(selectAll:)) return hasText;
if (action == @selector(paste:)) return NO;

return NO;
}
@end;

The lesson is this: Never underestimate your user’s ability to thwart your design when you leave openings for him or her to do so.

Recipe 6-10. Filtering User Text Entry

Click here to view code image

#define ALPHA @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz "

@implementation TestBedViewController
- (BOOL)textField:(UITextField *) aTextField
shouldChangeCharactersInRange: (NSRange)range
replacementString:(NSString *) string
{
NSMutableCharacterSet *cs =
[NSMutableCharacterSet
characterSetWithCharactersInString:@""];

switch (segmentedControl.selectedSegmentIndex)
{
case 0:
[cs addCharactersInString:ALPHA];
break;
case 1:
[cs formUnionWithCharacterSet:
[NSCharacterSet decimalDigitCharacterSet]];
break;
case 2:
[cs formUnionWithCharacterSet:
[NSCharacterSet decimalDigitCharacterSet]];

// permit one decimal only
if ([textField.text rangeOfString:@"."].location
== NSNotFound)
[cs addCharactersInString:@"."];
break;
case 3:
[cs addCharactersInString:ALPHA];
[cs formUnionWithCharacterSet:
[NSCharacterSet decimalDigitCharacterSet]];
break;
default:
break;
}

NSString *filtered =
[[string componentsSeparatedByCharactersInSet:[cs invertedSet]]
componentsJoinedByString:@""];
BOOL basicTest = [string isEqualToString:filtered];
return basicTest;
}

- (void) segmentChanged: (UISegmentedControl *) seg
{
// Reset text on segment change
textField.text = @"";
}

- (void) viewDidAppear: (BOOL) animated
{
// Create a testbed text field to work with
textField = [[UITextField alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, 200.0f, 30.0f)];
textField.delegate = self;
[self.view addSubview: textField];

// Add segmented control with entry options
segmentedControl = [[UISegmentedControl alloc] initWithItems:
[@"ABC 123 2.3 A2C" componentsSeparatedByString:@" "]];
[segmentedControl addTarget:self action:@selector(segmentChanged:)
forControlEvents:UIControlEventValueChanged];
self.navigationItem.titleView = segmentedControl;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Detecting Text Patterns

Recipe 6-10 introduced ways to limit users to entering legal characters. From there, it’s just a short hop to matching user input against a variety of legal patterns. Consider a floating-point number. It might be described as an optional sign followed by a whole component followed by an optional decimal and then a fractional component. Or maybe the whole component should be optional but the sign mandatory.

Unfortunately, there are many standard ways of describing things, and those ways increase exponentially when you expand from simple numbers to phone numbers, e-mail addresses, and URLs. Apple has taken care of many of these for you, with its built-in data detector classes, but it often helps to know how to roll your own.

Rolling Your Own Expressions

Some standards organizations have published descriptions of exactly what makes up a legal value, and enterprising developers have transformed many of those descriptions into fairly portable regular expressions. Consider the following regular expression definition of a floating-point number:

^[+-]?[0-9]+[\.]?[0-9]*$

It’s not a perfect definition, but for many purposes it’s a pretty good one, and a flexible one to boot. It accepts a pretty good range of floating-point numbers with optional signs at the start. Admittedly, as presented it won’t accept –.75, but it will also not accept –. which I think offers a fair compromise, because –0.75 isn’t too hard to guess on the part of the user. Alternatively, you could use a set of regular expression checks, and accept any positive result that occurs out of that set—for example, adding in floating points that do not require a whole portion but do require a decimal point to start them followed by one or more digits:

^[+-]?\.[0-9]+$

NSPredicate instances can compare NSString text to a regular expression, detecting when users have entered a valid floating-point number. Here’s an example:

Click here to view code image

NSPredicate *fpPredicate = [NSPredicate predicateWithFormat:
@"SELF MATCHES '^[+-]?[0-9]+[\\.]?[0-9]*$'"];
BOOL match = [fpPredicate evaluateWithObject:string];

It is, as already stated, a bit harder to detect phone numbers, e-mail, and other more sophisticated entry types. Here’s my inexpert go at the phone number problem, using U.S. numbers, in the form of a regular expression:

^[\(]?([2-9][0-9]{2})[\)]?[-.\. ]?([2-9][0-9]{2})[-.\. ]?([0-9]{4})$

This regular expression offers optional parentheses, although there is no way to check that they balance; you could, however, accomplish that with some simple additional Objective-C coding. It ensures that both the area code and the phone number prefix don’t start with 0 or 1, and allows the user to enter optional spacers between the numbers (a space, a dash, or a period). In other words, for one line of description, it’s a pretty okay but not spectacular definition of phone numbers.

Recipe 6-11 uses this regular expression to determine when a user has entered a phone number. Upon receiving a positive match, it updates the navigation bar’s title to acknowledge success. This recipe demonstrates how you can perform real-time filtering and pattern matching to detect some goal pattern and provide a way to act on positive results.

Enumerating Regular Expressions

The NSRegularExpression class offers a block-based enumeration approach to find matches within a string. Use this to apply updates to given ranges. When you work with attributed text, you can apply color or font hints to just those elements that match the regex. This is similar to a text view’s spell checker, which adds underlines to highlight misspelled words.

To roll your own, create a regular expression. Enumerate it over a string (typically one found in a text view of some sort) and use each range to create some kind of visual update. With iOS 6’s support for attributed strings, it’s easier than ever to add visual feedback to text view contents:

Click here to view code image

// Check for matches
NSRegularExpression *regex = [NSRegularExpression
regularExpressionWithPattern:@"REGEXHERE"
options:NSRegularExpressionCaseInsensitive error:nil];

// Enumerate over a string
[regex enumerateMatchesInString:text options:0 range:fullRange
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags, BOOL *stop){
NSRange range = match.range;
// Perform some action on the range
}];

Data Detectors

The NSDataDetector class is a subclass of NSRegularExpression. Data detectors allow you to search for well-defined data types, including dates, addresses, URL links, phone numbers, and transit information using Apple’s fully tested algorithms instead of trying to create your own regular expressions. Even better, they’re localized!

Take the same approach shown previously for enumerating regular expressions. This code snippet searches for links (URLs) and phone numbers:

Click here to view code image

NSError *error = NULL;
NSDataDetector *detector = [NSDataDetector
dataDetectorWithTypes:NSTextCheckingTypeLink|NSTextCheckingTypePhoneNumber
error:&error];

// Enumerate over a string
[detector enumerateMatchesInString:text options:0 range:fullRange
usingBlock:^(NSTextCheckingResult *match,
NSMatchingFlags flags, BOOL *stop){
NSRange range = match.range;
// Perform some action on the range
}];

The checks are built around the NSTextCheckingResult class. This class describes items that match the data detector’s content discovery. The kinds of data detectors supported by iOS are going to grow over time. For now, they are limited to dates (NSTextCheckingTypeDate), addresses (NSTextCheckingTypeAddress), links (NSTextCheckingTypeLink), phone numbers (NSTextCheckingTypePhoneNumber), and transit info like flight information (NSTextCheckingTypeTransitInformation). I expect to possibly see this list expand to include common stock symbols, UPS/FedEx shipping numbers, and other easily recognized patterns.

Using Built-In Type Detectors

UITextViews and UIWebViews offer built-in data type detectors, including phone numbers, HTTP links, and so forth. Set the dataDetectorTypes property to allow the view to automatically convert pattern matches into clickable URLs that are embedded into the view’s text. Legal types include addresses, calendar events, links, and phone numbers. Use UIDataDetectorTypeAll to match all supported types, or use UIDataDetectorTypeNone to disable pattern matching.

Useful Websites

When working with regular expressions, you may want to check out a number of handy websites to assist you with your work:

• The Regular Expression Library (http://regexlib.com) site has indexed thousands of regular expressions from contributors around the world.

• Go to Regex Pal (http://regexpal.com) to test your regex expressions via an interactive JavaScript tool.

• Use the txt2re generator (http://txt2re.com) to build code that extracts elements from source strings that you provide. It supports output in C as well as several other language destinations.

Recipe 6-11. Detecting Text Patterns Using Predicates and Regular Expressions

Click here to view code image

@implementation TestBedViewController
- (void) updateStatus: (NSString *) string
{
// This is a predicate matching U.S. telephone numbers
NSPredicate *telePredicate = [NSPredicate predicateWithFormat:
@"SELF MATCHES \
'^[\\(]?([2-9][0-9]{2})[\\)]?[-.\\. ]?([2-9][0-9]{2})\
[-.\\. ]?([0-9]{4})$'"];
BOOL match = [telePredicate evaluateWithObject:string];
self.title = match ? @"Phone Number" : nil;
}

- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
NSString *newString = [textField.text
stringByReplacingCharactersInRange:range withString:string];

if (!string.length)
{
[self updateStatus:newString];
return YES;
}

NSMutableCharacterSet *cs = [NSMutableCharacterSet
characterSetWithCharactersInString:@""];
[cs formUnionWithCharacterSet:
[NSCharacterSet decimalDigitCharacterSet]];
[cs addCharactersInString:@"()-. "];

// Legal characters check
NSString *filtered = [[string componentsSeparatedByCharactersInSet:
[cs invertedSet]] componentsJoinedByString:@""];
BOOL basicTest = [string isEqualToString:filtered];

// Test for phone number
[self updateStatus:basicTest ? newString : textField.text];

return basicTest;
}

- (void) loadView
{
[super loadView];

tf = [[UITextField alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, 200.0f, 30.0f)];
tf.center = CGPointMake(self.view.frame.size.width / 2.0f, 40.0f);
tf.borderStyle = UITextBorderStyleRoundedRect;
tf.autocorrectionType = UITextAutocorrectionTypeNo;
tf.clearButtonMode = UITextFieldViewModeAlways;
tf.delegate = self;
[self.view addSubview:tf];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Recipe: Detecting Misspelling in a UITextView

The UITextChecker class provides a way to automatically scan text for misspellings. To use this class, you must first set the target language—for example, en for English, en_US for U.S. English, or fr_CA for Canadian French. The language codes use a combination of ISO 639-1 and optional ISO 3166-1 regions. So, while you can choose to use a general English dictionary (en), you can also differentiate between usage in the United States (en_US), Australia (en_AU), and the United Kingdom (en_GB). Query UITextChecker for an array of availableLanguages from which to pick.

The class also allows you to learn new words (learnWord:) and forget words (unlearnWord:) to customize the onboard dictionary to the user’s need. Learned words are used across languages; so, when you add a person’s name, that name is available universally. Checker objects can also set words to ignore using instance methods.

Recipe 6-12 demonstrates how to incorporate a text checker into your application by iteratively selecting each misspelled word. To do this, you need to control range selection for the text view. To select text in a UITextView, it must already be first responder. Check the responder status and update the view if needed:

if (![textView isFirstResponder])
[textView becomeFirstResponder];

Then calculate a range you want to select, making sure you take the content length into account and set the selectedRange property for the text view:

textView.selectedRange = NSMakeRange(offset, length);

Because a text view must be editable, as well as the first responder, the keyboard appears onscreen while you perform any range selection. Because the user can edit any material you have onscreen, code for cases in which user edits may disrupt your application.

Recipe 6-12. Searching for Misspellings

Click here to view code image

@implementation TestBedViewController
- (void) nextMisspelling: (id) sender
{
// Scan for a new word from the current offset
NSRange range = [textChecker rangeOfMisspelledWordInString: textView.text
range:NSMakeRange(0, textView.text.length) startingAt:textOffset
wrap:YES language:@"en"];

// Skip forward each time a new misspelling is found
if (range.location != NSNotFound)
textOffset = range.location + range.length;
else
textOffset = 0;

// Select the word
if (![textView isFirstResponder])
[textView becomeFirstResponder];
if (range.location != NSNotFound)
textView.selectedRange = range;
}

- (void) viewDidAppear: (BOOL) animated
{
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Next Misspelling", @selector(nextMisspelling:));

textView = [[UITextView alloc] initWithFrame:self.view.bounds];
[self.view addSubview:textView];
textView.editable = YES;

textChecker = [[UITextChecker alloc] init];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 6.

Searching for Text Strings

It takes little work to adapt Recipe 6-12 to search for text. To implement search, add a text field to your navigation bar and change the bar button to Find. Use NSString’s rangeOfString:options:range: method to locate the desired string. Careful, the string you search for must not be nil. After finding the range of your target text (and assuming the location is not NSNotFound), you can then scroll the text view to the right position by calling scrollRangeToVisible:. Pass the range returned by the string method.

Note

NSNotFound is a constant used to indicate that a range was not successfully located. Check the location field after a search to ensure that a valid value was set.

Summary

This chapter introduced many ways to creatively use text in your iOS applications. In this chapter, you’ve read about controlling the keyboard and resizing views to accommodate text entry. You’ve discovered how to create custom input views and how to filter text and test it for valid entry. Before you leave this chapter, here are a few final thoughts to take away:

• Don’t assume your users will or will not be using Bluetooth keyboards. Test your applications with hardware as well as software text entry.

• Although accessory views provide a wonderful way to add extra functionality to your text-input chores, don’t overdo the accessories. Keyboards on the iPhone and iPod touch already cover an overwhelming portion of the screen. Adding accessory views further diminishes user space. Where possible, go spartan and minimal in your accessory design.

• Never assume your user will ever use shake-to-undo, a feature I find of questionable value. Provide undo/redo support directly in your application’s GUI, where the user can immediately recognize what to do rather than have to recall that Apple added that obscure feature and that it’s available for use. Shake-to-undo should always supplement other undo/redo support, not replace it. Undo/redo buttons are a best-use scenario for accessory views.

• Even though you might not be able to construct a perfect regular expression to test user input, don’t discount regular expressions that are good enough to cover most cases. And don’t forget that you can always use more than one regular expression in sequence to test different approaches to the same problem.

7. Working with View Controllers

View controllers simplify view management for many iOS applications. Each view controller owns a hierarchy of views, which presents a complete element of a unified interface. They enable you to build applications that centralize many tasks including orientation changes and responding to user actions. This chapter looks at using view-controller-based classes and how to apply them to real-world situations for both iPhone/iPod and iPad design scenarios.

View Controllers

As their name suggests, view controllers provide the Controller component of iOS’s Model-View-Controller design pattern. Each view controller manages a set of views that comprise a single user-interface component within an application. View controllers coordinate view loading and appearance, respond to user interactions, and update application semantics.

View controllers also harmonize with the device and underlying operating system. When a user rotates the device, for example, the view controller may update its views’ layout. When the OS encounters a low-memory scenario, controllers respond to memory warnings.

In short, view controllers provide central management. They negotiate with a range of orthogonal development requirements sourced from views, models, iOS, and the device itself.

View controllers also centralize presentation metaphors. The ability to layer view controllers in containers extends the paradigm to exciting custom designs. The most common styles of system-supplied parent/child view controllers include navigation controllers that allow users to move their attention from view to view, page view controllers that present virtual books, tab controllers that offer push-button access to multiple child controllers, and split view controllers that offer master-list/detail presentations.

View controllers aren’t views. They are classes with no visual representation. As a point of contrast, UIViews use initWithFrame:; UIViewControllers use init. View controllers help your views live in a larger application design environment.

The iOS software development kit (SDK) offers many view controller classes. These classes range from the general to the specific. Here’s a quick guide to a subset of the view controllers you’ll encounter while building your iOS application interfaces.

The UIViewController Class

UIViewController is the parent class for view controllers and the one you use to manage your primary views. It is the workhorse of view controllers. You may spend a large part of your time customizing subclasses of this one class. The basic UIViewController class manages each primary view’s lifetime from start to finish and takes into account the changes that the view must react to along the way.

UIViewController instances are responsible for setting up how a view looks and what subviews it displays. Often they rely on loading that information from storyboard files. Instance methods let you customize view layout (loadView) or add behavior after a view finishes loading (viewDidLoad).

Reacting to views being displayed or dismissed is another job that view controllers handle. These are the realities of belonging to a larger application. Methods such as viewWillAppear: and viewWillDisappear: let you finish any bookkeeping associated with your view management. You might preload data in anticipation of being presented or clean up once a view will no longer be shown onscreen.

Each of the tasks mentioned here specifies how a view fits into an enveloping application. The UIViewController mediates between views and these external demands, allowing the view to change itself to meet these needs.

Navigation Controllers

As the name suggests, navigation controllers allow you to drill up and down through tree-based view hierarchies, which is an important primary interface design strategy on smaller members of the iOS device family and a supporting one on tablets. Navigation controllers create the solid-colored navigation bars that appear at the top of many standard iOS applications.

Navigation controllers let you push new views into place onto a stored stack and automatically generate Back buttons showing the title of the calling view controller. All navigation controllers use a “root” view controller to establish the top of their navigation tree, letting those Back buttons lead you back to the primary view. On tablets, you can use a navigation controller-based interface to work with bar-button-based menu items, to present popover presentations, or to integrate with UISplitViewController instances for a master-detail presentation experience.

Handing off responsibility to a navigation controller lets you focus design work on creating individual view controller screens. You don’t have to worry about specific navigation details other than telling the navigation controller which view to move to next. The history stack and the navigation buttons are handled for you.

Tab Bar Controllers

The UITabBarController class lets you control parallel presentations in your application. These are like stations on a radio. A tab bar helps users select which view controller to “tune in to,” without there being a specific navigation hierarchy. Each parallel world operates independently, and each can have its own navigation hierarchy. You build the view controller or navigation controller that inhabits each tab, and Cocoa Touch handles the multiple-view details.

For example, when tab bar instances offer more than a certain number of view controller choices at a time (five on the iPhone-family of devices, more on tablets), users can customize them through the More > Edit screen. The More > Edit screen lets users drag their favorite controllers down to the button bar at the bottom of the screen. No extra programming is involved. You gain editable tabs for free. All you have to do is request them via the customizableViewControllers property.

Split View Controllers

Meant for use on tablet applications, the UISplitViewController class offers a way to encapsulate a persistent set of data (typically a table) and associate that data with a detail presentation. You can see split views in action in the iPad’s mail application. When used in landscape orientation, a list of messages appears on the left; individual message content appears on the right. The detail view (the message content in Mail) on the right is subordinate to the master view (Mail’s message list) on the left. Tapping a message updates the right-side view with its contents.

In portrait orientation, the master view is normally hidden. It is accessed by a popover, which is reached by tapping the left button of the split view’s top bar or via a swipe gesture (in iOS 5.1 and later).

Page View Controller

Like navigation controllers, tab view controllers, and split view controllers, page view controllers are containers for other view controllers. They create a book-like presentation where you set the book’s “spine,” typically along the left or top of the view. Build your “book” by adding individual content controllers. Each “page” transitions to the next using page curls or pans.

Popover Controllers

Specific to tablets, popover controllers create transient views that pop over other existing interface content. These controllers present information without taking over the entire screen, the way that modal views normally do. The popovers are usually invoked by tapping a bar button item in the interface (although they can be created using other interaction techniques) and are dismissed either by interacting with the content they present or by tapping outside their main view.

Popovers are populated with view controller instances. Build the view controller and assign it as the popover’s contentViewController property, before presenting the popover. This allows popovers to present any range of material that you can design into a standard view controller, offering exceptional programming flexibility.

Note

Starting in iOS 5, you can subclass UINavigationBar and incorporate custom presentations into your app’s navigation interfaces. Use the initWithNavigationBarClass:toolbarClass: initialization method.

Developing with Navigation Controllers and Split Views

The UINavigationController class offers one of the most important ways of managing interfaces on a device with limited screen space. It creates a way for users to navigate up and down a hierarchy of interface presentations to create a virtual GUI that’s far larger than the device. Navigation controllers fold their GUIs into a neat tree-based scheme. Users travel through that scheme using buttons and choices that transport them around the tree. You see navigation controllers in the Contacts application and in Settings, where selections lead to new screens and Back buttons move to previous ones.

Several standard GUI elements identify the use of navigation controllers in applications, as seen in Figure 7-1 (left). These include their large navigation bars that appear at the top of each screen, the backward-pointing button at the top-left that appears when the user drills into hierarchies, and option buttons at the top-right that offer other application functionality such as editing. Many navigation controller applications are built around scrolling lists, where elements in that list lead to new screens, indicated by gray and blue chevrons found on the right side of each table cell.

Figure 7-1. The iPhone’s navigation controller uses chevrons to indicate that detail views will be pushed onscreen when their parents are selected. On the iPad, split view controllers use the entire screen, separating navigation elements from detail presentations.

The iPad, with its large screen size, doesn’t require the kind of space-saving shortcuts that navigation controllers leverage on iPhone-family devices. Tablet applications can use navigation controllers directly, but the UISplitViewController shown in Figure 7-1 (right) offers a presentation that’s better suited for the more expansive device.

Notice the differences between the iPhone implementation on the left and the iPad implementation on the right of Figure 7-1. The iPad’s split view controller contains no chevrons. When items are tapped, their data appears on the same screen using the large right-side detail area. The iPhone, lacking this space, presents chevrons that indicate new views will be pushed onscreen. Each approach takes device-specific design into account in its presentation.

Both the iPhone-family and tablet Inbox views use similar navigation controller elements. These include the Back button (iPad Book/Gmail for Book), an options button (Edit, for example), and a status in the title bar (with its one unread message). Each element is created using the navigation controller application programming interface (API) to present a hierarchy of e-mail accounts and mailboxes.

The difference lies at the bottom of the navigation tree, at the level of individual messages that form the leaves of the data structure. The iPhone-family standard uses chevrons to indicate leaves. When selected, these leaf view controllers are pushed onto the navigation stack. They join the other views controllers that trace a user’s progress through the interface. The iPad doesn’t push its leaves. It presents them in a separate view and omits chevrons that otherwise indicate that users have reached the extent of the hierarchy traversal.

iPhone-style navigation controllers play roles as well on the iPad. When iPad applications use standard (iPhone-style) navigation controllers, they usually do so in narrow contexts such as transient popover presentations, where the controller is presented onscreen in a small view with a limited lifetime. Otherwise, iPad applications are encouraged to use the split view approach that occupies the entire screen.

Using Navigation Controllers and Stacks

Every navigation controller owns a root view controller. This controller forms the base of its stack. You can programmatically push other controllers onto the stack as the user makes choices while navigating through the model’s tree. Although the tree itself may be multidimensional, the user’s path (essentially his history) is always a straight line representing the choices already made to date. Moving to a new choice extends the navigation breadcrumb trail and automatically builds a Back button each time a new view controller gets pushed onto the stack.

Users can tap a Back button to pop controllers off the stack. The name of each button represents the title of the most recent view controller. As you return through the stack of previous view controllers, each Back button indicates the view controller that can be returned to. Users can pop back until reaching the root. Then they can go no further. The root is the root, and you cannot pop beyond that root.

This stack-based design lingers even when you plan to use just one view controller. You might want to leverage the UINavigationController’s built-in navigation bar to build a simple utility that uses a two-button menu, for example. This would disregard any navigational advantage of the stack. You still need to set that one controller as the root via initWithRootViewController:.

Pushing and Popping View Controllers

Add new items onto the navigation stack by pushing a new controller with pushViewController:animated:. Each view controller provides a navigationController property. This property points to the navigation controller that this controller is participating in. The property is nil if the controller is not pushed onto a navigation stack.

Use the navigationController property to push a new view controller onto the navigation stack, calling the push method on the navigation controller. When pushed, the new controller slides onscreen from the right (assuming you set animated to YES). A left-pointing Back button appears, leading you one step back on the stack. The Back button uses the title of the previous view controller on the navigation stack.

You would push a new view for many reasons. Typically, these involve navigating to specialty views such as detail views or drilling down a file structure or preferences hierarchy. You can push controllers onto the navigation controller stack after your user taps a button, a table item, or a disclosure accessory.

There’s little reason to ever subclass UINavigationController. Perform push requests and navigation bar customization (such as setting up a bar’s title or buttons) inside UIViewController subclasses. Customization gets passed up to the navigation controller from the child controllers.

For the most part, you don’t access the navigation controller directly. The exceptions to this rule include managing the navigation bar’s buttons, changing the bar’s look, or initializing with a custom navigation bar class. You might change a bar style or its tint color by accessing the navigationBar property directly, as follows:

Click here to view code image
self.navigationController.navigationBar.barStyle =
UIBarStyleBlackTranslucent;

Or you could affect all instances in your application by using an appearance proxy (iOS 5 and later):

[[UINavigationBar appearance] setTintColor:[UIColor purpleColor]];

Bar Buttons

To add new buttons, you modify your navigationItem, which provides a representational class that describes the content shown on the navigation bar, including its left and right bar button items and its title view. Here’s how you can assign a button to the bar. To remove a button, assign the item to nil:

Click here to view code image
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithTitle:@"Action" style:UIBarButtonItemStylePlain target:self
action:@selector(performAction:)];

Bar button items are not views. They are classes that contain titles, styles, and callback information that are used by navigation items and toolbars to build actual buttons in to interfaces. iOS does not provide you with access to the button views built by bar button items and their navigation items.

Starting in iOS 5, you can add multiple bar button items to the left and right. Assign an array to the rightBarButtonItems (notice the s) or leftBarButtonItems properties for the navigation item:

self.navigationItem.rightBarButtonItems = barButtonArray;

Recipe: The Navigation Item Class

The objects that populate the navigation bar are put into place using the UINavigationItem class, which is a class that stores information about those objects. Navigation item properties include the left and right bar button items, the title shown on the bar, the view used to show the title, and any Back button used to navigate back from the current view.

This class enables you to attach buttons, text, and other UI objects into three key locations: the left, the center, and the right of the navigation bar. Typically, this works out to be a regular button on the right, some text (usually the UIViewController’s title) in the middle, and a Back-styled button on the left. But you’re not limited to that layout. You can add custom controls to any of three locations: the left, the center (title area), and the right. You might build navigation bars with search fields in the middle instead, or segment controls, toolbars, pictures, and more. Further, you can add multiple items to the left and right button arrays. It’s all easy to modify.

Titles and Back Buttons

The central title area is especially customizable. You can assign a title to the navigation item like this:

self.navigationItem.title = @"My Title"

This is equivalent to setting the view controller’s title property directly. The simplest way to customize the actual title is to use the title property of the child view controller rather than the navigation item, although some developers feel this is not a best practice:

self.title = @"Hello";

When assigned, the navigation controller uses the title to establish the Back button’s “go back” text. If you push a new controller on top of a controller titled "Hello", the Back button’s indicates that it links back to "Hello".

You could also replace the text-based title with a custom view like a control. This code adds a custom segmented control, but this could be an image view, a stepper, or anything else:

Click here to view code image
self.navigationItem.titleView =
[[UISegmentedControl alloc] initWithItems:items];

Macros

Macros simplify your work when building bar buttons because the creation task is so repetitive. The following macro creates a basic button item:

Click here to view code image

#define BARBUTTON(TITLE, SELECTOR) [[UIBarButtonItem alloc] \
initWithTitle:TITLE style:UIBarButtonItemStylePlain \
target:self action:SELECTOR]

You supply it with a title and a selector to call. Each call to this macro specifies the tile and selector, tightening up the code’s readability.

Click here to view code image
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Push", @selector(push:));

This version of the macro assumes the target is "self", which is quite common, although you could easily adapt this. The following macro adds a target that you specify:

Click here to view code image

#define BARBUTTON_TARGET(TITLE, TARGET, SELECTOR) \
[[UIBarButtonItem alloc] initWithTitle:TITLE \
style:UIBarButtonItemStylePlain target:TARGET action:SELECTOR]

The vocabulary of bar buttons you use varies by your particular application demands. It’s easy to create macros for system items provided by Apple, image items created from picture resources, and custom view items, which can embed controls and other non-bar button elements.

Recipe 7-1 combines these features to demonstrate how controller titles and navigation items build together during drilling. It offers a super-simple interface: You select the title for the next item you want to push onto the navigation stack, and then push it on. This allows you to see how the navigation controller stack grows using default behavior.

Recipe 7-1. Basic Navigation Drilling

Click here to view code image

// Push a new controller onto the stack
- (void) push: (id) sender
{
NSString *newTitle = [@"Foo*Bar*Baz*Qux"
componentsSeparatedByString:@"*"][seg.selectedSegmentIndex];

UIViewController *newController = [[TestBedViewController alloc] init];
newController.title = newTitle;

[self.navigationController
pushViewController:newController animated:YES];
}

- (void) loadView
{
[super loadView];

// Establish a button to push new controllers
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Push", @selector(push:));

// Create a segmented control to pick the next title
seg = [[UISegmentedControl alloc] initWithItems:
[@"Foo*Bar*Baz*Qux" componentsSeparatedByString:@"*"]];
seg.selectedSegmentIndex = 0;
[self.view addSubview:seg];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Modal Presentation

With normal navigation controllers, you push your way along views, stopping occasionally to pop back to previous views. That approach assumes that you’re drilling your way up and down a set of data that matches the tree-based view structure you’re using. Modal presentation offers another way to show a view controller.

After you send the presentViewController:animated:completion: message to a view controller, a new view controller appears on the screen and takes control until it’s dismissed with dismissViewControllerAnimated:completion:. This enables you to add special-purpose dialogs into your applications that go beyond alert views.

Typically, modal controllers prompt users to pick data such as contacts from the Address Book or photos from the Library or perform a short-lived task such as sending e-mail or setting preferences. Use modal controllers in any setting where it makes sense to perform a limited-time task that lies outside the normal scope of the active view controller.

Modal presentations can use four transition styles:

• Slide—This transition style slides a new view over the old.

• Fade—This transition style dissolves the new view into visibility.

• Flip—This transition style turns a view over to the “back” of the presentation.

• Curl—This transition style makes the primary view curl up out of the way to reveal the new view beneath it.

These styles are set by the modalTransitionStyle property of the presented view controller. The standard, UIModalTransitionStyleCoverVertical, slides the modal view up and over the current view controller. When dismissed it slides back down. UIModalTransitionStyleFlipHorizontal performs a back-to-front flip from right to left. It looks as if you’re revealing the back side of the currently presented view. When dismissed, it flips back left to right. UIModalTransitionStyleCrossDissolve fades the new view in over the previous one. On dismissal, it fades back to the original view. Use UIModalTransitionStylePartialCurl to curl up content (in the way the Maps application does) to reveal a modal settings view “underneath” the primary view controller.

On the iPhone and iPod touch, modal controllers always fully take over the screen. The iPad offers more nuanced presentations. The iPad offers three presentation styles:

• Full Screen—A full-screen presentation is the default on the iPhone, where the new modal view completely covers both the screen and any existing content. This is the only presentation style that is legal for curls; any other presentation style raises a runtime exception, crashing the application.

• Page Sheet—In the page sheet, coverage defaults to a portrait aspect ratio, so the modal view controller completely covers the screen in portrait mode and partially covers the screen in landscape mode, as if a portrait-aligned piece of paper were added to the display.

• Form Sheet—The form sheet display covers a small center portion of the screen, allowing you to shift focus to the modal element while retaining the maximum visibility of the primary application view.

In addition to the default full-screen style (UIModalPresentationFullScreen), use UIModalPresentationFormSheet to present a small overlay in the center of the screen or UIModalPresentationPageSheet to slide up a sheet in the middle of the screen. These styles are best experienced in landscape mode to visually differentiate the page sheet presentation from the full-screen one.

Presenting a Custom Modal Information View

Presenting a modal controller branches off from your primary navigation path, introducing a new interface that takes charge until your user explicitly dismisses it. You present a modal controller like this:

[self presentViewController:someControllerInstance animated:YES completion:nil];

The controller that is presented can be any kind of view controller subclass, as well. In the case of a navigation controller, the modal presentation can have its own navigation hierarchy built as a chain of interactions. Use the completion block to finish up any tasks you need to perform after the view controller has animated into place.

Always provide a Done option of some kind to allow users to dismiss the controller. The easiest way to accomplish this is to present a navigation controller, adding a bar button to its navigation items.

Click here to view code image

- (IBAction)done:(id)sender
{
[self dismissViewControllerAnimated:YES completion:nil];
}

Storyboards simplify the creation of modal controller elements. Drag in a navigation controller instance, along with its paired view controller, adding a Done button to the provided navigation bar. Set the view controller’s class to your custom modal type and connect the Done button to the done: method. Name your navigation controller in the attributes inspector so that you can use that identifier to load it.

You can either add the modal components to your primary storyboard or create them in a separate file. Recipe 7-2 loads a custom file (Modal~DeviceType.storyboard) but you can just as easily add the elements in your MainStoryboard_DeviceType file.

Recipe 7-2 offers the key pieces for creating modal elements. The presentation is performed in the application’s main view controller hierarchy. Here, users select the transition and presentation styles from segmented controls, but these are normally chosen in advance by the developer and set in code or in Interface Builder (IB). This recipe offers a toolbox that you can test out on each platform, using each orientation to explore how each option looks.

Recipe 7-2. Presenting and Dismissing a Modal Controller

Click here to view code image

// Presenting the controller
- (void) action: (id) sender
{
// Load info controller from storyboard
UIStoryboard *storyBoard = [UIStoryboard
storyboardWithName:
(IS_IPAD ? @"Modal~iPad" : @"Modal~iPhone")
bundle:[NSBundle mainBundle]];
UINavigationController *navController =
[storyBoard instantiateViewControllerWithIdentfier:
@"infoNavigationController"];

// Select the transition style
int styleSegment =
[(UISegmentedControl *)self.navigationItem.titleView
selectedSegmentIndex];
int transitionStyles[4] = {
UIModalTransitionStyleCoverVertical,
UIModalTransitionStyleCrossDissolve,
UIModalTransitionStyleFlipHorizontal,
UIModalTransitionStylePartialCurl};
navController.modalTransitionStyle = transitionStyles[styleSegment];

// Select the presentation style for iPad only
if (IS_IPAD)
{
int presentationSegment =
[(UISegmentedControl *)[[self.view subviews]
lastObject] selectedSegmentIndex];
int presentationStyles[3] = {
UIModalPresentationFullScreen,
UIModalPresentationPageSheet,
UIModalPresentationFormSheet};

if (navController.modalTransitionStyle ==
UIModalTransitionStylePartialCurl)
{
// Partial curl with any non-full-screen presentation
// raises an exception
navController.modalPresentationStyle =
UIModalPresentationFullScreen;
[(UISegmentedControl *)[[self.view subviews]
lastObject] setSelectedSegmentIndex:0];
}
else
navController.modalPresentationStyle =
presentationStyles[presentationSegment];
}

[self.navigationController presentViewController:navController
animated:YES completion:nil];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Action", @selector(action:));

UISegmentedControl *segmentedControl =
[[UISegmentedControl alloc] initWithItems:
[@"Slide Fade Flip Curl" componentsSeparatedByString:@" "]];
segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;
self.navigationItem.titleView = segmentedControl;

if (IS_IPAD)
{
NSArray *presentationChoices =
[NSArray arrayWithObjects:
@"Full Screen", @"Page Sheet", @"Form Sheet", nil];
UISegmentedControl *iPadStyleControl =
[[UISegmentedControl alloc] init
WithItems:presentationChoices];
iPadStyleControl.segmentedControlStyle =
UISegmentedControlStyleBar;
iPadStyleControl.autoresizingMask =
UIViewAutoresizingFlexibleWidth;
iPadStyleControl.center =
CGPointMake(CGRectGetMidX(self.view.bounds), 22.0f);
[self.view addSubview:iPadStyleControl];
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Building Split View Controllers

Split view controllers provide the preferred way to present hierarchically driven navigation on the iPad. They generally consist of a table of contents on the left and a detail view on the right, although the class (and Apple’s guidelines) is not limited to this presentation style. The heart of the class consists of the notion of an organizing section (master) and a presentation section (detail), both of which can appear onscreen simultaneously in landscape orientation, and whose organizing section optionally converts to a popover in portrait orientation. (You can override this default behavior by implementing splitViewController:shouldHideViewController:inOrientation: in your delegate, letting your split view show both sections in portrait mode.)

Figure 7-2 shows the very basic split view controller built by Recipe 7-3 in landscape and portrait orientations. This controller sets the color of the detail view by selecting an item from the list in the root view. In landscape, both views are shown at once. In portrait orientation, the user must tap the upper-left button on the detail view to access the root view as a popover or use an optional swipe gesture. When programming for this orientation, be aware that the popover can interfere with detail view, as it is presented over that view; design accordingly.

Figure 7-2. At their simplest, split view controllers consist of an organizing pane and a detail view pane. The organizing pane, which you see in this figure, is normally hidden in portrait orientation. Users view it via a popover accessed from a navigation bar button or invoke it with a swipe gesture.

You code builds three separate objects: the master and detail view controllers, and the split view controller that owns the first two. The split view controller always contains two children, the master at index 0 and the detail at index 1.

You’ll want to add the master and detail controllers to navigation controller shells, to provide a consistent interface. In the case of the detail controller, this provides a home for the bar button in portrait orientation. The following method builds the two child views, embeds them into navigation controllers, adds them to a view controller array, and returns a new split view controller that hosts those views:

Click here to view code image

- (UISplitViewController *) splitViewController
{
// Create the navigation-run root (master) view
ColorTableViewController *rootVC = [[ColorTableViewController alloc] init];
rootVC.title = @"Colors"; // make sure to set the title
UINavigationController *rootNav = [[UINavigationController alloc]
initWithRootViewController:rootVC];

// Create the navigation-run detail view
DetailViewController *detailVC = [DetailViewController controller];
UINavigationController *detailNav = [[UINavigationController alloc]
initWithRootViewController:detailVC];

// Add both to the split view controller
UISplitViewController *svc =
[[UISplitViewController alloc] init];
svc.viewControllers = @[rootNav, detailNav];
svc.delegate = detailVC;

return svc;
}

The master view controller is often some kind of table view controller, as is the one in Recipe 7-3. What you see here is pretty much as bare bones as tables get. It is a list of color items (specifically, UIColor method names), each one with a cell title that is tinted to match that color.

When an item is selected, the controller uses its built-in splitViewController property to send a request to its detail view. This property returns the split view controller that owns the root view. From there, the controller can retrieve the split view’s delegate, which has been assigned to the detail view. By casting that delegate to the detail view controller’s class, the root view can affect the detail view more meaningfully. In this extremely simple example, the selected cell’s text tint is applied to the detail view’s background color.

Note

Make sure you set the root view controller’s title property. It is used to set the text for the button that appears in the detail view during portrait mode.

Recipe 7-3’s DetailViewController class is about as skeletal an implementation as you can get. It provides the most basic functionality you need to provide a detail view implementation with split view controllers. This consists of the will-hide/will-show method pair that adds and hides that all-important bar button for the detail view.

When the split view controller converts the master view controller into a popover controller in portrait orientation, it passes that new controller to the detail view controller. It is the detail controller’s job to retain and handle that popover until the interface returns to landscape orientation. In this skeletal class definition, a strong property holds onto the popover for the duration of portrait interaction.

Recipe 7-3. Building Detail and Master Views for a Split View Controller

Click here to view code image

@interface DetailViewController : UIViewController
<UIPopoverControllerDelegate, UISplitViewControllerDelegate>
{
UIPopoverController *popoverController;
}
@property (nonatomic, strong) UIPopoverController *popoverController;
@end

@implementation DetailViewController
+ (id) controller
{
DetailViewController *controller =
[[DetailViewController alloc] init];
controller.view.backgroundColor = [UIColor blackColor];
return controller;
}

// Called upon going into portrait mode, hiding the normal table view
- (void)splitViewController: (UISplitViewController*)svc
willHideViewController:(UIViewController *)aViewController
withBarButtonItem:(UIBarButtonItem*)barButtonItem
forPopoverController: (UIPopoverController*)aPopoverController
{
barButtonItem.title = aViewController.title;
self.navigationItem.leftBarButtonItem = barButtonItem;
self.popoverController = aPopoverController;
}

// Called upon going into landscape mode.
- (void)splitViewController: (UISplitViewController*)svc
willShowViewController:(UIViewController *)aViewController
invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
self.navigationItem.leftBarButtonItem = nil;
self.popoverController = nil;
}

// Use this to avoid the popover hiding in portrait.
// When omitted, you get the default behavior.
/* - (BOOL)splitViewController:(UISplitViewController *)svc
shouldHideViewController:(UIViewController *)vc
inOrientation:(UIInterfaceOrientation)orientation
{
return NO;
}*/
@end

@interface ColorTableViewController : UITableViewController
@end
@implementation ColorTableViewController
+ (id) controller
{
ColorTableViewController *controller =
[ [ColorTableViewController alloc] init];
controller.title = @"Colors";
return controller;
}

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

- (NSArray *) selectors
{
return @[@"blackColor", @"redColor", @"greenColor", @"blueColor",
@"cyanColor", @"yellowColor", @"magentaColor", @"orangeColor",
@"purpleColor", @"brownColor"];
}

- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
return [self selectors].count;
}

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"generic"];
if (!cell) cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:@"generic"];

// Set title and color
NSString *item = [self selectors][indexPath.row];
cell.textLabel.text = item;
cell.textLabel.textColor =
[UIColor performSelector:NSSelectorFromString(item) withObject:nil];

return cell;
}

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// On selection, update the main view background color
UINavigationController *nav =
[self.splitViewController.viewControllers lastObject];
UIViewController *controller = [nav topViewController];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
controller.view.backgroundColor = cell.textLabel.textColor;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Creating Universal Split View/Navigation Apps

Recipe 7-4 modifies Recipe 7-3’s split view controller to provide a functionally equivalent application that runs properly on both iPhone and iPad platforms. Accomplishing this takes several steps that add to Recipe 7-3’s code base. You do not have to remove functionality from the split view controller approach, but you must provide alternatives in several places.

Recipe 7-4 uses a macro to determine whether the code is being run on an iPad- or iPhone-style device. It leverages the user interface idiom as follows:

#define IS_IPAD (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)

This macro returns YES when the device characteristics are iPad-like, rather than being iPhone-like (such as on the iPhone or iPod touch). First introduced in iOS 3.2, which introduced the iPad as a new hardware platform, idioms allow you to perform runtime checks in your code to provide interface choices that match the deployed platform.

In an iPhone deployment, the detail view controller remains code identical to Recipe 7-3, but to be displayed it must be pushed onto the navigation stack rather than shown side by side in a split view. The navigation controller is set up as the primary view for the application window rather than the split view. A simple check at application launch lets your code choose which approach to use:

Click here to view code image

- (UINavigationController *) navWithColorTableViewController
{
ColorTableViewController *rootVC =
[[ColorTableViewController alloc] init];
rootVC.title = @"Colors";
UINavigationController *nav = [[UINavigationController alloc]
initWithRootViewController: rootVC];
return nav;
}

- (void)applicationDidFinishLaunching:(UIApplication *)application
{
window = [[UIWindow alloc] initWithFrame:
[[UIScreen mainScreen] bounds]];

if (IS_IPAD)
window.rootViewController = [self splitviewController];
else
window.rootViewController = [self navWithColorTableViewController];

[window addSubview:mainController.view];
[window makeKeyAndVisible];
}

The rest of the story lies in the two methods of Recipe 7-4, within the color-picking table view controller. Two key checks decide whether to show disclosure accessories and how to respond to table taps:

• On the iPad, disclosure indicators should never be used at the last level of detail presentation. On the iPhone, they indicate that a new view will be pushed on selection. Checking for deployment platform lets your code choose whether to include these accessories in cells.

• When you’re working with the iPhone, there’s no option for using split views, so your code must push a new detail view onto the navigation controller stack. Compare this to the iPad code, which only needs to reach out to an existing detail view and update its background color.

In real-world deployment, these two checks would likely expand in complexity beyond the details shown in this simple recipe. You’d want to add a check to your model to determine if you are, indeed, at the lowest level of the tree hierarchy before suppressing disclosure accessories. Similarly, you may need to update or replace presentations in your detail view controller.

Recipe 7-4. Adding Universal Support for Split View Alternatives

Click here to view code image

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"generic"];
if (!cell) cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier:@"generic"];

NSString *item = [self selectors][indexPath.row];
cell.textLabel.text = item;
cell.textLabel.textColor =
[UIColor performSelector:NSSelectorFromString(item) withObject:nil];

cell.accessoryType = IS_IPAD ?
UITableViewCellAccessoryNone :
UITableViewCellAccessoryDisclosureIndicator;

return cell;
}

- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];

if (IS_IPAD)
{
UINavigationController *nav =
[self.splitViewController.viewControllers lastObject];
UIViewController *controller = [nav topViewController];
controller.view.backgroundColor = cell.textLabel.textColor;
}
else
{
DetailViewController *controller = [
DetailViewController controller];
controller.view.backgroundColor = cell.textLabel.textColor;
controller.title = cell.textLabel.text;

[self.navigationController
pushViewController:controller animated:YES];
}
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Tab Bars

On iPhone-like devices, the UITabBarController class allows users to move between multiple view controllers and to customize the bar at the bottom of the screen. This is best seen in the music application. It offers one-tap access to different views and a More button leading to user selection and editing of the bottom bar. Tab bars are not recommended for use as a primary design pattern on the iPad, although Apple supports their use when needed, especially in split views and popovers.

With tab bars, you don’t push views the way you do with navigation bars. Instead, you assemble a collection of controllers (they can individually be UIViewControllers, UINavigationControllers, or any other kind of view controllers) and add them into a tab bar by setting the bar’s viewControllers property. Cocoa Touch does all the rest of the work for you. Set allowsCustomizing to YES to enable end-user reordering of the bar.

Recipe 7-5 creates 11 simple view controllers of the BrightnessController class. This class sets its background to a specified gray level—in this case, from 0% to 100% in steps of 10%. Figure 7-3 (left) shows the interface in its default mode, with the first four items and a More button displayed.

Figure 7-3. Tab bar controllers allow users to pick view controllers from a bar at the bottom of the screen (left side of the figure) and to customize the bar from a list of available view controllers (right side of the figure).

Users may reorder tabs by selecting the More option and then tapping Edit. This opens the configuration panel shown in Figure 7-3 (right). These 11 view controllers offer the options a user can navigate through and select from. Readers of the early editions of this book might note that the Configure title bar’s tint matches the rest of the interface. Apple introduced the UIAppearance protocol, which allows you to customize all instances of a given class. Recipe 7-5 uses this functionality to tint its navigation bars black:

[[UINavigationBar appearance] setTintColor:[UIColor blackColor]];

This recipe adds its 11 controllers twice. The first time it assigns them to the list of view controllers available to the user:

tbarController.viewControllers = controllers;

The second time it specifies that the user can select from the entire list when interactively customizing the bottom tab bar:

tbarController.customizableViewControllers = controllers;

The second line is optional; the first is mandatory. After setting up the view controllers, you can add all or some to the customizable list. If you don’t, you still can see the extra view controllers using the More button, but users won’t be able to include them in the main tab bar on demand.

Tab art appears inverted in color on the More screen. According to Apple, this is the expected and proper behavior. They have no plans to change this. It does provide an interesting view contrast when your 100% white swatch appears as pure black on that screen.

Recipe 7-5. Creating a Tab Bar View Controller

Click here to view code image

@interface BrightnessController : UIViewController
{
int brightness;
}
@end

@implementation BrightnessController
// Create a swatch for the tab icon using standard Quartz
// and UIKit image calls
- (UIImage*) buildSwatch: (int) aBrightness
{
CGRect rect = CGRectMake(0.0f, 0.0f, 30.0f, 30.0f);
UIGraphicsBeginImageContext(rect.size);
UIBezierPath *path = [UIBezierPath
bezierPathWithRoundedRect:rect cornerRadius:4.0f];
[[[UIColor blackColor]
colorWithAlphaComponent:(float) aBrightness / 10.0f] set];
[path fill];

UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return image;
}

// The view controller consists of a background color
// and a tab bar item icon
-(BrightnessController *) initWithBrightness: (int) aBrightness
{
self = [super init];
brightness = aBrightness;
self.title = [NSString stringWithFormat:@"%d%%", brightness * 10];
self.tabBarItem = [[UITabBarItem alloc] initWithTitle:self.title
image:[self buildSwatch:brightness] tag:0];
return self;
}

// Tint the background
- (void) viewDidLoad
{
[super viewDidLoad];
self.view.backgroundColor =
[UIColor colorWithWhite:(brightness / 10.0f) alpha:1.0f];
}

+ (id) controllerWithBrightness: (int) brightness
{
BrightnessController *controller = [[BrightnessController alloc]
initWithBrightness:brightness];
return controller;
}
@end

#pragma mark Application Setup
@interface TestBedAppDelegate : NSObject
<UIApplicationDelegate, UITabBarControllerDelegate>
{
UIWindow *window;
UITabBarController *tabBarController;
}
@end

@implementation TestBedAppDelegate
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
[application setStatusBarHidden:YES];
window = [[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen] bounds]];

// Globally use a black tint for nav bars
[[UINavigationBar appearance]
setTintColor:[UIColor blackColor]];

// Build an array of controllers
NSMutableArray *controllers = [NSMutableArray array];
for (int i = 0; i <= 10; i++)
{
BrightnessController *controller =
[BrightnessController controllerWithBrightness:i];
UINavigationController *nav =
[[UINavigationController alloc]
initWithRootViewController:controller];
nav.navigationBar.barStyle = UIBarStyleBlackTranslucent;
[controllers addObject:nav];
}

tabBarController = [[RotatingTabController alloc] init];
tabBarController.viewControllers = controllers;
tabBarController.customizableViewControllers = controllers;
tabBarController.delegate = self;

window.rootViewController = tabBarController;
[window makeKeyAndVisible];
return YES;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Remembering Tab State

On iOS, persistence is golden. When starting or resuming your application from termination or interruption, always return users to a state that closely matches where they left off. This lets your users pick up with whatever tasks they were involved with and provides a user interface that matches the previous session. Listing 7-1 introduces an example of doing exactly that.

This update to Recipe 7-5 stores both the current tab order and the currently selected tab, and does so whenever those items are updated. When a user launches the application, the code searches for previous settings and applies them when they are found.

To respond to updates, a tab bar delegate must declare the UITabBarControllerDelegate protocol. The approach used here depends on two delegate methods. The first, tabBarController:didEndCustomizingViewControllers:, provides the current array of view controllers after the user has customized them with the More > Edit screen. This code captures their titles (10%, 20%, and so on) and uses that information to relate a name to each view controller.

The second delegate method is tabBarController:didSelectViewController:. The tab bar controller sends this method each time a user selects a new tab. By capturing the selectedIndex, this code stores the controller number relative to the current array.

In this example, setting these values depends on using iOS’s built-in user defaults system, NSUserDefaults. This preferences system works very much as a large mutable dictionary. You can set values for keys using setObject:forKey:, as shown here:

Click here to view code image

[[NSUserDefaults standardUserDefaults] setObject:titles
forKey:@"tabOrder"];

Then you can retrieve them with objectForKey:, like so:

Click here to view code image

NSArray *titles = [[NSUserDefaults standardUserDefaults]
objectForKey:@"tabOrder"];

Synchronizing your settings ensures that the stored defaults dictionary matches your changes.

When the application launches, it checks for previous settings for the last selected tab order and selected tab. If it finds them, it uses these to set up the tabs and select a tab to make active. Because the titles contain the information about what brightness value to show, this code converts the stored title from text to a number and divides that number by ten to send to the initialization function.

Most applications aren’t based on such a simple numeric system. Should you use titles to store your tab bar order, make sure you name your view controllers meaningfully and in a way that lets you match a view controller with the tab ordering.

Note

You could also store an array of the view tags as NSNumbers or, better yet, use the NSKeyedArchiver class or iOS’s new restorable state coding. Archiving lets you rebuild views using state information that you store on termination.

Listing 7-1. Storing Tab State to User Defaults

Click here to view code image

@implementation TestBedAppDelegate
- (void)tabBarController:(UITabBarController *)tabBarController
didEndCustomizingViewControllers:(NSArray *)viewControllers
changed:(BOOL)changed
{
// Collect the view controller order
NSMutableArray *titles = [NSMutableArray array];
for (UIViewController *vc in viewControllers)
[titles addObject:vc.title];

[[NSUserDefaults standardUserDefaults]
setObject:titles forKey:@"tabOrder"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

- (void)tabBarController:(UITabBarController *)controller
didSelectViewController:(UIViewController *)viewController
{
// Store the selected tab
NSNumber *tabNumber = [NSNumber numberWithInt:
[controller selectedIndex]];
[[NSUserDefaults standardUserDefaults]
setObject:tabNumber forKey:@"selectedTab"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[application setStatusBarHidden:YES];
window = [[UIWindow alloc]
initWithFrame:[[UIScreen mainScreen] bounds]];

// Globally use a black tint for nav bars
[[UINavigationBar appearance] setTintColor:[UIColor blackColor]];

NSMutableArray *controllers = [NSMutableArray array];
NSArray *titles = [[NSUserDefaults standardUserDefaults]
objectForKey:@"tabOrder"];

if (titles)
{
// titles retrieved from user defaults
for (NSString *theTitle in titles)
{
BrightnessController *controller =
[BrightnessController controllerWithBrightness:
([theTitle intValue] / 10)];
UINavigationController *nav =
[[UINavigationController alloc]
initWithRootViewController:controller];
nav.navigationBar.barStyle = UIBarStyleBlackTranslucent;
[controllers addObject:nav];
}
}
else
{
// generate all new controllers
for (int i = 0; i <= 10; i++)
{
BrightnessController *controller =
[BrightnessController controllerWithBrightness:i];
UINavigationController *nav =
[[UINavigationController alloc]
initWithRootViewController:controller];
nav.navigationBar.barStyle = UIBarStyleBlackTranslucent;
[controllers addObject:nav];
}
}

tabBarController = [[RotatingTabController alloc] init];
tabBarController.viewControllers = controllers;
tabBarController.customizableViewControllers = controllers;
tabBarController.delegate = self;

// Restore any previously selected tab
NSNumber *tabNumber = [[NSUserDefaults standardUserDefaults]
objectForKey:@"selectedTab"];
if (tabNumber)
tabBarController.selectedIndex = [tabNumber intValue];

window.rootViewController = tabBarController;
[window makeKeyAndVisible];
return YES;
}
@end

Recipe: Page View Controllers

This UIPageViewController class builds a book-like interface that uses individual view controllers as its pages. Users swipe from one page to the next or tap the edges to move to the next or previous page. You can create a book-looking layout with pages, as shown in Figure 7-4 (top), or use a flat scrolling presentation, as shown in Figure 7-4 (bottom). The scrolling presentation offers an optional page indicator presentation, which is shown here at the bottom of the view.

Figure 7-4. The UIPageViewController class creates virtual “books” from individual view controllers. View your books in paged (top) or scrolling (bottom) presentations.

All a controller’s pages can be laid out in a similar fashion, such as in Figure 7-4, or each page can provide a unique user interaction experience. Apple precooked all the animation and gesture handling into the class for you. You provide the content, implementing delegate and data source callbacks.

Book Properties

Your code customizes a page view controller’s look and behavior. Key properties specify how many pages display simultaneously, the content used for the reverse side of each page, and more. Here’s a rundown of those Apple-specified properties:

• The controller’s doubleSided property determines whether content appears on both sides of a page, as shown in Figure 7-4 (top), or just one side (bottom). Reserve the double-sided presentation for side-by-side layout when showing two pages simultaneously. If you don’t, you’ll end up making half your pages inaccessible. The controllers on the “back” of the pages will never move into the primary viewing space. The book layout is controlled by the book’s spine.

• The spineLocation property can be set at the left or right, top or bottom, or center of the page. The three spine constants are UIPageViewControllerSpineLocationMin, corresponding to top or left, UIPageViewControllerSpineLocationMax for the right or bottom, and UIPageViewControllerSpineLocationMid for the center. The first two of these produce single-page presentations; the last with its middle spine is used for two-page layouts. Return one of these choices from the pageViewController:spineLocationForInterfaceOrientation: delegate method, which is called whenever the device reorients, to let the controller update its views to match the current device orientation.

• Set the navigationOrientation property to specify whether the spine goes left/right or top/bottom. Use either UIPageViewControllerNavigationOrientationHorizontal (left/right) or UIPageViewControllerNavigationOrientationVertical (top/bottom). For a vertical book, the pages flip up and down, rather than employing the left and right flips normally used.

• The transitionStyle property controls how one view controller transitions to the next. At the time of writing, the only transition styles supported by the page view controller are the page curl, UIPageViewControllerTransitionStylePageCurl, and the scrolling presentation UIPageViewControllerTransitionStyleScroll. This latter style is new to iOS 6.

Wrapping the Implementation

Like table views, page view controllers use a delegate and data source to set the behavior and contents of its presentation. Unlike with table views, I have found that it’s simplest to wrap these items into a custom class to hide their details from my applications. Apple notes that it does not recommend subclassing page view controllers; I’ll courteously stand by my approach here. I find the code needed to support a page view implementation rather quirky—but highly reusable. A wrapper lets you turn your attention away from fussy coding details to specific content-handling concerns.

In the standard implementation, the data source is responsible for providing page controllers on demand. It returns the next and previous view controller in relationship to a given one. The delegate handles reorientation events and animation callbacks, setting the page view controller’s controller array, which always consists of either one or two controllers, depending on the view layout. As Recipe 7-6 demonstrates, it’s a bit of a mess to implement, but once implemented it’s something you really don’t need to spend much time coming back to.

Recipe 7-6 creates a BookController class. This class numbers each page, hiding the next/previous implementation details and handling all reorientation events. A custom delegate protocol (BookDelegate) becomes responsible for returning a controller for a given page number when sent the viewControllerForPage: message. This simplifies implementation so that the calling app only has to handle a single method, which it can do by building controllers by hand or by pulling them from a storyboard.

To use the class defined in Recipe 7-6, you establish the controller, add it as a subview, and declare it as a child view controller, ensuring it receives orientation and memory events. Here’s what that code might look like:

Click here to view code image

- (void) viewWillAppear:(BOOL)animated
{
if (!bookController)
bookController = [BookController bookWithDelegate:self
style:BookLayoutStyleBook];
bookController.view.frame = self.view.bounds;

[self addChildViewController:bookController];
[self.view addSubview:bookController.view];
[bookController didMoveToParentViewController:self];

[bookController moveToPage:0];
}

Notice how the new controller is added as a child to the parent controller in the viewWillAppear: method, and its initial page number set. In this edition, the book controller creation convenience method also takes a second argument: a style. I updated Recipe 7-6 to allow developers to build four styles of books: a traditional book, a vertical book, and two scrolling styles:

Click here to view code image
typedef enum
{
BookLayoutStyleBook, // side by side in landscape
BookLayoutStyleFlipBook, // side by side in portrait
BookLayoutStyleHorizontalScroll,
BookLayoutStyleVerticalScroll,
} BookLayoutStyle;

The standard book presents one page in portrait (spine vertical and to the left) and a side-by-side presentation in landscape (spine vertical in the middle). This corresponds to a standard Western-style book, with page movement going left to right.

The “flip”-style book uses a horizontal spine. In landscape mode, the spine is at the top, with one page shown at a time. In portrait, I extend that to two pages, with the horizontal spine in the middle, halfway between top and bottom.

The two scroll layouts allow you to scroll horizontally and vertically through individual pages. You cannot use multipage (side-by-side) layout with scrolling.

The converse of this process is as follows, allowing the book controller to retire from its superview:

Click here to view code image

- (void) viewWillDisappear:(BOOL)animated
{
[bookController willMoveToParentViewController:nil];
[bookController.view removeFromSuperview];
[bookController removeFromParentViewController];
}

Exploring the Recipe

Recipe 7-6 handles its delegate and data source duties by tagging each view controller’s view with a page number. It uses this number to know exactly which page is presented at any time and to delegate another class, the BookDelegate, to produce a view controller by index.

The page controller itself always stores zero, one, or two pages in its view controller array. Zero pages means the controller has not yet been properly set up. One page is used for spine locations on the edge of the screen, two pages for a central spine. If the page count does not exactly match the spine setup, you will encounter a rather nasty runtime crash.

The controllers stored in those pages are produced by the two data source methods, which implement the before and after callbacks. In the page controller’s native implementation, controllers are defined strictly by their relationship to each other, not by an index. This recipe replaces those relationships with a simple number, asking its delegate for the page at a given index.

Here, the useSideBySide: method decides where to place the spine, and thus how many controllers show simultaneously. This implementation sets landscape as side by side and portrait as one page. You may want to change this for your applications. For example, you might use only one page on the iPhone, regardless of orientation, to enhance text readability.

Recipe 7-6 allows both user- and application-based page control. Users can swipe and tap to new pages or the application can send a moveToPage: request. This allows you to add external controls in addition to the page view controller’s gesture recognizers.

The direction that the page turns is set by comparing the new page number against the old. This recipe uses a Western-style page turn, where higher numbers are to the right and pages flip to the left. You may want to adjust this as needed for countries in the Middle and Far East.

This recipe, as shown here, continually stores the current page to system defaults, so it can be recovered when the application is relaunched. It will also notify its delegate when the user has turned to a given page, which is useful if you add a page slider, as is demonstrated in Recipe 7-7.

Building a Presentation Index
iOS 6’s new scrolling layouts allow you to add an optional index (via a page control). Any book using the scrolling layout style (UIPageViewControllerTransitionStyleScroll) can implement two data source methods, as follows. When found, iOS uses these to build the indicator at the bottom of the scrolling book that you saw in Figure 7-4 (bottom).

As you can see from this snippet, the implementation in the early iOS 6 beta was a bit wobbly. Returning 0 from the presentation index and the number of pages for the presentation count produced the most stable indicator. The page count used here is deferred to the book’s delegate, via an optional method called numberOfPages:

Click here to view code image

- (NSInteger)presentationIndexForPageViewController:
(UIPageViewController *)pageViewController
{
// Slightly borked in iOS 6 beta
// return [self currentPage];
return 0;
}

- (NSInteger)presentationCountForPageViewController:
(UIPageViewController *)pageViewController
{
if (bookDelegate &&
[bookDelegate respondsToSelector:@selector(numberOfPages)])
return [bookDelegate numberOfPages];

return 0;
}

Note that you are not limited to a one-to-one correlation between your index and your page count and current page number. For a large book, you can imagine dividing this number down somewhat, so each page dot corresponds to five or ten pages, showing progress through the book without an exact page correspondence.

Note

Apple enables you to access a page view controller’s gesture recognizers to allow or disallow touch-based page turns based on a touch’s location on a page. I recommend against doing so. First, this approach is not valid for scroll-based controllers. Second, adding recognizer delegate methods tends to mess up app stability.

Recipe 7-6. Creating a Page View Controller Wrapper

Click here to view code image

// Define a custom delegate protocol for this wrapper class
@protocol BookControllerDelegate <NSObject>
- (id) viewControllerForPage: (int) pageNumber;
@optional
- (NSInteger) numberOfPages; // for scrolling layouts
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber;
@end

// A book controller wraps the page view controller
@interface BookController : UIPageViewController
<UIPageViewControllerDelegate, UIPageViewControllerDataSource>
+ (id) bookWithDelegate: (id) theDelegate style: (BookLayoutStyle) aStyle;
- (void) moveToPage: (uint) requestedPage;
- (int) currentPage;

@property (nonatomic, weak) id <BookControllerDelegate> bookDelegate;
@property (nonatomic, assign) uint pageNumber;
@property (nonatomic) BookLayoutStyle layoutStyle;
@end

#pragma Book Controller
@implementation BookController

#pragma mark Utility
// Page controllers are numbered using tags
- (int) currentPage
{
int pageCheck = ((UIViewController *)[self.viewControllers
objectAtIndex:0]).view.tag;
return pageCheck;
}

#pragma mark Page Handling
// Update if you'd rather use some other decision style
- (BOOL) useSideBySide: (UIInterfaceOrientation) orientation
{
BOOL isLandscape = UIInterfaceOrientationIsLandscape(orientation);

// Each layout style controls whether side by side is used
switch (layoutStyle)
{
case BookLayoutStyleHorizontalScroll:
case BookLayoutStyleVerticalScroll: return NO;
case BookLayoutStyleFlipBook: return !isLandscape;
default: return isLandscape;
}
}

// Update the current page, set defaults, call the delegate
- (void) updatePageTo: (uint) newPageNumber
{
pageNumber = newPageNumber;

[[NSUserDefaults standardUserDefaults]
setInteger:pageNumber forKey:DEFAULTS_BOOKPAGE];
[[NSUserDefaults standardUserDefaults] synchronize];

SAFE_PERFORM_WITH_ARG(bookDelegate,
@selector(bookControllerDidTurnToPage:),
[NSNumber numberWithInt:pageNumber]);
}

// Request controller from delegate
- (UIViewController *) controllerAtPage: (int) aPageNumber
{
if (bookDelegate && [bookDelegate respondsToSelector:
@selector(viewControllerForPage:)])
{
UIViewController *controller =
[bookDelegate viewControllerForPage:aPageNumber];
controller.view.tag = aPageNumber;
return controller;
}
return nil;
}

// Update interface to the given page
- (void) fetchControllersForPage: (uint) requestedPage
orientation: (UIInterfaceOrientation) orientation
{
BOOL sideBySide = [self useSideBySide:orientation];
int numberOfPagesNeeded = sideBySide ? 2 : 1;
int currentCount = self.viewControllers.count;

uint leftPage = requestedPage;
if (sideBySide && (leftPage % 2)) leftPage--;

// Only check against current page when count is appropriate
if (currentCount && (currentCount == numberOfPagesNeeded))
{
if (pageNumber == requestedPage) return;
if (pageNumber == leftPage) return;
}

// Decide the prevailing direction, check new page against the old
UIPageViewControllerNavigationDirection direction =
(requestedPage > pageNumber) ?
UIPageViewControllerNavigationDirectionForward :
UIPageViewControllerNavigationDirectionReverse;
[self updatePageTo:requestedPage];

// Update the controllers, never adding a nil result
NSMutableArray *pageControllers = [NSMutableArray array];
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage]);
if (sideBySide)
SAFE_ADD(pageControllers, [self controllerAtPage:leftPage + 1]);
[self setViewControllers:pageControllers
direction: direction animated:YES completion:nil];
}

// Entry point for external move request
- (void) moveToPage: (uint) requestedPage
{
// Thanks Dino Lupo
[self fetchControllersForPage:requestedPage
orientation:(UIInterfaceOrientation)self.interfaceOrientation];
}
#pragma mark Data Source
- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController
viewControllerAfterViewController:
(UIViewController *)viewController
{
[self updatePageTo:pageNumber + 1];
return [self controllerAtPage:(viewController.view.tag + 1)];
}

- (UIViewController *)pageViewController:
(UIPageViewController *)pageViewController
viewControllerBeforeViewController:
(UIViewController *)viewController
{
[self updatePageTo:pageNumber - 1];
return [self controllerAtPage:(viewController.view.tag - 1)];
}

#pragma mark Delegate Method
- (UIPageViewControllerSpineLocation)pageViewController:
(UIPageViewController *) pageViewController
spineLocationForInterfaceOrientation:
(UIInterfaceOrientation) orientation
{
// Always start with left or single page
NSUInteger indexOfCurrentViewController = 0;
if (self.viewControllers.count)
indexOfCurrentViewController =
((UIViewController *)[self.viewControllers
objectAtIndex:0]).view.tag;
[self fetchControllersForPage:indexOfCurrentViewController
orientation:orientation];

// Decide whether to present side by side
BOOL sideBySide = [self useSideBySide:orientation];
self.doubleSided = sideBySide;

UIPageViewControllerSpineLocation spineLocation = sideBySide ?
UIPageViewControllerSpineLocationMid :
UIPageViewControllerSpineLocationMin;
return spineLocation;
}

// Return a new book
+ (id) bookWithDelegate: (id) theDelegate style: (BookLayoutStyle) aStyle
{
// Determine orientation
UIPageViewControllerNavigationOrientation orientation =
UIPageViewControllerNavigationOrientationHorizontal;
if ((aStyle == BookLayoutStyleFlipBook) ||
(aStyle == BookLayoutStyleVerticalScroll))
orientation = UIPageViewControllerNavigationOrientationVertical;

// Determine transitionStyle
UIPageViewControllerTransitionStyle transitionStyle =
UIPageViewControllerTransitionStylePageCurl;
if ((aStyle == BookLayoutStyleHorizontalScroll) ||
(aStyle == BookLayoutStyleVerticalScroll))
transitionStyle = UIPageViewControllerTransitionStyleScroll;

// Pass options as a dictionary. Keys are spine location (curl)
// and spacing between vc's(scroll)
BookController *bc = [[BookController alloc]
initWithTransitionStyle:transitionStyle
navigationOrientation:orientation
options:nil];

bc.layoutStyle = aStyle;
bc.dataSource = bc;
bc.delegate = bc;
bc.bookDelegate = theDelegate;

return bc;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Scrubbing Pages in a Page View Controller

Manually flipping from page to page quickly becomes tedious, especially when you’re working with a “curl” presentation of dozens or hundreds of virtual pages. To address this, you can add a slider to your books. Recipe 7-7 creates a slider that appears when the background is double-tapped and that fades away after a few seconds if not used.

A custom tap gesture recognizer starts the timer, which is reset whenever the user interacts with the slider. Once the timer fires, the slider overview animates away and the user is left with the full-screen page presentation. This approach, using a tap-based overlay, is common to many of Apple’s own applications such as the Photos app.

There is a drawback to this approach. iOS’s ability to flip through pages rapidly must be weighed against the new begin/end appearance transition overhead. You may see, as I did in early betas of iOS 6, a number of “unbalanced calls” because of the rapid page changes.

Recipe 7-7. Adding an Auto-Hiding Slider to a Page View Controller

Click here to view code image

// Slider callback resets the timer, moves to the new page
- (void) moveToPage: (UISlider *) theSlider
{
[hiderTimer invalidate];
hiderTimer = [NSTimer scheduledTimerWithTimeInterval:3.0f
target:self selector:@selector(hideSlider:)
userInfo:nil repeats:NO];
[bookController moveToPage:(int) theSlider.value];
}

// BookController Delegate method allows slider value update
- (void) bookControllerDidTurnToPage: (NSNumber *) pageNumber
{
pageSlider.value = pageNumber.intValue;
}

// Hide the slider after the timer fires
- (void) hideSlider: (NSTimer *) aTimer
{
[UIView animateWithDuration:0.3f animations:^(void){
pageSlider.alpha = 0.0f;}];
[hiderTimer invalidate];
hiderTimer = nil;
}

// Present the slider when tapped
- (void) handleTap: (UIGestureRecognizer *) recognizer
{
[UIView animateWithDuration:0.3f animations:^(void){
pageSlider.alpha = 1.0f;}];
[hiderTimer invalidate];
hiderTimer = [NSTimer scheduledTimerWithTimeInterval:3.0f
target:self selector:@selector(hideSlider:)
userInfo:nil repeats:NO];
}

// Set up the book controller as a child view controller
- (void) viewWillAppear:(BOOL)animated
{
if (!bookController)
{
bookController = [BookController bookWithDelegate:self
style:BookLayoutStyleBook];
RESIZABLE(bookController.view);
}

[self addChildViewController:bookController];
[self.view addSubview:bookController.view];
[bookController didMoveToParentViewController:self];

[bookController moveToPage:0];
[self.view bringSubviewToFront:pageSlider];
}

// Close down the child view controller
- (void) viewWillDisappear:(BOOL)animated
{
[bookController willMoveToParentViewController:nil];
[bookController.view removeFromSuperview];
[bookController removeFromParentViewController];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Custom Containers

Apple’s split view controller was groundbreaking in that it introduced the notion that more than one controller could live onscreen at a time. Until the split view, the rule was one controller with many views at a time. With split view, several controllers coexisted onscreen, all of them independently responding to orientation and memory events.

Apple exposed this multiple-controller paradigm to developers in the iOS 5 SDK. You can now design a parent controller and add child controllers to it. Events are passed from parent to child as needed. This allows you to build custom containers, outside of the Apple-standard set of containers such as tab bar and navigation controllers.

Recipe 7-8 builds a reusable container that can hold either one or two children. When loaded with two child view controllers, it lets you flip from one to the other and back. It has quite a lot of conditionality built in. That’s because it needs to handle use as a standalone view controller, as a child view controller itself, and as a modal view controller. Imagine the following situations.

Like a navigation controller, you can create this flip view controller directly and set it as your primary window’s root view controller. In that case, it has no further relationship with any hierarchy. It merely manages its children. You can also use it as a child of some other container, such as in a tab bar controller presentation, a split view controller, and so forth. When used in that way, it acts as both a parent of its children and as a child of the container that holds it. Finally, you can present the controller directly. The flip view container must behave as a solid citizen in all of these situations.

To do this, the controller has two tasks. First, it must manage its children using standard UIKit calls. Second, it must be aware of how it is participating in the view hierarchy. When being presented, this recipe adds a navigation bar so a Done button becomes available to end users.

Adding and Removing a Child View Controller

In the simplest scenario, a container controller takes three steps to properly add a child. It must do the following:

1. Call addChildViewController: on the parent, passing the child as the argument (for example, [self addChildViewController:childvc]).

2. Add the child controller’s view as a subview (for example, [self.view addSubview:childvc.view]).

3. Call didMoveToParent: on the child with the parent as its argument (for example, [childvc didMoveToParent:self]).

To remove a child view controller, the steps are almost (but not quite) mirrored:

1. Call willMoveToParentViewController: on the child, passing nil as the argument (for example, [childvc willMoveToParentViewController:nil]).

2. Remove the child controller’s view (for example, [childvc.view removeFromSuperview]).

3. Call removeFromParentViewController on the child (for example, [childvc removeFromParentViewController]).

Transitioning Between View Controllers

UIKit offers a simple way to animate view features when you move from one child view controller to another. You provide a source view controller, a destination, and a duration for the animated transition. You can specify the kind of transition in the options. Supported transitions include page curls, dissolves, and flips. This method creates a simple curl from one view controller to the next:

Click here to view code image

- (void) action: (id) sender
{
[redController willMoveToParentViewController:nil];
[self addChildViewController:blueController];

[self transitionFromViewController:redController
toViewController:blueController
duration:1.0f
options:UIViewAnimationOptionLayoutSubviews |
UIViewAnimationOptionTransitionCurlUp
animations:^(void){}
completion:^(BOOL finished){
[redController.view removeFromSuperview];
[self.view addSubview:blueController.view];}

[redController removeFromParentViewController];
[blueController didMoveToParentViewController:self];
];
}

You can use the same approach to animate UIView properties without the built-in transitions. For example, this method re-centers and fades out the red controller while fading in the blue. These are all animatable UIView features and are changed in the animations: block:

Click here to view code image

- (void) action: (id) sender
{
[redController willMoveToParentViewController:nil];
[self addChildViewController:blueController];

blueController.view.alpha = 0.0f;
[self transitionFromViewController:redController
toViewController:blueController
duration:2.0f
options:UIViewAnimationOptionLayoutSubviews
animations:^(void){
redController.view.center = CGPointMake(0.0f, 0.0f);
redController.view.alpha = 0.0f;
blueController.view.alpha = 1.0f;}
completion:^(BOOL finished){
[redController.view removeFromSuperview];
[self.view addSubview:blueController.view];

[redController removeFromParentViewController];
[blueController didMoveToParentViewController:self];
}
];
}

Using transitions and view animations is an either/or scenario. Either set a transition option or change view features in the animations block. Otherwise, they conflict, as you can easily confirm for yourself. Use the completion block to remove the old view and move the new view into place.

Although simple to implement, this kind of transition is not meant for use with Core Animation. If you want to add Core Animation effects to your view-controller-to-view-controller transitions, look at using a custom segue instead.

Recipe 7-8. Creating a View Controller Container

Click here to view code image

- (void) viewDidDisappear:(BOOL)animated
{
if (!controllers.count)
{
NSLog(@"Error: No root view controller");
return;
}

// Clean up the child view controller
UIViewController *currentController = (UIViewController *)controllers[0];
[currentController willMoveToParentViewController:nil];
[currentController.view removeFromSuperview];
[currentController removeFromParentViewController];
}

- (void) flip: (id) sender
{
// Please call only with two controllers;
if (controllers.count < 2) return;

// Determine which item is front, which is back
UIViewController *front = (UIViewController *)controllers[0];
UIViewController *back =  (UIViewController *)controllers[1];

// Select the transition direction
UIViewAnimationTransition transition = reversedOrder ?
UIViewAnimationOptionTransitionFlipFromLeft :
UIViewAnimationOptionTransitionFlipFromRight;

// Hide the info button until after the flip
infoButton.alpha = 0.0f;

// Prepare the front for removal, the back for adding
[front willMoveToParentViewController:nil];
[self addChildViewController:back];

// Perform the transition
[self transitionFromViewController: front
toViewController:back duration:0.5f options: transition
animations:nil completion:^(BOOL done){

// Bring the Info button back into view
[self.view bringSubviewToFront:infoButton];
[UIView animateWithDuration:0.3f animations:^(){
infoButton.alpha = 1.0f;
}];

// Finish up transition
[front removeFromParentViewController];
[back didMoveToParentViewController:self];

reversedOrder = !reversedOrder;
controllers = @[back, front];
}];
}

- (void) viewWillAppear:(BOOL)animated
{
if (!controllers.count)
{
NSLog(@"Error: No root view controller");
return;
}

UIViewController *front = controllers[0];
UIViewController *back = nil;
if (controllers.count > 1) back = controllers[1];

[self addChildViewController:front];
[self.view addSubview:front.view];
[front didMoveToParentViewController:front];

// Check for presentation and for flippability
BOOL isPresented = self.isBeingPresented;

// Clean up instance if re-use
if (navbar || infoButton)
{
[navbar removeFromSuperview];
[infoButton removeFromSuperview];
navbar = nil;
}

// When presented, add a custom navigation bar
if (isPresented)
{
navbar = [[UINavigationBar alloc] initWithFrame:CGRectZero];
navbar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
navbar.frame = CGRectMake(0.0f, 0.0f, self.view.frame.size.width,44.0f);
[self.view addSubview:navbar];
}

// Right button is done when VC is presented
self.navigationItem.leftBarButtonItem = nil;
self.navigationItem.rightBarButtonItem = isPresented ?
SYSBARBUTTON(UIBarButtonSystemItemDone, @selector(done:)) : nil;

// Populate the navigation bar
if (navbar)
[navbar setItems:@[self.navigationItem] animated:NO];

// Size the child VC view(s)
CGFloat verticalOffset = (navbar != nil) ? 44.0f : 0.0f;
CGRect destFrame = CGRectMake(0.0f, verticalOffset,
self.view.frame.size.width,
self.view.frame.size.height - verticalOffset);
front.view.frame = destFrame;
back.view.frame = destFrame;

// Set up info button
if (controllers.count < 2) return; // our work is done here

// Create the "i" button
infoButton = [UIButton buttonWithType:prefersDarkInfoButton ?
UIButtonTypeInfoDark : UIButtonTypeInfoLight];
[infoButton addTarget:self action:@selector(flip:)
forControlEvents:UIControlEventTouchUpInside];

// Place "i" button at bottom right of view
CGSize frameSize = self.view.frame.size;
infoButton.frame = CGRectMake(frameSize.width - 44.0f,
frameSize.height - 44.0f, 44.0f, 44.0f);
[self.view addSubview:infoButton];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Recipe: Segues

With custom containers comes their little brother, custom segues. Just as tab and navigation controllers provide a distinct way of transitioning between child controllers, you can build custom segues that define animations unique to your class. There’s not a lot of support in Interface Builder for custom containers with custom segues, so it’s best to develop your presentations in code at this time. Here’s how you might implement the code that moves the controller to a new view:

Click here to view code image

// Informal custom delegate method
- (void) segueDidComplete
{
// Retrieve the two vc's
UIViewController *source = [childControllers objectAtIndex:vcIndex];
UIViewController *destination = [childControllers objectAtIndex:nextIndex];

// Reparent as needed
[destination didMoveToParentViewController:self];
[source removeFromParentViewController];

// Update the bookkeeping
vcIndex = nextIndex;
pageControl.currentPage = vcIndex;
}

// Transition to new view using custom segue
- (void) switchToView: (int) newIndex
goingForward: (BOOL) goesForward
{
if (vcIndex == newIndex) return;

// Segue to the new controller
UIViewController *source =
[childControllers objectAtIndex:vcIndex];
UIViewController *destination =
[childControllers objectAtIndex:newIndex];

// Start the reparenting process
[source willMoveToParentViewController:nil];
[self addChildViewController:destination];

RotatingSegue *segue = [[RotatingSegue alloc]
initWithIdentifier:@"segue"
source:source destination:destination];
segue.goesForward = goesForward;
segue.delegate = self;
[segue perform];

vcIndex = newIndex;
}

Here, the code identifies the source and destination child controllers, builds a segue, sets its parameters, and tells it to perform. An informal delegate method is called back by that custom segue on its completion. Recipe 7-9 shows how the segue is built. In this example, it creates a rotating cube effect that moves from one view to the next. Figure 7-5 shows the segue in action.

Figure 7-5. Custom segues allow you to create visual metaphors for your custom containers. Recipe 7-9 builds a “cube” of view controllers that can be rotated from one to the next. The switches on each controller update the art alpha value from translucent to solid and back.

The segue’s goesForward property determines whether the rotation moves to the right or left around the virtual cube. Although this example uses four view controllers, as you saw in the code that laid out the child view controllers, that’s a limitation of the metaphor, not of the code itself, which will work with any number of child controllers. You can just as easily build three- or seven-sided presentations with this, although you are breaking an implicit “reality” contract with your user if you do so. To add more (or fewer) sides, you should adjust the animation geometry in the segue away from a cube to fit your virtual n-hedron.

Recipe 7-9. Creating a Custom View Controller Segue

Click here to view code image

#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG)\
(([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? \
[THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] :
nil)

@implementation RotatingSegue
// Return a shot of the given view
- (UIImage *)screenShot: (UIView *) aView
{
// Arbitrarily dims to 40%. Adjust as desired.
UIGraphicsBeginImageContext(hostView.frame.size);
[aView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
CGContextSetRGBFillColor(UIGraphicsGetCurrentContext(),
0, 0, 0, 0.4f);
CGContextFillRect (UIGraphicsGetCurrentContext(), hostView.frame);
UIGraphicsEndImageContext();
return image;
}

// Return a layer with the view contents
- (CALayer *) createLayerFromView: (UIView *) aView
transform: (CATransform3D) transform
{
CALayer *imageLayer = [CALayer layer];
imageLayer.anchorPoint = CGPointMake(1.0f, 1.0f);
imageLayer.frame = (CGRect){.size = hostView.frame.size};
imageLayer.transform = transform;
UIImage *shot = [self screenShot:aView];
imageLayer.contents = (__bridge id) shot.CGImage;

return imageLayer;
}

// On starting the animation, remove the source view
- (void)animationDidStart:(CAAnimation *)animation
{
UIViewController *source =
(UIViewController *) super.sourceViewController;
[source.view removeFromSuperview];
}

// On completing the animation, add the destination view,
// remove the animation, and ping the delegate
- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)finished
{
UIViewController *dest =
(UIViewController *) super.destinationViewController;
[hostView addSubview:dest.view];
[transformationLayer removeFromSuperlayer];
if (delegate)
SAFE_PERFORM_WITH_ARG(delegate,
@selector(segueDidComplete), nil);
}

// Perform the animation
-(void)animateWithDuration: (CGFloat) aDuration
{
CAAnimationGroup *group = [CAAnimationGroup animation];
group.delegate = self;
group.duration = aDuration;

CGFloat halfWidth = hostView.frame.size.width / 2.0f;
float multiplier = goesForward ? -1.0f : 1.0f;

// Set the x, y, and z animations
CABasicAnimation *translationX = [CABasicAnimation
animationWithKeyPath:@"sublayerTransform.translation.x"];
translationX.toValue =
[NSNumber numberWithFloat:multiplier * halfWidth];

CABasicAnimation *translationZ = [CABasicAnimation
animationWithKeyPath:@"sublayerTransform.translation.z"];
translationZ.toValue = [NSNumber numberWithFloat:-halfWidth];

CABasicAnimation *rotationY = [CABasicAnimation
animationWithKeyPath:@"sublayerTransform.rotation.y"];
rotationY.toValue = [NSNumber numberWithFloat: multiplier * M_PI_2];

// Set the animation group
group.animations = [NSArray arrayWithObjects:
rotationY, translationX, translationZ, nil];
group.fillMode = kCAFillModeForwards;
group.removedOnCompletion = NO;

// Perform the animation
[CATransaction flush];
[transformationLayer addAnimation:group forKey:kAnimationKey];
}

- (void) constructRotationLayer
{
UIViewController *source =
(UIViewController *) super.sourceViewController;
UIViewController *dest =
(UIViewController *) super.destinationViewController;
hostView = source.view.superview;

// Build a new layer for the transformation
transformationLayer = [CALayer layer];
transformationLayer.frame = hostView.bounds;
transformationLayer.anchorPoint = CGPointMake(0.5f, 0.5f);
CATransform3D sublayerTransform = CATransform3DIdentity;
sublayerTransform.m34 = 1.0 / -1000;
[transformationLayer setSublayerTransform:sublayerTransform];
[hostView.layer addSublayer:transformationLayer];

// Add the source view, which is in front
CATransform3D transform = CATransform3DMakeIdentity;
[transformationLayer addSublayer:
[self createLayerFromView:source.view
transform:transform]];

// Prepare the destination view either to the right or left
// at a 90/270 degree angle off the main
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
transform = CATransform3DTranslate(transform,
hostView.frame.size.width, 0, 0);
if (!goesForward)
{
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
transform = CATransform3DTranslate(transform,
hostView.frame.size.width, 0, 0);
transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0);
transform = CATransform3DTranslate(transform,
hostView.frame.size.width, 0, 0);
}
[transformationLayer addSublayer:
[self createLayerFromView:dest.view transform:transform]];
}

// Standard UIStoryboardSegue perform
- (void)perform
{
[self constructRotationLayer];
[self animateWithDuration:0.5f];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 7.

Segues, Interface Builder, and iOS 6

Starting in the iOS 6 SDK, you can apply custom segues in your storyboards. You’ll need to tie those segues to some action item such as a button or bar button press, or similar. Figure 7-6 shows how custom segues are now listed in Interface Builder. The “rotating” segue is from Recipe 7-9.

Figure 7-6. The iOS 6 SDK allows you to apply custom segues in Interface Builder. IB scans for UIStoryboardSegue child classes. Here, IB lists the custom “rotating” segue along with system-supplied options.

What’s more, in iOS 6, segues can be “unwound.” Unwinding allows you to move back from a new view controller to its logical parent using a custom segue you provide. You achieve this by implementing a few methods:

• Specify whether you can unwind with canPerformUnwindSegueAction:fromViewController:withSender:.

• Return a view controller to viewControllerForUnwindSegueAction:fromViewController:withSender:. This controller will be the unwinding destination.

• Supply the required unwinding segue instance via segueForUnwindingToViewController:fromViewController:identifier:. Typically, you’ll want your unwind to animate in the reverse direction as your original segue.

Finally, you can now allow or disallow any segue by implementing shouldPerformSegueWithIdentifier:sender:. You return either YES or NO, depending on whether you want the identified segue to proceed.

Summary

This chapter showed many view controller classes in action. You learned how to use them to handle view presentation and user navigation for various device deployment choices. With these classes, you discovered how to expand virtual interaction space and create multipage interfaces as demanded by applications, while respecting the human interface guidelines on the platform in question. Before moving on to the next chapter, here are a few points to consider about view controllers:

• Use navigation trees to build hierarchical interfaces. They work well for looking at file structures or building a settings tree. When you think “disclosure view” or “preferences,” consider pushing a new controller onto a navigation stack or using a split view to present them directly.

• Don’t be afraid to use conventional UI elements in unconventional ways so long as you respect the overall Apple Human Interface Guidelines. You can apply innovative approaches for UINavigationController that don’t involve any navigation. The tools are there for the using.

• Be persistent. Let your users return to the same GUI state that they last left from. NSUserDefaults provides a built-in system for storing information between application runs. Use these defaults to re-create the prior interface state.

• Go universal. Let your code adapt itself for various device deployments rather than forcing your app into an only-iPhone or only-iPad design. This chapter touched on some simple runtime device detection and interface updates that you can easily expand for more challenging circumstances. Universal deployment isn’t just about stretching views and using alternate art and XIB files. It’s also about detecting when a device influences the way you interact, not just the look of the interface.

• When working with custom containers, don’t be afraid of using storyboards directly. You do not have to build and retain an array of all your controllers simultaneously. Storyboards offer direct access to all your elements. Like the new page view controller class, just load the controllers you need, when you need them.

8. Common Controllers

The iOS software development kit (SDK) provides a wealth of system-supplied controllers that you can use in your day-to-day development tasks. This chapter introduces some of the most popular ones. You read about selecting images from your device library, snapping photos, and recording and editing videos. You discover how to allow users to compose e-mails and text messages, and how to post updates to social services like Twitter and Facebook. Each controller offers a way to leverage prepackaged iOS system functionality. Here’s the know-how you need to get started using them.

Image Picker Controller

The UIImagePickerController class enables users to select images from a device’s media library and to snap pictures with its camera. It is somewhat of a living fossil; its system-supplied interface was created back in the early days of iPhone OS. Over time, as Apple rolled out devices with video recording (iOS 3.1) and front and rear cameras (iOS 4), the class evolved. It introduced photo and video editing, customizable camera view overlays, and more.

Image Sources

The image picker works with the following three sources. As much as one might want more nuanced access to iCloud and to shared and individual photo streams, for now you can access your entire library, just the camera roll, or just the camera. Submit your enhancement suggestions to http://bugreport.apple.com:

• UIImagePickerControllerSourceTypePhotoLibrary—This source contains all images synced to iOS. Material in this source includes images snapped by the user (Camera Roll), photo streams, albums synced from computers, copied via the camera connection kit, and so on.

• UIImagePickerControllerSourceTypeSavedPhotosAlbum—This source refers only to the Camera Roll, which consists of pictures and videos captured by the user on units with cameras or to the Saved Photos album for noncamera units. Photo stream items captured on other devices also sync into this roll.

• UIImagePickerControllerSourceTypeCamera—This source enables users to shoot pictures with a built-in iPhone camera. The source provides support for front and back camera selection, and both still and video capture.

Presenting the Picker on iPhone and iPad

Figure 8-1 shows the image picker presented on an iPhone and iPad using a library source. The UIImagePickerController class was designed to operate in a modal presentation on iPhone-like devices (left) or a popover on tablets (right).

Figure 8-1. The core image picker allows users to select images from pictures stored in the media library.

On iPhone-like devices, present the picker modally. On the iPad, you can embed pickers into popovers instead. Never push image pickers onto an existing navigation stack. On older versions of iOS, this would create a second navigation bar under the primary. On modern versions of iOS, it throws a nasty exception: “Pushing a navigation controller is not supported by the image picker.”

Recipe: Selecting Images

In its simplest role, the image picker enables users to browse their library and select a stored photo. Recipe 8-1 demonstrates how to create and present a picker and retrieve an image selected by the user. Before proceeding with general how-to, let me introduce two key how-to’s for this section.

How To: Adding Photos to the Simulator

Before running this recipe on a Mac, you may want to populate the simulator’s photo collection. You can do this in two ways. First, you can drop images onto the simulator from Finder. Each image opens in Mobile Safari, where you can then tap-and-hold and choose Save Image to copy the image to your photo library.

Once you set up your test photo collection as you like, navigate to the Application Support folder in your home library on your Mac. Open the iPhone Simulator folder, and then the folder for the iOS version you’re currently using (for example, 6.0). Inside, you’ll find a Media folder. The path to the Media folder will look something like this: /Users/(Your Account)/Library/Application Support/iPhone Simulator/(OS Version)/Media.

Back up this newly populated Media folder to a convenient location. Creating a backup enables you to restore it in the future without having to re-add each photo individually. Each time you reset the simulator’s contents and settings, this material gets deleted. Having a folder on hand that’s ready to drop in and test with can be a huge time saver.

Alternatively, purchase a copy of Ecamm’s PhoneView (http://ecamm.com). PhoneView offers access to a device’s Media folder through the Apple File Connection (AFC) service. Connect an iPhone, launch the application, and then you can drag and drop folders from PhoneView to your Mac. Make sure you check Show Entire Disk in PhoneView preferences to see all the relevant folders.

Using PhoneView, copy the DCIM, PhotoData, and Photos folders from a device to a folder on your Macintosh. Once copied, quit the simulator and add the copied folders into the ~/Library/Application Support/iPhone Simulator/(OS Version)/Media destination. When you next launch the simulator, your new media will be waiting for you in the Photos app.

The Assets Library Framework

This recipe uses the assets library framework. To add frameworks, use the Targets > Build Phases > Link Binary with Libraries settings. Click + and select the framework to add. In the Project Navigator, drag the newly added framework down to the Frameworks group.

Using the assets library may sound complicating, but there are strong underlying reasons why this is a best practice for working with image pickers. In iOS 6, an image picker may return an asset URL without providing a direct image to use. Recipe 8-1 assumes that’s a possibility and offers a method to load an image from the assets library (loadImageFromAssetURL:into:). A typical URL looks something like this:

assets-library://asset/asset.JPG?id=553F6592-43C9-45A0-B851-28A726727436&ext=JPG

That URL provides direct access to media.

Fortunately, Apple has now moved past an extremely annoying assets library issue. Historically, iOS queried the user for permission to use his or her location—permissions that users would normally deny. Apps would get stuck because you couldn’t force the system to ask again. Instead, iOS 6 now properly states that the app would like to access a user’s photos rather than location, hopefully with better user success. Determine your authorization situation by querying the class’s authorizationStatus. You can reset these granted privileges by opening Settings > Privacy and update service-based permissions (like location and photo access) on an app-by-app basis.

The procedural how-to matters now having been addressed, the next section introduces the image picker itself.

Presenting a Picker

Create an image picker by allocating and initializing it. Next, set its source type to the library (all images) or Camera Roll (captured images). Recipe 8-1 selects the photo library source, allowing users to browse through all library images.

Click here to view code image

UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;

An optional editing property (allowsEditing) adds a step to the interactive selection process. When enabled, it allows users to scale and frame the image they picked before finishing their selection. When disabled, any media selection immediately redirects control to the next phase of the picker’s life cycle.

Be sure to set the picker’s delegate property. The delegate conforms to the UINavigationControllerDelegate and UIImagePickerControllerDelegate protocols; it receives callbacks after a user has selected an image or has cancelled selection. When using an image picker controller with popovers, declare the UIPopoverControllerDelegate protocol as well.

When working on iPhone-like devices, always present the picker modally; check for the active device at runtime. The following test (iOS 3.2 and later) returns true when run on an iPhone, false on an iPad:

#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)

The following snippet shows the typical presentation patterns for image pickers:

Click here to view code image
if (IS_IPHONE)
{
[self presentViewController:picker animated:YES completion:nil];
}
else
{
if (popover) [popover dismissPopoverAnimated:NO];
popover = [[UIPopoverController alloc] initWithContentViewController:picker];
popover.delegate = self;
[popover presentPopoverFromBarButtonItem:
self.navigationItem.rightBarButtonItem
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}

Handling Delegate Callbacks

Recipe 8-1 considers the following three possible image picker callback scenarios:

• The user has successfully selected an image.

• The user has tapped Cancel (only available on modal presentations).

• The user dismissed the popover that embeds the picker by tapping outside of it.

The last two cases are simple. For a modal presentation, dismiss the controller. For a popover, remove any local references holding onto the instance. Processing a selection takes a little more work.

Pickers finish their lives by returning a custom information dictionary to their assigned delegate. This info dictionary contains key-value pairs related to the user’s selection. Depending on way the image picker has been set up and on the kind of media selected by the user, the dictionary may contain few or many of these keys.

For example, when working with images on the simulator dropped in via Safari, expect to see nothing more than a media type and a reference URL. Images shot on a device and then edited through the picker may contain all six keys listed here:

• UIImagePickerControllerMediaType—Defines the kind of media selected by the user—normally public.image for images or public.movie for movies. Media types are defined in the Mobile Core Services framework. Media types are primarily used in this context for adding items to the system pasteboard.

• UIImagePickerControllerCropRect—Returns the portion of the image selected by the user as an NSValue that stores a CGRect.

• UIImagePickerControllerOriginalImage—Offers a UIImage instance with the original (nonedited) image contents.

• UIImagePickerControllerEditedImage—Provides the edited version of the image, containing the portion of the picture selected by the user. The UIImage returned is small, sized to fit the iPhone screen.

• UIImagePickerControllerReferenceURL—Specifies a file system URL for the selected asset. This URL always points to the original version of an item, regardless of whether a user has cropped or trimmed an asset.

• UIImagePickerControllerMediaMetadata—Offers metadata for a newly captured photograph.

Recipe 8-1 uses several steps to move from the initial info dictionary contents to produce a recovered image. First, it checks whether the dictionary contains an edited version. If it does not find this, it accesses the original image. Should that fail, it retrieves the reference URL and tries to load it through the assets library. Normally, at the end of these steps, the application has a valid image instance to work with. If it does not, it logs an error and returns.

Finally, don’t forget to dismiss modally presented controllers before wrapping up work in the delegate callback.

Note

When it comes to user interaction zoology, the UIImagePickerController is a cow. It is slow to load. It eagerly consumes application memory and spends extra time chewing its cud. Be aware of these limitations when designing your apps and do not tip your image picker.

Recipe 8-1. Selecting Images

Click here to view code image

#define IS_IPHONE (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)

// Dismiss the picker
- (void) performDismiss
{
if (IS_IPHONE)
[self dismissViewControllerAnimated:YES completion:nil];
else
[popover dismissPopoverAnimated:YES];
}

// Present the picker
- (void) presentViewController:(UIViewController *)viewControllerToPresent
{
if (IS_IPHONE)
{
[self presentViewController:viewControllerToPresent
animated:YES completion:nil];
}
else
{
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerToPresent];
popover.delegate = self;
[popover presentPopoverFromBarButtonItem:
self.navigationItem.rightBarButtonItem
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}

// Popover was dismissed
- (void)popoverControllerDidDismissPopover:
(UIPopoverController *)aPopoverController
{
popover = nil;
}

// Retrieve an image from an asset URL
- (void) imageFromAssetURL: (NSURL *) assetURL
into: (UIImage **) image
{
ALAssetsLibrary *library = [[ALAssetsLibrary alloc] init];
ALAssetsLibraryAssetForURLResultBlock resultsBlock = ^(ALAsset *asset)
{
ALAssetRepresentation *assetRepresentation =
[asset defaultRepresentation];
CGImageRef cgImage = [assetRepresentation CGImageWithOptions:nil];
CFRetain(cgImage); // Thanks Oliver Drobnik
if (image) *image = [UIImage imageWithCGImage:cgImage];
CFRelease(cgImage);
};
ALAssetsLibraryAccessFailureBlock failure = ^(NSError *__strong error)
{
NSLog(@"Error retrieving asset from url: %@",
error.localizedFailureReason);
};

[library assetForURL:assetURL
resultBlock:resultsBlock failureBlock:failure];
}

// Update image and for iPhone, dismiss the controller
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Use the edited image if available
UIImage __autoreleasing *image =
info[UIImagePickerControllerEditedImage];

// If not, grab the original image
if (!image) image = info[UIImagePickerControllerOriginalImage];

// If still no luck, check for an asset URL
NSURL *assetURL = info[UIImagePickerControllerReferenceURL];
if (!image && !assetURL)
{
NSLog(@"Cannot retrieve an image from the selected item. Giving up.");
}
else if (!image)
{
// Retrieve the image from the asset library
[self imageFromAssetURL:assetURL into:&image];
}

// Display the image
if (image)
imageView.image = image;

if (IS_IPHONE)
[self performDismiss];
}

// iPhone-like devices only: dismiss the picker with cancel button
- (void) imagePickerControllerDidCancel: (UIImagePickerController *)picker
{
[self performDismiss];
}

- (void) pickImage
{
if (popover) return;

// Create and initialize the picker
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
picker.allowsEditing = editSwitch.isOn;
picker.delegate = self;

[self presentViewController:picker];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Snapping Photos

In addition to selecting pictures, the image picker controller enables you to snap photos with a device’s built-in camera. Because cameras are not available on all iOS units (specifically, older iPod touch and iPad devices), begin by checking whether the system running the application supports camera usage:

Click here to view code image
if ([UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera]) ...

The rule is this: Never offer camera-based features for devices without cameras. Although iOS 6 was deployed only to camera-ready devices, Apple has not committed to this as policy. As unlikely as it sounds, they could introduce new models without cameras. Until Apple says otherwise, assume the possibility exists for a noncamera system, even under modern iOS releases. Further, assume this method will accurately report state for camera-enabled devices whose source has been disabled through some future system setting.

Setting Up the Picker

You instantiate a camera version of the image picker the way you create a picture selection one. Just change the source type from library or Camera Roll to camera:

picker.sourceType =  UIImagePickerControllerSourceTypeCamera;

As with other modes, you can allow or disallow image editing as part of the photo-capture process by setting the allowsEditing property.

Although the setup is the same, the user experience differs slightly (see Figure 8-2). The camera picker offers a preview that displays after the user taps the camera icon to snap a photo. This preview lets users Retake the photo or Use the photo as is. Once they tap Use, control passes to the next phase. If you’ve enabled image editing, the user does so next. If not, control moves to the standard “did finish picking” method in the delegate.

Figure 8-2. The camera version of the image picker controller offers a distinct user experience for snapping photos.

Most modern devices offer more than one camera. The iPhone 3GS, the last remaining iOS 6 dinosaur, does not. Assign the cameraDevice property to select which camera you want to use. The rear camera is always the default.

The isCameraDeviceAvailable: class method queries whether a camera device is available. This snippet checks to see whether the front camera is available, and if so selects it:

Click here to view code image
if ([UIImagePickerController isCameraDeviceAvailable:
UIImagePickerControllerCameraDeviceFront])
picker.cameraDevice = UIImagePickerControllerCameraDeviceFront;

Here are a few more points about the camera or cameras that you can access through the UIImagePickerController class:

• You can query the device’s ability to use flash using the isFlashAvailableForCameraDevice: class method. Supply either the front or back device constant. This method returns YES for available flash, or otherwise NO.

• When a camera supports flash, you can set the cameraFlashMode property directly to auto (UIImagePickerControllerCameraFlashModeAuto, which is the default), to always used (UIImagePickerControllerCameraFlashModeOn), or always off (UIImagePickerControllerCameraFlashModeOff). Selecting off disables the flash regardless of ambient light conditions.

• Choose between photo and video capture by setting the cameraCaptureMode property. The picker defaults to photo-capture mode. You can test what modes are available for a device using availableCaptureModesForCameraDevice:. This returns an array of NSNumber objects, each of which encodes a valid capture mode, either photo (UIImagePickerControllerCameraCaptureModePhoto) or video (UIImagePickerControllerCameraCaptureModeVideo).

Displaying Images

When working with photos, keep image size in mind. Snapped pictures, especially those from high-resolution cameras, can be quite large, even in the age of Retina displays. Those captured from front-facing video cameras use lower-quality sensors and are much smaller.

Content modes provide an in-app solution to displaying large images. They allow image views to scale their embedded images to available screen space. Consider using one of the following modes:

• The UIViewContentModeScaleAspectFit mode ensures that the entire image is shown with the aspect ratio retained. The image may be padded with empty rectangles on the sides or the top and bottom to preserve that aspect.

• The UIViewContentModeScaleAspectFill mode displays as much of the image as possible, while filling the entire view. Some content may be clipped so that the entire view’s bounds are filled.

Saving Images to the Photo Album

Save a snapped image (or any UIImage instance, actually) to the photo album by calling UIImageWriteToSavedPhotosAlbum(). This function takes four arguments. The first is the image to save. The second and third arguments specify a callback target and selector, typically your primary view controller and image:didFinishSavingWithError:contextInfo:. The fourth argument is an optional context pointer. Whatever selector you use, it must take three arguments: an image, an error, and a pointer to the passed context information.

Recipe 8-2 uses this function to demonstrate how to snap a new image, allow user edits, and then save it to the photo album.

Recipe 8-2. Snapping Pictures

Click here to view code image

// "Finished saving" callback method
- (void)image:(UIImage *)image
didFinishSavingWithError: (NSError *)error
contextInfo:(void *)contextInfo;
{
// Handle the end of the image write process
if (!error)
NSLog(@"Image written to photo album");
else
NSLog(@"Error writing to photo album: %@", error.localizedFailureReason);
}

// Save the returned image
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
// Use the edited image if available
UIImage __autoreleasing *image =
info[UIImagePickerControllerEditedImage];

// If not, grab the original image
if (!image) image = info[UIImagePickerControllerOriginalImage];

NSURL *assetURL = info[UIImagePickerControllerReferenceURL];
if (!image && !assetURL)
{
NSLog(@"Cannot retrieve an image from selected item. Giving up.");
}
else if (!image)
{
NSLog(@"Retrieving from Assets Library");
[self loadImageFromAssetURL:assetURL into:&image];
}

if (image)
{
// Save the image
UIImageWriteToSavedPhotosAlbum(image, self,
@selector(image:didFinishSavingWithError:contextInfo:), NULL);
imageView.image = image;
}

[self performDismiss];
}

- (void) loadView
{
[super loadView];

// Only present the "Snap" option for camera-ready devices
if ([UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera])
self.navigationItem.rightBarButtonItem =
SYSBARBUTTON(UIBarButtonSystemItemCamera,
@selector(snapImage)));
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Recording Video

Even in the age of ubiquitous cameras on iOS 6, exercise caution regarding not just the availability but also the kinds of cameras provided by each device. When recording video, allow your application to detect whether a device supports camera-based video recording.

This is a two-step process. It isn’t sufficient to only check for a camera, such as those in the first-generation and 3G iPhones (in contrast to early iPad and iPod touch models, which shipped without). Only the 3GS and newer units provided video-recording capabilities and, however unlikely, future models could ship without cameras or with still cameras.

That means you perform two checks: first, that a camera is available; and second, that the available capture types includes video. This method returns a Boolean value indicating whether the device running the application is video ready:

Click here to view code image

- (BOOL) videoRecordingAvailable
{
// The source type must be available
if (![UIImagePickerController isSourceTypeAvailable:
UIImagePickerControllerSourceTypeCamera])
return NO;

// And the media type must include the movie type
NSArray *mediaTypes = [UIImagePickerController
availableMediaTypesForSourceType:
UIImagePickerControllerSourceTypeCamera]
return  [mediaTypes containsObject:(NSString *)kUTTypeMovie];
}

This method searches for a movie type (kUTTypeMovie, a.k.a. public.movie) in the results for the available media types query. Uniform type identifiers (UTIs) are strings that identify abstract types for common file formats such as images, movies, and data. These types are defined in the Mobile Core Services framework. Be sure to include the framework in your project:

#import <MobileCoreServices/MobileCoreServices.h>

Creating the Video Recording Picker

Recording video is almost identical to capturing still images with the camera. You allocate and initialize a new image picker, set its delegate, and present it:

Click here to view code image

UIImagePickerController *picker =
[[UIImagePickerController alloc] init];
picker.sourceType =  UIImagePickerControllerSourceTypeCamera;
picker.videoQuality = UIImagePickerControllerQualityTypeMedium;
picker.mediaTypes = @[(NSString *)kUTTypeMovie]; // public.movie
picker.delegate = self;

Choose the video quality you want to record. As you improve quality, the data stored per second increases. Select from high (UIImagePickerControllerQualityTypeHigh), medium (UIImagePickerControllerQualityTypeMedium), low (UIImagePickerControllerQualityTypeLow), or VGA (UIImagePickerControllerQualityType640x480).

As with image picking, the video version allows you to set an allowsEditing property, as discussed in Recipe 8-5.

Saving the Video

The info dictionary returned by the video picker contains a UIImagePickerControllerMediaURL key. This media URL points to the captured video, which is stored in a temporary folder within the sandbox. Use the UISaveVideoAtPathToSavedPhotosAlbum() method to store the video to your library.

This save method takes four arguments: the path to the video you want to add to the library, a callback target, a selector with three arguments (basically identical to the selector used during image save callbacks), and an optional context. The save method calls the target with that selector after it finishes its work, and there’s where you can check for success.

Recipe 8-3. Recording Video

Click here to view code image

- (void)video:(NSString *)videoPath
didFinishSavingWithError:(NSError *)error
contextInfo:(void *)contextInfo
{
if (!error)
self.title = @"Saved!";
else
NSLog(@"Error saving video: %@", error.localizedFailureReason);
}

- (void) saveVideo: (NSURL *) mediaURL
{
// check if video is compatible with album
BOOL compatible = UIVideoAtPathIsCompatibleWithSavedPhotosAlbum(
mediaURL.path);

// save
if (compatible)
UISaveVideoAtPathToSavedPhotosAlbum(
mediaURL.path, self,
@selector(video:didFinishSavingWithError:contextInfo:),
NULL);
}

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[self performDismiss];

// Save the video
NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
[self saveVideo: mediaURL];
}

- (void) recordVideo
{
if (popover) return;
self.title = nil;

// Create and initialize the picker
UIImagePickerController *picker =
[[UIImagePickerController alloc] init];
picker.sourceType =  UIImagePickerControllerSourceTypeCamera;
picker.videoQuality = UIImagePickerControllerQualityTypeMedium;
picker.mediaTypes = @[(NSString *)kUTTypeMovie];
picker.delegate = self;

[self presentViewController:picker];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Playing Video with Media Player

The MPMoviePlayerViewController and MPMoviePlayerController classes simplify video display in your applications. Part of the Media Player framework, these classes allow you to embed video into your views or to play movies back full screen. Offering the ready-built full-feature video player shown in Figure 8-3, you do little more than supply a content URL. The player provides the Done button, the time scrubber, the aspect control, and the playback controls, plus the underlying video presentation.

Figure 8-3. The Media Player framework simplifies adding video playback to your applications. This class allows off-device streaming video as well as fixed-size local assets. Supported video standards include H.264 Baseline Profile Level 3.0 video (up to 640x480 at 30fps) and MPEG-4 Part 2 video (Simple Profile). Most files with .mov, .mp4, .mpv, and .3gp extensions can be played. Audio support includes AAC-LC audio (up to 48KHz), MP3 (MPEG-1 Audio Layer 3, up to 48KHz) stereo.

Recipe 8-4 builds on the video recording introduced in Recipe 8-3. It adds playback after each recording by switching the Camera button in the navigation bar to a Play button. Once the video finishes playing, the button returns to the Camera. This recipe does not save any videos to the library, so you can record, play, and record, play, ad infinitum.

The image picker supplies a media URL, which is all you need to establish the player. Recipe 8-4 instantiates a new player and sets two properties. The first enables AirPlay, letting you stream the recorded video to an AirPlay-enabled receiver like Apple TV or a commercial application like Reflection (http://reflectionapp.com). The second sets the playback style to show the video full screen. It then presents the movie.

The two movie player classes consist of a presentable view controller and the actual player controller, which it owns as a property. That is why Recipe 8-4 makes so many mentions of player.moviePlayer. The view controller class is quite small and easy to launch. The real work takes place in the player controller.

Movie players use notifications rather than delegates to communicate with applications. You subscribe to these notifications to determine when the movie starts playing, when it finishes, and when it changes state (as in pause/play). Recipe 8-4 observes two notifications: when the movie becomes playable, and when it finishes.

After the movie loads and its state changes to playable, Recipe 8-4 starts playback. The movie appears full screen and continues playing until the user taps Done or the movie finishes. In either case, the player generates a finish notification. At that time, the app returns to recording mode, presenting its camera button to allow the user to record the next video sequence.

This recipe demonstrates the basics for playing video in iOS. You are not limited to video you record yourself. The movie player controller is agnostic about its video source. You can set the content URL to a file stored in your sandbox or even point it to a compliant resource out on the Internet.

Note

If your movie player opens and immediately closes, always check your URLs to make sure that they are valid. Do not forget that local file URLs need fileURLWithPath:, whereas remote ones can use URLWithString:.

Recipe 8-4. Video Playback

Click here to view code image

- (void) playMovie
{
// play
MPMoviePlayerViewController *player =
[[MPMoviePlayerViewController alloc] initWithContentURL:mediaURL];
player.moviePlayer.allowsAirPlay = YES;
player.moviePlayer.controlStyle = MPMovieControlStyleFullscreen;

[self.navigationController
presentMoviePlayerViewControllerAnimated:player];

// Handle the end of movie playback
[[NSNotificationCenter defaultCenter]
addObserverForName:MPMoviePlayerPlaybackDidFinishNotification
object:player.moviePlayer queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification){
// Return to recording mode
self.navigationItem.rightBarButtonItem =
SYSBARBUTTON(UIBarButtonSystemItemCamera,
@selector(recordVideo));

// Stop listening to movie notifications
[[NSNotificationCenter defaultCenter] removeObserver:self];
}];

// Wait for the movie to load and become playable
[[NSNotificationCenter defaultCenter]
addObserverForName:MPMoviePlayerLoadStateDidChangeNotification
object:player.moviePlayer queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification) {

// When the movie sets the playable flag, start playback
if ((player.moviePlayer.loadState &
MPMovieLoadStatePlayable) != 0)
[player.moviePlayer performSelector:@selector(play)
withObject:nil afterDelay:1.0f];
}];
}

// After recording any content, allow the user to play it
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[self performDismiss];

// recover video URL
mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
self.navigationItem.rightBarButtonItem =
SYSBARBUTTON(UIBarButtonSystemItemPlay, @selector(playMovie));
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Editing Video

Enabling an image picker’s allowsEditing property for a video source activates the yellow editing bars you’ve seen in the built-in photos app. (Drag the grips at either side to see them in action.) During the editing step of the capture process, users drag the ends of the scrubbing track to choose the video range they want to use.

Surprisingly, the picker does not trim the video itself. Instead, it returns four items in the info dictionary:

• UIImagePickerControllerMediaURL

• UIImagePickerControllerMediaType

• _UIImagePickerControllerVideoEditingStart

• _UIImagePickerControllerVideoEditingEnd

The media URL points to the untrimmed video, which is stored in a temporary folder within the sandbox. The video start and end points are NSNumbers, containing the offsets the user chose with those yellow edit bars. The media type is public.movie.

If you save the video to the library (as shown in Recipe 8-3), it stores the unedited version, which is not what your user expects or you want. The iOS SDK offers you two ways to edit video. Recipe 8-5 demonstrates how to use the AVFoundation framework to respond to the edit requests returned by the video image picker. Recipe 8-6 shows you how to pick videos from your library and use the UIVideoEditorController to edit.

AV Foundation and Core Media

This recipe requires access to two very specialized frameworks. The AV Foundation framework provides an Objective-C interface that supports media processing. Core Media uses a low-level C interface to describe media properties. Together these provide an iOS version of the Mac’s QuickTime media experience. Include both frameworks in your project’s build and add their headers to your source code for this recipe.

Recipe 8-5 begins by recovering the media URL from the image picker’s info dictionary. This URL points to the temporary file in the sandbox created by the image picker. The recipe creates a new AV asset URL from that.

Next it creates the export range, the times within the video that should be saved to the library. It does this using the Core Media CMTimeRange structure, building it from the info dictionary’s start and end times. The CMTimeMakeWithSeconds() function takes two arguments, a time and a scale factor. This recipe uses a factor of 1, preserving the exact times.

An export session allows your app to save data back out to the file system. This session does not save video to the library; that is a separate step. The session exports the trimmed video to a local file in the sandbox tmp folder, alongside the originally captured video. To create an export session, allocate it, and set its asset and quality.

Recipe 8-5 uses a new path to save to. This path is identical to the one it read from with “-trimmed” added to the core filename. The export session uses this path to set its output URL, the export range to specify what time range to include, and selects a QuickTime movie output file type. Now established, it’s ready to process the video. The export session asynchronously performs the file export using the properties you and the contents of the passed asset.

When complete, it’s time to save the trimmed movie to the central media library. Recipe 8-5 does so in the export session’s completion block.

Recipe 8-5. Trimming Video with AV Foundation

Click here to view code image

- (void) trimVideo: (NSDictionary *) info
{
// recover video URL
NSURL *mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
AVURLAsset *asset = [AVURLAsset URLAssetWithURL:mediaURL options:nil];

// Create the export range
CGFloat editingStart =
[info[@"_UIImagePickerControllerVideoEditingStart"] floatValue];
CGFloat editingEnd =
[info[@"_UIImagePickerControllerVideoEditingEnd"] floatValue];
CMTime startTime = CMTimeMakeWithSeconds(editingStart, 1);
CMTime endTime = CMTimeMakeWithSeconds(editingEnd, 1);
CMTimeRange exportRange = CMTimeRangeFromTimeToTime(startTime, endTime);

// Create a trimmed version URL: file:originalpath-trimmed.mov
NSString *urlPath = mediaURL.path;
NSString *extension = urlPath.pathExtension;
NSString *base = [urlPath stringByDeletingPathExtension];
NSString *newPath = [NSString stringWithFormat:
@"%@-trimmed.%@", base, extension];
NSURL *fileURL = [NSURL fileURLWithPath:newPath];

// Establish an export session
AVAssetExportSession *session = [AVAssetExportSession
exportSessionWithAsset:asset
presetName:AVAssetExportPresetMediumQuality];
session.outputURL = fileURL;
session.outputFileType = AVFileTypeQuickTimeMovie;
session.timeRange = exportRange;

// Perform the export
[session exportAsynchronouslyWithCompletionHandler:^(){
if (session.status == AVAssetExportSessionStatusCompleted)
[self saveVideo:fileURL];
else if (session.status == AVAssetExportSessionStatusFailed)
NSLog(@"AV export session failed");
else
NSLog(@"Export session status: %d", session.status);
}];
}}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Picking and Editing Video

You can use the Image Picker class to select videos as well as images, as demonstrated in Recipe 8-6. All it takes is a little editing of the media types property. Set the picker source type as normal, to either the photo library or the saved photos album, but restrict the media types property. The following snippet shows how to set the media types to request a picker that presents video assets only:

Click here to view code image
picker.sourceType =  UIImagePickerControllerSourceTypePhotoLibrary;
picker.mediaTypes = @[(NSString *)kUTTypeMovie];

Once the user selects a video, Recipe 8-6 enters editing mode. Always check that the video asset can be modified. Call the UIVideoEditorController class method canEditVideoAtPath:. This returns a Boolean value that indicates whether the video is compatible with the editor controller:

if (![UIVideoEditorController canEditVideoAtPath:vpath]) ...

If it is compatible, allocate a new video editor. The UIVideoEditorController class uses a system-supplied interface that allows users to interactively trim videos. Set its delegate and videoPath properties and present it. (This class can also be used to reencode data to a lower quality via its videoQuality property.)

The editor uses a set of delegate callbacks that are similar to but not identical to the ones used by the UIImagePickerController class. Callbacks include methods for success, failure, and user cancellation:

• videoEditorController:didSaveEditedVideoToPath:

• videoEditorController:didFailWithError:

• videoEditorControllerDidCancel:

Cancellation only occurs when the user taps the Cancel button within the video editor. Tapping outside a popover dismisses the editor but won’t invoke the callback. For both cancellation and failure, Recipe 8-6 responds by resetting its interface, allowing users to pick another video.

A success callback occurs when a user has finished editing the video and taps Use. The controller saves the trimmed video to a temporary path and calls the did-save method. Do not confuse this “saving” with storing items to your photo library; this path resides in the application sandbox’s tmp folder. If you do nothing with the data, iOS deletes it the next time the device reboots. Once past this step, Recipe 8-6 offers a button to save the trimmed data into the shared iOS photo album, which is the save-to-library feature introduced in Recipe 8-3.

Recipe 8-6. Using the Video Editor Controller

Click here to view code image

// The edited video is now stored in the local tmp folder
- (void)videoEditorController:(UIVideoEditorController *)editor
didSaveEditedVideoToPath:(NSString *)editedVideoPath
{
[self performDismiss];

// Update the working URL and present the Save button
mediaURL = [NSURL URLWithString:editedVideoPath];
self.navigationItem.leftBarButtonItem =
BARBUTTON(@"Save", @selector(saveVideo));
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Pick", @selector(pickVideo));
}

// Handle failed edit
- (void)videoEditorController:(UIVideoEditorController *)editor
didFailWithError:(NSError *)error
{
[self performDismiss];
mediaURL = nil;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Pick", @selector(pickVideo));
self.navigationItem.leftBarButtonItem = nil;
NSLog(@"Video edit failed: %@", error.localizedFailureReason);
}

// Handle cancel by returning to Pick state
- (void)videoEditorControllerDidCancel:
(UIVideoEditorController *)editor
{
[self performDismiss];
mediaURL = nil;
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Pick", @selector(pickVideo));
self.navigationItem.leftBarButtonItem = nil;
}

// Allow the user to edit the media with a video editor
- (void) editMedia
{
if (![UIVideoEditorController canEditVideoAtPath:mediaURL.path])
{
self.title = @"Cannot Edit Video";
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Pick", @selector(pickVideo));
return;
}

UIVideoEditorController *editor =
[[UIVideoEditorController alloc] init];
editor.videoPath = mediaURL.path;
editor.delegate = self;
[self presentViewController:editor];
}

// The user has selected a video. Offer an edit button
- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
[self performDismiss];

// Store the video URL and present an Edit button
mediaURL = [info objectForKey:UIImagePickerControllerMediaURL];
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Edit", @selector(editMedia));
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: E-Mailing Pictures

The Message UI framework allows users to compose e-mail and text messages within applications. As with camera access and the image picker, check whether a user’s device has been enabled for these services. A simple test allows you to determine when mail is available:

[MFMailComposeViewController canSendMail]

When mail capabilities are enabled, users can send their photographs via instances of MFMailComposeViewController. Texts are sent through MFMessageComposeViewController instances.

Recipe 8-7 uses this composition class to create a new mail item populated with the user-snapped photograph. The mail composition controller works best as a modally presented client on both the iPhone family and tablets. Your primary view controller presents it and waits for results via a delegate callback.

Creating Message Contents

The composition controller’s properties allow you to programmatically build a message including to/cc/bcc recipients and attachments. Recipe 8-7 demonstrates the creation of a simple HTML message with an attachment. Properties are almost universally optional. Define the subject and body contents via setSubject: and setMessageBody:. Each method takes a string as its argument.

Leave the To Recipients unassigned to greet the user with an unaddressed message. The times you’ll want to prefill this field include adding call-home features such as Report a Bug or Send Feedback to the Developer or when you allow the user to choose a favorite recipient in your settings.

Creating the attachment requires slightly more work. To add an attachment, you need to provide all the file components expected by the mail client. You supply data (via an NSData object), a MIME type (a string), and a filename (another string). Retrieve the image data using the UIImageJPEGRepresentation() function. This function can take time to work. So, expect slight delays before the message view appears.

This recipe uses a hard-coded MIME type of image/jpeg. If you want to send other data types, you can query iOS for MIME types via typical file extensions. Use UTTypeCopyPreferredTagWithClass(), as shown in the following method, which is defined in the MobileCoreServices framework:

Click here to view code image

#import <MobileCoreServices/UTType.h>
- (NSString *) mimeTypeForExtension: (NSString *) ext
{
// Request the UTI via the file extension
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension,
(__bridge CFStringRef) ext, NULL);
if (!UTI) return nil;

// Request the MIME file type via the UTI,
// may return nil for unrecognized MIME types
NSString *mimeType = (__bridge_transfer NSString *)
UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);

return mimeType;
}

This method returns a standard MIME type based on the file extension passed to it, such as .jpg, .png, .txt, .html, and so on. Always test to see whether this method returns nil, because the iOS’s built-in knowledge base of extension-MIME type matches is limited. Alternatively, search on the Internet for the proper MIME representations, adding them to your project by hand.

The e-mail uses a filename you specify to transmit the data you send. Use any name you like. Here, the name is set to pickerimage.jpg. Because you’re just sending data, there’s no true connection between the content you send and the name you assign:

Click here to view code image

[mcvc addAttachmentData:UIImageJPEGRepresentation(image, 1.0f)
mimeType:@"image/jpeg" fileName:@"pickerimage.jpg"];

Note

When using the iOS mail composer, attachments appear at the end of sent mail. Apple does not provide a way to embed images inside the flow of HTML text. This is due to differences between Apple and Microsoft representations.

Recipe 8-7. Sending Images by E-Mail

Click here to view code image

- (void)mailComposeController:(MFMailComposeViewController*)controller
didFinishWithResult:(MFMailComposeResult)result
error:(NSError*)error
{
// Wrap up the composer details
[self performDismiss];
switch (result)
{
case MFMailComposeResultCancelled:
NSLog(@"Mail was cancelled");
break;
case MFMailComposeResultFailed:
NSLog(@"Mail failed");
break;
case MFMailComposeResultSaved:
NSLog(@"Mail was saved");
break;
case MFMailComposeResultSent:
NSLog(@"Mail was sent");
break;
default:
break;
}
}

- (void) sendImage
{
UIImage *image = imageView.image;
if (!image) return;

// Customize the e-mail
MFMailComposeViewController *mcvc =
MFMailComposeViewController alloc] init];
mcvc.mailComposeDelegate = self;

// Set the subject
[mcvc setSubject:@"Here's a great photo!"];

// Create a prefilled body
NSString *body = @"<h1>Check this out</h1>\
<p>I snapped this image from the\
<code><b>UIImagePickerController</b></code>.</p>";
[mcvc setMessageBody:body isHTML:YES];

// Add the attachment
[mcvc addAttachmentData:UIImageJPEGRepresentation(image, 1.0f)
mimeType:@"image/jpeg" fileName:@"pickerimage.jpg"];

// Present the e-mail composition controller
[self presentViewController:mcvc];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Sending a Text Message

It’s even easier to send a text from your applications than e-mail. This particular controller is shown in Figure 8-4. As with mail, first ensure that the capability exists on your iOS device and declare the MFMessageComposeViewControllerDelegate protocol:

[MFMessageComposeViewController canSendText]

Figure 8-4. The message compose view controller.

Monitor the availability of text support, which may change over time, by listening for the MFMessageComposeViewControllerTextMessageAvailabilityDidChangeNotification notification.

Recipe 8-8 creates the new controller and sets its messageComposeDelegate and its body. If you know the intended recipients, you can prepopulate that field by passing an array of phone number strings. Present the controller however you like and wait for the delegate callback, where you dismiss it.

Recipe 8-8. Sending Texts

Click here to view code image

- (void)messageComposeViewController:
(MFMessageComposeViewController *)controller
didFinishWithResult:(MessageComposeResult)result
{
[self performDismiss];

switch (result)
{
case MessageComposeResultCancelled:
NSLog(@"Message was cancelled");
break;
case MessageComposeResultFailed:
NSLog(@"Message failed");
break;
case MessageComposeResultSent:
NSLog(@"Message was sent");
break;
default:
break;
}
}

- (void) sendMessage
{
MFMessageComposeViewController *mcvc =
[[MFMessageComposeViewController alloc] init];
mcvc.messageComposeDelegate = self;
mcvc.body = @"I'm reading the iOS Developer's Cookbook";
[self presentViewController:mcvc];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
if ([MFMessageComposeViewController canSendText])
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Send", @selector(sendMessage));
else
self.title = @"Cannot send texts";
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Posting Social Updates

The Social framework offers a unified API for integrating applications with social networking services. The framework currently supports Facebook, Twitter, and the China-based Sina Weibo. As with mail and messaging, start off by testing whether the service type you want to support is supported:

[SLComposeViewController isAvailableForServiceType:SLServiceTypeFacebook]

If it is, you can create a composition view controller for that service:

Click here to view code image

SLComposeViewController *fbController = [SLComposeViewController
composeViewControllerForServiceType:SLServiceTypeFacebook];

You customize a controller with images, URLs, and initial text. Recipe 8-9 demonstrates the steps to create the interface shown in Figure 8-5.

Figure 8-5. Composing Twitter messages

Originally introduced in iOS 5, the TWTweetComposeViewController provided the same interface as the iOS 6 Social version. It cannot, however, be presented in a popover. Further, you cannot generalize its use to other social services. Wherever possible for iOS 6 deployment, replace your use of the TW classes with the Social framework versions. The API is essentially identical.

Recipe 8-9. Posting Social Updates

Click here to view code image

- (void) postSocial: (NSString *) serviceType
{
// Establish the controller
SLComposeViewController *controller = [SLComposeViewController
composeViewControllerForServiceType:serviceType];

// Add text and an image
[controller addImage:[UIImage imageNamed:@"Icon.png"]];
[controller setInitialText:
@"I'm reading the iOS Developer's Coookbook"];

// Define the completion handler
controller.completionHandler =
^(SLComposeViewControllerResult result){
switch (result)
{
case SLComposeViewControllerResultCancelled:
NSLog(@"Cancelled");
break;
case SLComposeViewControllerResultDone:
NSLog(@"Posted");
break;
default:
break;
}
};

// Present the controller
[self presentViewController:controller];
}

- (void) postToFacebook
{
[self postSocial:SLServiceTypeFacebook];
}

- (void) postToTwitter
{
[self postSocial:SLServiceTypeTwitter];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];
if ([SLComposeViewController
isAvailableForServiceType:SLServiceTypeFacebook])
self.navigationItem.leftBarButtonItem =
BARBUTTON(@"Facebook", @selector(postToFacebook));
if ([SLComposeViewController
isAvailableForServiceType:SLServiceTypeTwitter])
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Twitter", @selector(postToTwitter));
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Recipe: Activity View Controller

Newly introduced, iOS 6’s activity view controller integrates data activities into the interface shown in Figure 8-6. With minimal development cost on your part, this new controller enables your users to copy items to the pasteboard, post to social media, share via e-mail and texting, and more. Built-in activities include Facebook, Twitter, Weibo, SMS, mail, printing, copying to pasteboard, and assigning data to a contact. Apps can define their own custom services, as well, which you’ll read about later in this section:

• UIActivityTypePostToFacebook

• UIActivityTypePostToTwitter

• UIActivityTypePostToWeibo

• UIActivityTypeMessage

• UIActivityTypeMail

• UIActivityTypePrint

• UIActivityTypeCopyToPasteboard

• UIActivityTypeAssignToContact

Figure 8-6. The UIActivityViewController class offers system and custom services.

How you present the controller varies by device. Show it modally on members of the iPhone family and in a popover on tablets. The UIBarButtonSystemItemAction icon provides the perfect way to populate bar buttons linking to this controller.

Best of all, almost no work is required on your end. After users select an activity, the controller handles all further interaction such as presenting a mail or Twitter compose sheet, adding a picture to the onboard library, or assigning it to a contact. This class offers a one-stop utility for many techniques already covered in this chapter.

Creating and Presenting the Controller

Create and present the view controller from your code, as demonstrated in Recipe 8-10. This implementation lets its main class adopt the UIActivityItemSource protocol, passing self in the items array passed to the controller. This represents the first of two ways to create and present the controller.

The protocol’s two mandatory methods supply the item to process and a placeholder for that item. The item corresponds to an appropriate item for a given activity type. The placeholder for that item is typically the same item, unless you are working with objects that you must process or create. In that case, you can create a placeholder object without real data. Both callbacks run on the main thread so keep your data small. If you need to process your data, consider using a provider instead.

Note

The controller passed back to the item method is actually the composition controller for the type of item being processed. For an email activity, it’s an MFMailComposeViewController, for Twitter, it’s the TWTweetComposeViewController, and so forth. You can cast the controller accordingly and perform any further updates like adding a subject line. At the time this book was being written, Apple had not fully documented this behavior, and it may change in the future. However, you can easily check (and it makes logical sense) that the first parameter in the item-for-activity-type callback is properly the activity’s composition controller and not the initial UIActivityViewController you presented.

The UIActivityItemProvider class enables you to delay passing data. It’s a type of operation (NSOperation) that offers you the flexibility to process data before being shared. For example, you might need to process a large video file before it can be uploaded to a social sharing site, or subsample some audio.

Subclass the provider class and implement the item method. This takes the place of the main method you normally use with operations. Generate the processed data, safe in the knowledge that it will run asynchronously without blocking your user’s interactive experience.

Recipe 8-10 passes self to the controller as part of its items array. self adopts the source protocol (<UIActivityItemSource>), so the controller understands to use callbacks when retrieving data items. The callbacks methods allow you to vary your data based on each one’s intended use. Use the activity types (such as Facebook or Add to Contacts; they’re listed earlier in this section) to choose the exact data you want to provide. This is especially important when selecting from resolutions for various uses. When printing, you’ll want to keep your data quality high. When tweeting, a low-res image may do the job instead.

If your data is invariant (that is, you’ll be passing the same data to e-mail as you would to Facebook), you can directly supply an array of data items (typically strings, images, and URLs). For example, you could create the controller like this. This uses a single image:

Click here to view code image

UIActivityViewController *activity = [[UIActivityViewController alloc]
initWithActivityItems:@[imageView.image]
applicationActivities:nil];

This direct approach is much simpler. Your primary class need not declare the item source protocol; you do not need to implement the extra methods. It’s a quick and easy way to manage activities for uncomplex items.

You’re not limited to passing single items, either. Include additional elements in the activity items array as needed. The following controller might add its two images to an e-mail or save both to the system Camera Roll, depending on the user’s selection. Broadening activities to use multiple items enables users to be more efficient while using your app:

Click here to view code image

UIImage *secondImage = [UIImage imageNamed:@"Default.png"];
UIActivityViewController *activity = [[UIActivityViewController alloc]
initWithActivityItems:@[imageView.image, secondImage]
applicationActivities:nil];

Recipe 8-10. The Activity View Controller

Click here to view code image

- (void) presentViewController:
(UIViewController *)viewControllerToPresent
{
if (IS_IPHONE)
{
[self presentViewController:viewControllerToPresent
animated:YES completion:nil];
}
else
{
popover = [[UIPopoverController alloc]
initWithContentViewController:viewControllerToPresent];
popover.delegate = self;
[popover presentPopoverFromBarButtonItem:
self.navigationItem.leftBarButtonItem
permittedArrowDirections:UIPopoverArrowDirectionAny
animated:YES];
}
}

// Return the item to process
- (id)activityViewController:
(UIActivityViewController *)activityViewController
itemForActivityType:(NSString *)activityType
{
return imageView.image;
}

// Return a thumbnail version of that item
- (id)activityViewControllerPlaceholderItem:
(UIActivityViewController *)activityViewController
{
return imageView.image;
}

// Create and present the view controller
- (void) action
{
UIActivityViewController *activity =
[[UIActivityViewController alloc]
initWithActivityItems:@[self] applicationActivities:nil];
[self presentViewController:activity];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Adding Services

Each app can provide application-specific services by subclassing the UIActivity class and presenting a custom view controller. The view controller allows the user to process the passed data in some way. Listing 8-1 introduces a skeletal activity that presents a simple text view. This view lists the items passed to it by the activity controller. It displays each item’s class and description.

This listing includes details for two distinct classes. The first class implements a simple text controller and is intended for use within a navigation hierarchy. It includes a view for presenting text and a handler that updates the calling UIActivity instance by sending activityDidFinish: when the user taps Done.

Adding a way for your activity to complete is important, especially when your controller doesn’t have a natural endpoint. When your action uploads data to an FTP server, you know when it completes. If it tweets, you know when the status posts. In this example, it’s up to the user to determine when this activity finishes. Make sure your view controller contains a weak property pointing back to the activity so that you can send the did-finish method after your work concludes.

The activity class contains a number of mandatory and optional items. I recommend you implement all the methods shown in this listing. The methods to support a custom activity include the following:

• activityType—Returns a unique string describing the type of activity. One of this string’s counterparts in the system-supplied activities is UIActivityTypePostToFacebook. Use a similar naming scheme. This string identifies a particular activity type, and what it does. In this listing, I return @"CustomActivityTypeListItemsAndTypes", which describes the activity.

• activityTitle—Supply the text you want to show in the activity controller. The custom text in Figure 8-7 was returned by this method. Use active descriptions when describing your custom action. Follow Apple’s lead; for example, Save to Camera Roll, Print, Copy. Your title should finish the phrase “I want to...” For example, “I want to Print,” “I want to Copy,” or, in this example, “I want to list items.” Use header case and capitalize each word except for minor ones like to or and.

Figure 8-7. Add your own custom application activities.

• activityImage—Return an image for the controller to use. The controller adds a backsplash and converts your image to a one-value bitmap, layering it on top. Use simple art on a transparent background to build the contents of your icon image.

• canPerformWithActivityItems:—Scan the passed items and decide whether your controller can process them. If so, return YES.

• prepareWithActivityItems:—Stores the passed items for later use (here, they’re assigned to a local instance variable) and performs any necessary pre-processing.

• activityViewController—Return a fully initialized presentable view controller using the activity items passed to you earlier. This controller is automatically presented to the user, where he or she can customize options before performing the promised action.

Adding custom activities allows your app to expand its data-handling possibilities while integrating features into a consistent system-supplied interface. It’s a powerful iOS feature. The strongest activity choices will integrate with system services (such as copying to the pasteboard or saving to the photo album) or provide a connection to off-device APIs, like Facebook, Twitter, Dropbox, and FTP.

This example, which simply lists items, represents a weak use-case. There’s no reason the same feature couldn’t be provided as a normal in-app screen. When you think actions, try to project outside the app. Connect your user’s data with sharing and processing features that expand beyond the normal GUI.

Listing 8-1. Application Activities

Click here to view code image

// All activities present a view controller. This custom controller
// provides a full-sized text view.
@interface TextViewController : UIViewController
@property (nonatomic, readonly) UITextView *textView;
@property (nonatomic, weak) UIActivity *activity;
@end

@implementation TextViewController

// Make sure you provide a done handler of some kind, such as this
// or an integrated button that finishes and wraps up
- (void) done
{
[_activity activityDidFinish:YES];
}

// Just a super-basic text view controller
- (id) init
{
if (!(self = [super init])) return nil;
_textView = [[UITextView alloc] init];
_textView.font = [UIFont fontWithName:@"Futura" size:16.0f];
_textView.editable = NO;

[self.view addSubview:_textView];
PREPCONSTRAINTS(_textView);
STRETCH_VIEW(self.view, _textView);

// Prepare a Done button
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Done", @selector(done));

return self;
}
@end

@interface MyActivity : UIActivity
@end
@implementation MyActivity
{
NSArray *items;
}

// A unique type name
- (NSString *)activityType
{
return @"CustomActivityTypeListItemsAndTypes";
}

// The title listed on the controller
- (NSString *) activityTitle
{
return @"Cookbook";
}

// A custom image, displayed as a bitmap over a textured background
// This one says "iOS" in a rounded rect edge
- (UIImage *) activityImage
{
CGRect rect = CGRectMake(0.0f, 0.0f, 75.0f, 75.0f);
UIGraphicsBeginImageContext(rect.size);
rect = CGRectInset(rect, 15.0f, 15.0f);
UIBezierPath *path = [UIBezierPath
bezierPathWithRoundedRect:rect cornerRadius:4.0f];
[path stroke];
rect = CGRectInset(rect, 0.0f, 10.0f);
[@"iOS" drawInRect:rect
withFont:[UIFont fontWithName:@"Futura" size:18.0f]
lineBreakMode:NSLineBreakByWordWrapping
alignment:NSTextAlignmentCenter];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();

return image;
}

// Specify if you can respond to these items
- (BOOL)canPerformWithActivityItems:(NSArray *)activityItems
{
return YES;
}

// Store the items locally for later use
- (void)prepareWithActivityItems:(NSArray *)activityItems
{
items = activityItems;
}

// Return a view controller, in this case one that lists
// its items and their classes
- (UIViewController *) activityViewController
{
TextViewController *tvc = [[TextViewController alloc] init];
tvc.activity = self;
UITextView *textView = tvc.textView;

NSMutableString *string = [NSMutableString string];
for (id item in items)
[string appendFormat:
@"%@: %@\n", [item class], [item description]];
textView.text = string;

// Make sure to provide some kind of done: handler in
// your main controller.
UINavigationController *nav = [[UINavigationController alloc]
initWithRootViewController:tvc];
return nav;
}
@end

Items and Services

The services presented for each item vary by the kind of data you pass. Table 8-1 lists offered activities by source data type. As you’ll see in the recipe that follows, preview controller support expands beyond these foundation types. iOS’s Quick Look framework integrates activity controllers into its file previews. The Quick Look-provided activity controller can print and e-mail many kinds of documents. Some document types support other activities as well.

Table 8-1. Activity Types for Data Types

Recipe: The Quick Look Preview Controller

The Quick Look preview controller class enables users to preview many document types. This controller supports text, images, PDF, RTF, iWork files, Microsoft Office documents (Office 97 and later, including DOC, PPT, XLS, and so on), and comma-separated value (CSV) files. You supply a supported file type, and the Quick Look controller displays it for the user. An integrated system-supplied activity view controller helps share the previewed document, as you can see in Figure 8-8.

Figure 8-8. This Quick Look controller was presented modally and shows the screen after the user has tapped the action button. Quick Look handles a wide range of document types, enabling users to see the file contents before deciding on an action to apply to them. Most Quick Look types support Mail and Print. Many support Copy, and image files offer even more options.

Either push or present your preview controllers. The controller adapts to both situations, working with navigation stacks and with modal presentation. Recipe 8-11 demonstrates both approaches.

Implementing Quick Look

Quick Look support requires just a few simple steps:

1. Declare the QLPreviewControllerDataSource protocol in your primary controller class.

2. Implement the numberOfPreviewItemsInPreviewController: and previewController:previewItemAtIndex: data source methods. The first of these methods returns a count of items to preview. The second returns the preview item referred to by the index.

3. Preview items must conform to the QLPreviewItem protocol. This protocol consists of two required properties: a preview title and an item URL. Recipe 8-11 creates a conforming QuickItem class. This class implements an absolutely minimal approach to support the data source.

Once these requirements are met, your code is ready to create a new preview controller, set its data source, and present or push it.

Recipe 8-11. Quick Look

Click here to view code image

@interface QuickItem : NSObject <QLPreviewItem>
@property (nonatomic, strong) NSString *path;
@property (readonly) NSString *previewItemTitle;
@property (readonly) NSURL *previewItemURL;
@end

@implementation QuickItem

// Title for preview item
- (NSString *) previewItemTitle
{
return @"Generated PDF";
}

// URL for preview item
- (NSURL *) previewItemURL
{
return [NSURL fileURLWithPath:_path];
}
@end

#define FILE_PATH   [NSHomeDirectory() \
stringByAppendingPathComponent:@"Documents/PDFSample.pdf"]

@interface TestBedViewController : UIViewController
<QLPreviewControllerDataSource>
@end

@implementation TestBedViewController
- (NSInteger) numberOfPreviewItemsInPreviewController:
(QLPreviewController *) controller
{
return 1;
}

- (id <QLPreviewItem>) previewController:
(QLPreviewController *) controller
previewItemAtIndex: (NSInteger) index;
{
QuickItem *item = [[QuickItem alloc] init];
item.path = FILE_PATH;
return item;
}

// Push onto navigation stack
- (void) push
{
QLPreviewController *controller =
[[QLPreviewController alloc] init];
controller.dataSource = self;
[self.navigationController
pushViewController:controller animated:YES];
}

// Use modal presentation
- (void) present
{
QLPreviewController *controller =
[[QLPreviewController alloc] init];
controller.dataSource = self;
[self presentViewController:controller
animated:YES completion:nil];
}

- (void) loadView
{
[super loadView];
self.view.backgroundColor = [UIColor whiteColor];

self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Push", @selector(push));
self.navigationItem.leftBarButtonItem =
BARBUTTON(@"Present", @selector(present));
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 8.

Summary

This chapter introduced a number of ready-to-use controllers that you can prepare and present to good effect. System-supplied controllers simplify programming for common tasks like tweeting and sending e-mail. Here are a few parting thoughts about the recipes you just encountered:

• Although you can roll your own versions of a few of these controllers, why bother? System-supplied controllers represent the rare cases where enforcing your own design takes a back seat to a consistency of user experience across applications. When a user sends an e-mail, he or she expects that e-mail compose screen to look basically the same regardless of application. Go ahead and leverage Apple system services to mail, tweet, and interact with the system media library.

• The image picker controller has grown to be a bit of a Frankenclass. It has long deserved a proper refresh and redesign. From controlling sources at a fine grain to reducing its memory overhead, the class deserves some loving attention from Apple. Now that so many great media processing classes have made the jump to iOS, I’d love to see better integration with AV Foundation, Core Media, and other key technologies—and not just through a visual controller. Although preserving user privacy is critical, it would be nice if the library opened up a more flexible range of APIs (with user-directed permissions, of course).

• The Social framework can do a lot more than post Facebook updates and tweets. The class lets you submit authenticated and unauthenticated service requests using appropriate security. Use the Accounts framework along with Social to retrieve login information for placing credentialed requests.

• When you’re looking for one-stop shopping for data file sharing, you’ll be hard-pressed to find a superior solution than activity view controllers. Easy to use, and simple to present, this single controller does the work of an army, integrating your app with iOS’s system-supplied services.

9. Accessibility

Accessibility enhancements open up the iPhone to users with disabilities. iPhone OS features enable users to magnify (or “zoom”) displays, invert colors, and more. As a developer, accessibility enhancement centers on VoiceOver, a way that visually impaired users can “listen” to their GUI. VoiceOver converts an application’s visual presentation into an audio description.

Don’t confuse VoiceOver with Voice Control or the Siri Assistant. The former is a method for presenting an audio description of a user interface and is highly gesture based. The latter refers to Apple’s proprietary voice-recognition technology for hands-free interaction.

This chapter briefly overviews VoiceOver accessibility. You read about adding accessibility labels and hints to your applications and testing those features in the simulator and on the iOS device. Accessibility is available and can be tested on third generation or later devices, including all iPads, the iPhone 3GS and later, and the third-generation iPod touch and later.

Accessibility 101

Create accessibility by adding descriptive attributes to your user interface elements. This programming interface is defined by the informal UIAccessibility protocol and consists of a set of properties including labels, hints, and values. Together, they act to supply information to VoiceOver to present an audible presentation of your interface.

Either assign these properties in code or add them through Interface Builder (IB). Listing 9-1 shows how you could set a button’s accessibilityHint property. This property describes how this control reacts to a user action. In this example, the button’s hint updates when a user types a username into a related text field. The hint changes to match the context of the current UI. Instead of a general hint about placing a call, the updated version directly names its target.

Listing 9-1. Programmatically Updating Accessibility Information

Click here to view code image

- (BOOL)textField:(UITextField *)textField
shouldChangeCharactersInRange:(NSRange)range
replacementString:(NSString *)string
{
// Catch the change to the username field and update
// the accessibility hint to mirror that
NSString *username = textField.text;
if (username && username.length > 1)
callbutton.accessibilityHint = [NSString
stringWithFormat:@"Places a call to %@", username];
else
callbutton.accessibilityHint =
@"Places a call to the person named in the text field.";
return YES;
}

The UIAccessibility protocol includes the following properties:

• accessibilityTraits—A set of flags that describe a UI element. These flags specify how items behave or how they should be treated by the interpretive system. For example, these traits include state (for example, selected) and behavior (for buttons, for example).

• accessibilityLabel—A short phrase or word that describes the view’s role or the control’s action (for example, Pause or Delete). Labels can be localized.

• accessibilityHint—A short phrase that describes what user actions do with this element (for example, Navigates to the home page). Hints can also be localized.

• accessibilityFrame—A rectangle specifically for nonview elements, describing how the object should be represented onscreen. Views use their normal UIView frame property.

• accessibilityValue—The value associated with an object, such as a slider’s current level (for example, 75%) or a switch’s on/off setting (for example, ON).

Accessibility in Interface Builder

The Interface Builder (IB) Identity Inspector > Accessibility pane (see Figure 9-1) enables you to add accessibility details to UIKit elements in your interface. These fields and the text they contain play different roles in the accessibility picture. There’s a one-to-one correlation with the UIAccessibility protocol and the inspector elements presented in IB. As with direct code access, labels identify views; hints describe them. In addition to these fields, you’ll find a general accessibility Enabled check box and a number of Traits check boxes.

Figure 9-1. IB’s Identity Inspector lets you specify object accessibility information.

Enabling Accessibility

The Enabled check box controls whether a UIKit view works with VoiceOver. To declare an element’s accessibility support, assign the isAccessibilityElement property to YES, or check the Accessibility Enabled box in IB. (You can see this check box in Figure 9-1.) This Boolean property allows GUI elements to participate in the accessibility system. By default, all UIControl instances inherit a value of YES.

As a rule, enable accessibility unless the view is a container whose subviews need to be accessible. Enable only those items at the most direct level of interaction or presentation. Views that organize other views don’t play a meaningful role in the voice presentation. Exclude them.

Table view cells offer a good example of accessibility containers (that is, objects that contain other objects). The rules for table view cells are as follows:

• A table view cell without embedded controls should be accessible.

• A table view cell with embedded controls should not be. Its child controls should be.

Nonaccessible containers are responsible for reporting how many accessible children they contain and which child views those are. See Apple’s Accessibility Programming Guide for iPhone for further details about programming containers for accessibility. Custom container views need to declare and implement the UIAccessibilityContainer protocol.

Traits

Traits characterize UIKit item behaviors. VoiceOver uses these traits while describing interfaces. As Figure 9-1 shows, there are numerous possible traits you can assign to views. Select the traits that apply to the selected view, keeping in mind that you can always update these choices programmatically.

Traits help characterize the elements in your interface to the VoiceOver system. They specify how items behave and how they should be treated by VoiceOver. You set them via the accessibilityTraits property, by OR’ing together one or more flags. You can also set them in IB, as you saw in Figure 9-1. These traits vary in how they operate and how VoiceOver uses them.

At the most basic, default level, there’s the “no trait” flag:

• UIAccessibilityTraitNone—The element has no traits.

Then there are the flags that describe what the user element is. These include the following:

• UIAccessibilityTraitButton—The element is a button.

• UIAccessibilityTraitLink—The element is a hyperlink.

• UIAccessibilityTraitSearchField—The element is a search field.

• UIAccessibilityTraitImage—The element is a picture.

• UIAccessibilityTraitKeyboardKey—The element acts as a keyboard key.

• UIAccessibilityTraitStaticText—The element is unchanging text.

Apple’s accessibility documents request that you only check one of the following four mutually exclusive items at any time: Button, Link, Static Text, or Search Field. If a button works as a link as well, choose either the button trait or the link trait but not both. You choose which best characterizes how that button is used. At the same time, a button might show an image and play a sound when tapped, and you can freely add those traits.

There are a few basic state flags, which discuss the selection, adjustability, and interactive state of the object:

• UIAccessibilityTraitSelected—The element is currently selected, such as in a segmented control or the selected row in a table.

• UIAccessibilityTraitNotEnabled—The element is disabled from user interaction.

• UIAccessibilityTraitAdjustable—The element can be set to multiple values, as with a slider or picker. You specify how much each interaction adjusts the current value by implementing the accessibilityIncrement and accessibilityDecrement methods.

• UIAccessibilityTraitAllowsDirectInteraction—The user can touch and interact with the element.

If an element plays a sound when interacted with, there’s a flag for that as well:

• UIAccessibilityTraitPlaysSound—The element will play a sound when activated.

Finally, a handful of states caution and describe how the element behaves and interacts with the larger world:

• UIAccessibilityTraitUpdatesFrequently—The element changes often enough that you won’t want to overburden the user with its state changes, such as the readout of a stopwatch.

• UIAccessibilityTraitStartsMediaSession—The element begins a media session. Use this to limit VoiceOver interruptions when playing back or recording audio or video.

• UIAccessibilityTraitSummaryElement—The element provides summary information when the application starts, such as the current settings or state.

• UIAccessibilityTraitCausesPageTurn—The element should automatically turn the page once VoiceOver finishes reading its text.

As Figure 9-1 demonstrates, most of these traits (but not all) can be toggled off or on through the IB identity inspector pane. If you need finer-level control, set the flags in code.

Labels

You set an element’s label by assigning its accessibilityLabel property. A good label tells the user what an item is, often with a single word. Label an accessible GUI the same way you’d label a button with text. Edit, Delete, and Add all describe what objects do. They’re excellent button text and accessibility label text.

But accessibility isn’t just about buttons. Feedback, User Photo, and User Name might describe the contents and function of a text view, an image view, and a text label. If an object plays a visual role in your interface, it should play an auditory role in VoiceOver. Here are a few tips for designing your labels:

• Do not add the view type into the label. For example, don’t use “Delete button,” “Feedback text view,” or “User Name text field.” VoiceOver adds this information automatically, so “Delete button” in the identity pane becomes “Delete button button” in the actual VoiceOver playback.

• Capitalize the label but don’t add a period. VoiceOver uses your capitalization to properly inflect the label when it speaks. Adding a period typically causes VoiceOver to end the label with a downward tone, which does not blend well into the object-type that follows. “Delete. button” sounds wrong. “Delete button” does not.

• Aggregate information. When working with complex views that function as a single unit, build all the information in that view into a single descriptive label and attach it to that parent view. For example, in a table view cell with several subviews but without individual controls, you might aggregate all the text information into a single label that describes the entire cell.

• Label only at the lowest interaction level. When users need to interact with subviews, label at that level. Parent views, whose children are accessible, do not need labels.

• Localize. Localizing your accessibility strings opens them up to the widest audience of users.

Hints

Assign the accessibilityHint property to set an element’s hint. Hints tell users what to expect from interaction. In particular, they describe any nonobvious results. For example, consider an interface where tapping on a name—for example, John Smith—attempts to call that person by telephone. The name itself offers no information about the interaction outcome. So, offer a hint telling the user about it—for example, “Places a phone call to this person,” or even better, “Places a phone call to John Smith.” Here are tips for building better hints:

• Use sentence form. Start with a capital letter and end with a period. Do this even though each hint has a missing subject (for example, “Clears text in the form”). Here, the missing subject is “[This button].” Sentence format ensures that VoiceOver speaks the hint with proper inflection.

• Use verbs that describe what the element does, not what the user does. “[This text label] Places a phone call to this person.” provides the right context for the user. “[You will] Place a phone call to this person.” does not.

• Do not say the name or type of the GUI element. Avoid hints that refer to the UI item being manipulated. Skip the GUI name (its label, such as “Delete”) and type (its class, such as “button”). VoiceOver adds that information where needed, preventing any overly redundant playback, such as “Delete button [label] button [VoiceOver description] button [hint] removes item from screen.” Use “Removes item from screen.” instead.

• Avoid the action. Do not describe the action that the user takes. Do not say “Swiping places a phone call to this person” or “Tapping places a phone call to this person.” VoiceOver uses its own set of gestures to activate GUI elements. Never refer to gestures directly.

• Be verbose. “Place call” does not describe the outcome as well as “Place a call to this person,” or, even better, “Place a call to John Smith.” A short but thorough explanation better helps the user than one that is so terse that the user has to guess about details. Avoid hints that require the user to listen again before proceeding.

• Localize. As with labels, localizing your accessibility hints works with the widest user base.

Testing with the Simulator

The iOS simulator’s Accessibility Inspector is designed for testing accessible applications before deploying them to the iOS device. The simulator’s inspector simulates VoiceOver interaction with your application, providing immediate visual feedback via a floating pane (there is no actual voice produced) without having to use the VoiceOver gesture interface directly. Because you cannot replicate many VoiceOver gestures with the simulator (such as triple-swipes and sequential hold-then-tap gestures), the inspector focuses on describing interface items rather than responding to VoiceOver gestures.

Enable this feature by opening Settings > General > Accessibility. Switch the Accessibility Inspector to On. The inspector, shown in Figure 9-2, immediately appears. It lists the current settings for the currently selected accessible element.

Figure 9-2. The iPhone simulator’s Accessibility Inspector highlights the currently selected GUI feature, revealing its label, hint, and other accessibility properties.

Know how to enable and disable the inspector: The circled X in the top-left corner of the inspector controls that behavior. Click it once to shrink the inspector to a disabled single line. Click again to restore the inspector to active mode. For the most part, keep the inspector disabled until you actually need to inspect a GUI item. Figure 9-3 shows a button interface as described in the Accessibility Inspector.

Figure 9-3. The Accessibility Inspector reflects the values set either in IB or in code that describe the currently selected item.

Like VoiceOver, the inspector interferes with normal application gestures. It will slow down your work, so use it sparingly (normally when you are ready to test). You want to launch your application with the inspector disabled but available. Navigate to the screen you want to work with and then enable the inspector.

When you update accessibility hints in code, the inspector updates in real time to match those changes. Activating the inspector allows you to view the current hint as those changes happen, ensuring that the onscreen hints and labels reflect the up-to-date interface.

Broadcasting Updates

Your application should post notifications to let the VoiceOver accessibility system know about onscreen element changes outside of direct user interaction:

• When you add or remove a GUI element. The UIAccessibilityLayoutChangedNotification gives the VoiceOver accessibility system a heads up about those changes.

• Applications can post a UIAccessibilityPageScrolledNotification after completing a scroll action. The notification’s object should contain a description of the new scroll position (for example, “Page 5 of 17” or “Tab 2 of 4”).

• When the zoomed frame changes, send a UIAccessibilityZoomFocusChanged notification. Include a user dictionary with type, frame, and view parameters. These parameters specify the type of zoom that has taken place, the currently zoomed frame (in screen coordinates), and the view that contains the zoomed frame.

In addition to these change updates, you can broadcast general announcements through the accessibility VoiceOver system. The UIAccessibilityAnnouncement-Notification takes one parameter, a string, which contains an announcement. Use this to notify users when tiny GUI changes take place, or changes that only briefly appear on screen, or for changes that don’t affect the user interface directly.

Testing Accessibility on the iPhone

Testing on the iPhone is a critical part of accessibility development. The iPhone enables you to work with the actual VoiceOver utility rather than a window-based inspector. You hear what your users will hear and can test your GUI with your fingers and ears rather than with your eyes.

Like the simulator, the iPhone provides a way to enable and disable VoiceOver on-the-fly. Although you can enable VoiceOver in Settings and then test your application with VoiceOver running, you’ll find that it’s much easier to use a special toggle. The toggle lets you avoid the hassle of navigating out of Settings and over to your application using VoiceOver gestures. You can switch VoiceOver off, use normal iPhone interactions to get your application started, and then switch VoiceOver back on when you’re ready to test.

To enable that toggle, follow these steps:

1. Go to the Accessibility settings pane. Navigate to Settings > General > Accessibility.

2. Locate the Triple-click Home choice. The Triple-click Home button provides a user-settable shortcut for accessibility choices. Tap Triple-click Home to open the Home pane.

3. Choose Toggle VoiceOver. Select Toggle VoiceOver to set it as your triple-click action. Once selected (a check appears to its right), you can enable and disable VoiceOver by triple-clicking the physical Home button at the bottom of your iPhone. A spoken prompt confirms the current VoiceOver setting.

This VoiceOver toggle offers you the ability to skip many of the laborious details involved in navigating to your application using triple-fingered drags, and multistage button clicks. At the same time, you should be conversant with VoiceOver gestures and interactions. Table 9-1 summarizes the VoiceOver gestures that you need to know to test your application.

Table 9-1. Common VoiceOver Gestures for Applications

Take special note of ScreenCurtain, which enables you to blank your iPhone display, offering a true test of your application as an audio-based interface. Try the iPhone calculator application with ScreenCurtain enabled to gain a true sense of the challenge of using an iPhone application without sight.

Summary

When an iPhone application opens itself to Accessibility, it becomes an active participant in a wider ecosystem, with a larger potential user base. Here are a few thoughts to take with you:

• Including accessibility labels and hints create new audiences for your application, just as language localizations do. Adding these takes relatively little work to achieve and offers excellent payoffs to your users.

• Keep the role of labels and hints in mind as you prepare an auditory description of your application in IB.

• Don’t be afraid to change hints programmatically. Let your interface descriptions update as your interface does, to provide the best possible experience for your visually impaired users.

• iOS’s accessibility system is an evolving system. Keep on top of Apple’s documentation to find the latest updates and changes.

• Test with ScreenCurtain. A blank screen provides the best approximation of the VoiceOver user experience.

10. Creating and Managing Table Views

Tables provide a scrolling list-based interaction class that works particularly well for small GUI elements. Many apps that ship natively with the iPhone and iPod touch center on table-based navigation, including Contacts, Settings, and iPod. On these smaller iOS devices, limited screen size makes tables, with their scrolling and individual item selection, an ideal way to deliver information and content in a simple, easy-to-manipulate form. On the larger iPad, tables integrate with larger detail presentations, providing an important role in split view controllers. In this chapter, you discover how iOS tables work, what kinds of tables are available to you as a developer, and how you can use table features in your own programs.

iOS Tables

The standard iOS table consists of a simple vertical scrolling list of individual cells. Users may scroll or flick their way up and down until they find an item they want to interact with. On iOS, tables are ubiquitous. Several built-in iOS apps are based entirely on table views, and they form the core of numerous third-party applications.

Almost all tables you will ever see in iOS are built using UITableView and customized with options provided by its delegate and data source protocols. In addition to a standard scrolling list of cells, which provides the most generic table implementation, you can create specialized tables with custom art, background, labels, and more.

Specialized tables include the kind of tables you see in the Preferences application, with their blue-gray background and rounded cell edges; tables with sections and an index, such as the ones used in the Contacts application; and related classes of wheeled tables, such as those used to set appointment dates and alarms. And, when you need to move beyond tables and their scrolling lists to more grid-like presentations, you can use the related class of collection views, which are introduced in Chapter 11, “Collection Views.”

No matter what type of table you use, they all work in the same general way. Tables are built around the Model-View-Controller (MVC) paradigm. They present cells provided from a data source and respond to user interactions by calling well-defined delegate methods.

A data source provides a class with on-demand information about a table’s contents. It represents the underlying data model and mediates between that model and the table’s view. A data source tells the table about its structure. For example, it specifies how many sections to use and how many items each section includes. Data sources provide individual table cells on-demand and they populate those cells with model data that matches each cell’s position and ownership within the table.

Data sources express a table’s model; delegates act as controllers. Delegates manage user interactions, letting applications respond to changes in table selections and user-directed edits. For example, users might tap on a new cell to select it, reorder a cell to a new position, or add and delete cells. Delegates monitor these user interaction requests and react by allowing and disallowing those requests, and by updating the data model in response to successful actions.

Together the view, data source, and delegate work together to express an MVC development pattern. This pattern is not limited to table views. You see this view/data source/delegate approach used in a number of key iOS classes. Picker views, collection views, and page view controllers all use data sources and delegates.

Delegation

Table view data sources and delegates are examples of delegation, assigning responsibility for specific activities and information to a secondary object. Several UIKit classes use delegation to respond to user interactions and to providing content. For example, when you set a table’s delegate, you tell it to pass along any interaction messages and let that delegate take responsibility for them.

Table views are a good example of delegation. When a user taps on a table row, the UITableView instance has no built-in way of responding to that tap. The class is general purpose and it provides no native semantics for taps. Instead, it consults its delegate—usually a view controller class—and passes along the selection change. You add meaning to the tap at a point of time completely separate from when Apple created the table class. Delegation allows classes to be created without specific meaning while ensuring that application-specific handlers can be added at a later time.

The UITableView delegate method tableView:didSelectRowAtIndexPath: provides a typical delegation example. A delegate object defines this method and specifies how the app should react to a row change initiated by the user. You might display a menu or navigate to a subview or place a check mark on the tapped row. The response depends entirely on how you implement the delegated selection change method. None of this was known at the time the table class was implemented.

To set an object’s delegate or data source, assign its delegate or dataSource property. This instructs your application to redirect interaction callbacks to the assigned object. You let Objective-C know that your object implements delegation calls by declaring the protocol or protocols it implements in the class declaration. This declaration appears in angle brackets, to the right of the class inheritance (for example, <UITableViewDelegate> or <UITableViewDataSource>). When declaring multiple protocols, separate them by commas within a single set of angle brackets (for example, <UITableViewDelegate, UITableViewDataSource>). A class that declares a protocol is responsible for implementing all required methods associated with that protocol and may implement any or all of the optional methods as well.

Creating Tables
iOS includes two primary table classes: a prebuilt controller class (UITableViewController) and a direct view (UITableView). The controller offers a view controller subclass customized for tables. It includes an established table view that takes up the entire controller view and it eliminates repetitive tasks required for working with table instances. Specifically, it declares all the necessary protocols and defines itself as its table’s delegate and data source. When using a table view outside of the controller class, you’ll need to perform these tasks manually. The table view controller takes care of them for you.

Table Styles

On the iPhone, tables come in two formats: grouped tables and plain table lists. The iOS Settings application uses a grouped style. These lists display on a blue-gray background, and each subsection appears within a slightly rounded rectangle.

To change styles requires nothing more than initializing the table view controller with a different style. You can do this explicitly when creating a new instance. Here’s an example:

Click here to view code image
myTableViewController = [[UITableViewController alloc]
initWithStyle:UITableViewStyleGrouped];

When using controllers from nibs and storyboards, adjust the Table View > Style property in the attributes inspector.

Laying Out the View

UITableView instances are, as the name suggests, views that present interactive tables on the iOS screen. The UITableView class descends from the UIScrollView class. This inheritance provides the up and down scrolling capabilities used by the table. Like other views, UITableView instances define their boundaries through frames, and they can be children or parents of other views. To create a table view, you allocate it, initialize it with a frame, and then add all the bookkeeping details by assigning data source and delegate objects.

UITableViewControllers take care of the view layout work for you. The class creates a standard view controller and populates it with a single UITableView, setting its frame to allow for any navigation bars or toolbars, and so on. You may access that table view via the tableView instance variable.

Assigning a Data Source

UITableView instances rely on an external source to feed either new or existing table cells on demand. Cells are small views that populate the table, adding row-based content. This external source is called a data source and refers to the object whose responsibility it is to return a cell on request to a table.

The table’s dataSource property sets an object to act as a table’s source of cells and other layout information. That object declares and must implement the UITableViewDataSource protocol. In addition to returning cells, a table’s data source specifies the number of sections in the table, the number of cells per section, any titles associated with the sections, cell heights, an optional table of contents, and more. The data source defines how the table looks and the content that populates it.

Typically, the view controller that owns the table view acts as the data source for that view. When working with UITableViewController subclasses, you need not declare the protocol because the parent class implicitly supports that protocol and automatically assigns the controller as the data source.

Serving Cells

The table’s data source populates the table with cells by implementing the tableView:cell-ForRowAtIndexPath: method. Any time the table’s reloadData method is invoked, the table starts querying its data source to load the actual onscreen cells into your table. Your code can call reloadData at any time to force the table to reload its contents.

Data sources provide table cells based on an index path, which is passed as a parameter to the cell request method. Index paths, objects of the NSIndexPath class, describe the path through a data tree to a particular node—namely their section and their row. You can create an index path by supplying a section and row:

myIndexPath = [NSIndexPath indexPathForRow:5 inSection:0];

Tables use sections to split data into logical groups and rows to index members within each group. It’s the data source’s job to associate an index path with a concrete UITableViewCell instance and return that cell on demand.

Registering Cell Classes

You’ll want to register any cell type you work with early in the creation of your table view. Registration allows cell dequeuing methods to automatically create new cells for you. Typically, you register cells in your loadView or viewDidLoad methods. Be sure that this registration takes place before the first time your table attempts to load its data. Each table view instance registers its own types. You supply an arbitrary string identifier, which you use as a key when requesting new cells.

You can register by class (starting in iOS 6) or by nibs (iOS 5 and later). Here are examples of both approaches:

Click here to view code image

[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"table cell"];
[self.tableView registerNib:
[UINib nibWithNibName:@"CustomCell" bundle:[NSBundle mainBundle]]
forCellReuseIdentifier:@"custom cell"];

Register as many kinds of cells as you need. You are not limited to one type per table. Mix and match cells within a table however your design demands.

Dequeueing Cells

Your data source responds to cell requests by building cells from code or it can load its cells from Interface Builder sources. Here’s a minimal data source method that returns a cell at the requested index path, labeling it with text derived from its data model:

Click here to view code image

- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.textLabel.text = [dataModel objectAtIndexPath:indexPath].text;
return cell;
}

If you’re an established iOS developer, note that you no longer need to check to see whether the queue already has an existing cell of the type you requested. The queue now transparently creates and initializes new instances as needed.

Use the dequeuing mechanism to request cells. As cells scroll off the table and out of view, the table caches them into a queue, ready for reuse. This mechanism returns any available table cells stored in the queue; when the queue runs dry, it creates and returns new instances.

Registering cells for reuse provides each instance with an identifier tag. The table searches for that type and pops them off the queue as needed. This saves memory and provides a fast, efficient way to feed cells when users scroll quickly through long lists onscreen.

Assigning a Delegate

Like many other Cocoa Touch interaction objects, UITableView instances use delegates to respond to user interactions and implement a meaningful response. Your table’s delegate can respond to events such as the table scrolling, user edits, or row selection changes. Delegation allows the table to hand off responsibility for reacting to these interactions to the object you specify, typically the controller object that owns the table view.

If you’re working directly with a UITableView, assign the delegate property to a responsible object. The delegate declares the UITableViewDelegate protocol. As with data sources, you may skip setting the delegate and declaring the protocol when working with UITableViewController or its custom subclass.

Recipe: Implementing a Basic Table

A basic table implementation consists of little more than a set of data used to label cells and a few methods. Recipe 10-1 provides about as basic a table as you can imagine. It creates the flat (nonsectioned) table shown in Figure 10-1. Each cell includes a text label and an image consisting of the cell’s row number inside a box.

Figure 10-1. This basic table view is built by Recipe 10-1.

Users can tap on cells. When they do so, the controller’s title updates to match the selected item. A Deselect button tells the table to remove the current selection and reset the title; a Find button moves the selection into view, even if it’s been scrolled off screen.

This implementation attempts to scroll the “found” selection to the top (UITableViewScrollPositionTop) space permitting. Zulu, the last item in this table, cannot scroll any higher than the bottom of the view because you simply run out of table after its cell.

Data Source Methods

To display a table, even a basic flat one like Recipe 10-1, every table data source implements three core instance methods. These methods define how the table is structured and provide contents for the table:

• numberOfSectionsInTableView—Tables can display their data in sections or as a single list. For flat tables, return 1. This indicates that the entire table should be presented as one single list. For sectioned lists, return a value of 2 or higher.

• tableView:numberOfRowsInSection:—This method returns the number of rows for each section. Recipe 10-1’s flat list returns the number of rows for the entire table. For more complex lists, you’ll want to provide a way to report back per section. Core Data provides especially simple sectioned table integration, as you’ll read about in Chapter 12, “A Taste of Core Data.” As with all counting in iOS, section ordering starts with 0 as the first section.

• tableView:cellForRowAtIndexPath:—This method returns a cell to the calling table. Use the index path’s row and section properties to determine which cell to provide and make sure to take advantage of reusable cells where possible to minimize memory overhead.

Responding to User Touches

Recipe 10-1 responds to users in the tableView:didSelectRowAtIndexPath: delegate method. This recipe’s implementation updates the view controller’s title and enables both bar buttons for searching and deselecting. These buttons remain enabled as long as there’s a valid selection. Should the user choose the Deselect option, this code calls deselectRowAtIndexPath: animated: and disables both buttons.

Note

When you want the table to ignore user touches, set a cell’s selectionStyle property to UITableViewCellSelectionStyleNone. This disables the blue or gray overlays that display on the selected cell. The cell is still selected but will not highlight on selection in any way. If selecting your cell produces some kind of side effect other than presenting information, this is not the best way to approach things.

Recipe 10-1. Building a Basic Table

Click here to view code image

@implementation TestBedViewController

// Number of sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
return 1;
}

// Rows per section
- (NSInteger)tableView:(UITableView *)aTableView
numberOfRowsInSection:(NSInteger)section
{
return items.count;
}

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

// Cell label
cell.textLabel.text = [items objectAtIndex:indexPath.row];

// Cell image
NSString *indexString = [NSString stringWithFormat:@"%02d", indexPath.row];
cell.imageView.image = stringImage(indexString, imageFont, 6.0f);

return cell;
}

// On selection, update the title and enable find/deselect
- (void)tableView:(UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[self.tableView cellForRowAtIndexPath:indexPath];
self.title = cell.textLabel.text;
self.navigationItem.rightBarButtonItem.enabled = YES;
self.navigationItem.leftBarButtonItem.enabled = YES;
}

// Deselect any current selection
- (void) deselect
{
NSArray *paths = [self.tableView indexPathsForSelectedRows];
if (!paths.count) return;

NSIndexPath *path = paths[0];
[self.tableView deselectRowAtIndexPath:path animated:YES];
self.navigationItem.rightBarButtonItem.enabled = NO;
self.navigationItem.leftBarButtonItem.enabled = NO;
self.title = nil;
}

// Move to the selection
- (void) find
{
[self.tableView scrollToNearestSelectedRowAtScrollPosition:
UITableViewScrollPositionTop animated:YES];
}

// Set up table
- (void) loadView
{
[super loadView];

self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Deselect", @selector(deselect));
self.navigationItem.leftBarButtonItem =
BARBUTTON(@"Find", @selector(find));
self.navigationItem.rightBarButtonItem.enabled = NO;
self.navigationItem.leftBarButtonItem.enabled = NO;

imageFont = [UIFont fontWithName:@"Futura" size:18.0f];

[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
items = [@"Alpha Bravo Charlie Delta Echo Foxtrot Golf Hotel \
India Juliet Kilo Lima Mike November Oscar Papa Romeo Quebec \
Sierra Tango Uniform Victor Whiskey Xray Yankee Zulu"
componentsSeparatedByString:@" "];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Table View Cells

The UITableViewCell class offers four utilitarian base styles, which are shown in Figure 10-2. This class provides two text label properties: a primary textLabel and a secondary detailTextLabel, which is used for creating subtitles. The four styles are as follows:

• UITableViewCellStyleDefault—This cell offers a single left-aligned text label and an optional image. When images are used, the label is pushed to the right, decreasing the amount of space available for text. You can access and modify the detailTextLabel, but it is not shown onscreen.

• UITableViewCellStyleValue1—This cell style offers a large black primary label on the left side of the cell and a slightly smaller, blue subtitle detail label to its right.

• UITableViewCellStyleValue2—This kind of cell consists of a small blue primary label on the left and a small black subtitle detail label to its right. The small width of the primary label means that most text will be cut off by an ellipsis. This cell does not support images.

• UITableViewCellStyleSubtitle—This cell pushes the standard text label up a bit to make way for the smaller detail label beneath it. The detail label displays in gray. Like the default cell, the subtitle cell offers an optional image.

Figure 10-2. Cocoa Touch provides four standard cell types, several of which support optional images.

Selection Color

Tables enable you to set the color for the selected cell by choosing between a blue or gray overlay. Set the selectionStyle property to either UITableViewCellSelectionStyleBlue or UITableViewCellSelectionStyleGray. If you’d rather not show a selection, use UITableViewCellSelectionStyleNone. The cell can still be selected, but the overlay color will not display.

Adding in Custom Selection Traits

When users select cells, Cocoa Touch helps you emphasize the cell’s selection. Customize a cell’s selection behavior by updating its traits to stand out from its fellows. There are two ways to do this.

The selectedBackgroundView property allows you to add controls and other views to just the currently selected cell. This works in a similar manner to the accessory views that appear when a keyboard is shown. You might use the selected background view to add a preview button or a purchase option to the selected cell.

The cell label’s highlightedTextColor property lets you choose an alternative text color when the cell is selected.

Recipe: Creating Checked Table Cells

Accessory views expand normal UITableViewCell functionality. Check marks create interactive one-of-n or n-of-n selections, as shown in Figure 10-3. With these kinds of selections, you can ask your users to pick what they want to have for dinner or choose which items they want to update.

Figure 10-3. Check mark accessories offer a convenient way of making one-of-n or n-of-n selections from a list.

To check an item, use the UITableViewCellAccessoryCheckmark accessory type. Unchecked items use the UITableViewCellAccessoryNone variation. You set these by assigning the cell’s accessoryType property.

Cells have no “memory” to speak of. They do not know how an application last used them. They are views and nothing more. That means if you reuse cells without tying those cells to some sort of data model, you can end up with unexpected and unintentional results. This is a natural consequence of the MVC design paradigm.

Consider the following scenario. Say you created a series of cells, each of which owned a toggle switch. Users can interact with that switch and change its value. A cell that scrolls offscreen, landing on the reuse queue, could therefore show an already toggled state for a table element that user hasn’t yet touched.

To fix this problem, always check your cell state against a stored model and fully configure your cell in cellForRowAtIndexPath:. This keeps the view consistent with your application semantics and avoids lingering “dirty” state from the cell’s last use. It’s the cell that’s being checked, not the logical item associated with the cell. Reused cells may remain checked or unchecked at next use, so you always set the accessory to match the model state, not the cell state.

Recipe 10-2 builds a simple state dictionary to store the on/off state for each index path. Its data source returns cells initialized to match that dictionary. You can easily expand this recipe to store its state to user defaults so it persists between runs. This simple-to-add enhancement is left as an exercise for the reader.

Recipe 10-2. Accessory Views and Stored State

Click here to view code image

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

// Cell label
cell.textLabel.text = [items objectAtIndex:indexPath.row];
BOOL isChecked = ((NSNumber *)stateDictionary[indexPath]).boolValue;
cell.accessoryType =  isChecked ?
UITableViewCellAccessoryCheckmark :
UITableViewCellAccessoryNone;

return cell;
}

// On selection, update the title
- (void)tableView:(UITableView *)aTableView didSelectRowAtIndexPath:(NSIndexPath *)
indexPath
{
UITableViewCell *cell =
[self.tableView cellForRowAtIndexPath:indexPath];

// Toggle the cell checked state
BOOL isChecked =
!((NSNumber *)stateDictionary[indexPath]).boolValue;
stateDictionary[indexPath] = @(isChecked);
cell.accessoryType = isChecked ?
UITableViewCellAccessoryCheckmark :
UITableViewCellAccessoryNone;

// Count the checked items
int numChecked = 0;
for (int row = 0; row < items.count; row++)
{
NSIndexPath *path = INDEXPATH(0, row);
isChecked = ((NSNumber *)stateDictionary[path]).boolValue;
if (isChecked) numChecked++;
}

self.title = [@[@(numChecked).stringValue, @" Checked"]
componentsJoinedByString:@" "];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Working with Disclosure Accessories

Disclosures refer to those small, blue or gray, right-facing chevrons found on the right of table cells. Disclosures help you to link from a cell to a view that supports that cell. In the Contacts list and Calendar applications on the iPhone and iPod touch, these chevrons connect to screens that help you to customize contact information and set appointments. Figure 10-4 shows a table view example where each cell displays a disclosure control, showing the two available types.

Figure 10-4. The right-pointing chevrons indicate disclosure controls, allowing you to link individual table items to another view.

On the iPad, you should consider using a split view controller rather than disclosure accessories. The greater space on the iPad display allows you to present both an organizing list and its detail view at the same time, a feature that the disclosure chevrons attempt to mimic on the smaller iPhone units.

The blue and gray chevrons play two roles:

• The blue UITableViewCellAccessoryDetailDisclosureButton versions are actual buttons. They respond to touches and are supposed to indicate that the button leads to a full interactive detail view.

• The gray UITableViewCellAccessoryDisclosureIndicator does not track touches and should lead your users to a further options view—specifically, options about that choice.

You see these two accessories in play in the Settings application. In the Wi-Fi Networks screen, the detail disclosures lead to specific details about each Wi-Fi network: its IP address, subnet mask, router, Domain Name System (DNS), and so forth. The disclosure indicator for Other enables you to add a new network by scrolling up a screen for entering network information. A new network then appears with its own detail disclosure.

You also find disclosure indicators whenever one screen leads to a related submenu. When working with submenus, stick to the simple gray chevron. The rule of thumb is this: Submenus use gray chevrons, and object customization uses blue ones. Respond to cell selection for gray chevrons and to accessory button taps for blue chevrons. Unfortunately, Apple itself uses these inconsistently. In Wi-Fi settings, the Other... option’s disclosure indicator opens a modal detail settings view for entering network information.

The following snippet sets the accessoryType for each cell to UITableViewCellAccessory-DetailDisclosureButton. More important, it also sets editingAccessoryType to UITableViewCellAccessoryNone. When delete or reorder controls appear, your disclosure chevron will hide, enabling your users full control over their edits without accidentally popping over to a new view:

Click here to view code image

- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:@"CustomCell"];
cell.accessoryType =
UITableViewCellAccessoryDetailDisclosureButton;
cell.editingAccessoryType = UITableViewCellAccessoryNone;

return cell;
}

// Respond to accessory button taps
-(void) tableView:(UITableView *)tableView
accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath
{
// Do something here
}

To handle user taps on the disclosure, the tableView:accessoryButtonTappedForRowWithIndexPath: method enables you to determine the row that was tapped and implement some appropriate response. In real life, you’d move to a view that explains more about the selected item and enables you to choose from additional options.

Gray disclosures use a different approach. Because these accessories are not buttons, they respond to cell selection rather than the accessory button tap. Add your logic to tableView:didSelectRowAtIndexPath: to push the disclosure view onto your navigation stack or by presenting a modal view controller or an alert view.

Neither disclosure accessory changes the way the rest of the cell works. Even when sporting accessories, you can select cells, edit cells, and so forth. Accessories add an extra interaction modality; they don’t replace the ones you already have.

Recipe: Table Edits

Bring your tables to life by adding editing features. Table edits transform static information display into an interactive scrolling control that invites your user to add and remove data. Although the bookkeeping for working with table edits is moderately complex, the same techniques easily transfer from one app to another. Once you master the basic elements of entering and leaving edit mode and supporting undo, you can use these items over and over.

Recipe 10-3 introduces a table that responds meaningfully to table edits. This example creates a scrolling list of random images. Users create new cells by tapping Add and may remove cells either by swiping or entering edit mode (tapping Edit) and using the red remove controls (see Figure 10-5).

Figure 10-5. Red remove controls allow your users to interactively delete items from a table.

In day-to-day use, every iOS user quickly becomes familiar with the small, red circles that delete cells from tables. Many users also pick up on basic swipe-to-delete functionality. This recipe also adds move controls, those triples of small, gray, horizontal lines, which allow users to drag items to new positions. Users leave edit mode by tapping Done.

Adding Undo Support

Cocoa Touch offers the NSUndoManager class to provide a way to reverse user actions. By default, every application window provides a shared undo manager. You can use this shared manager or create your own.

All children of the UIResponder class can find the nearest undo manager in the responder chain. This means that if you use the window’s undo manager in your view controller, the controller automatically knows about that manager through its undoManager property. This is enormously convenient because you can add undo support in your main view controller, and all your child views basically pick up that support for free.

The manager can store an arbitrary number of undo actions. You may want to specify how deep that stack goes. The bigger the stack, the more memory you will use. Many applications allow three, five, or ten levels of undo when memory is tight. Each action can be complex, involving groups of undo activities, or the action can be simple as in the examples shown in this recipe.

This recipe uses an undo manager to support user undo- and redo-actions for adding, deleting, and moving cells. These Undo and Redo options enable users to move through their edit history. In this recipe, these buttons are enabled when the undo manager supplies actions to support their use.

Supporting Undo

Both adding and deleting items in Recipe 10-3 are handled by the same method, updateItemAtIndexPath:withObject:. The method works like this: it inserts any non-nil object at the index path. When the passed object is nil, it instead deletes the item at that index path.

This might seem like an odd way to handle requests, because it involves an extra method and extra steps, but there’s an underlying motive. This approach provides a unified foundation for undo support, allowing simple integration with undo managers.

The method, therefore, has two jobs to do. First, it prepares an undo invocation. That is, it tells the undo manager how to reverse the edits it is about to apply. Second, it applies the actual edits, making its changes to the items array and updating the table and bar buttons.

The setBarButtonItems method controls the state of the Undo and Redo buttons. This method checks the active undo manager, seeing whether the undo stack provides undo and redo actions. If so, it enables the appropriate buttons.

Although I’m not a fan of shake-to-undo, this recipe does support it. Its loadView method sets the applicationSupportsShakeToEdit property of the application delegate. Also note that the first responder calls were added to provide undo support. The table view becomes first responder as it appears, and resigns it upon disappearing.

Displaying Remove Controls

The iOS software development kit (SDK) displays table-based remove controls with a single call: [self.tableView setEditing:YES animated:YES]. This updates the table’s editing property and presents the remove controls shown in Figure 10-5 on each cell. The animated parameter is optional but recommended. As a rule, use animations in your iOS interfaces to lead your users from one state to the next so that they’re prepared for the mode changes that happen onscreen.

Recipe 10-3 uses a system-supplied Edit/Done button (self.editButtonItem) and implements setEditing:animated: to move the table into and out of an editing state. When a user taps the Edit or Done button (it toggles back and forth), this method updates the edit state and the navigation bar’s buttons.

Handling Delete Requests

On row deletion, the table communicates with your application by issuing a tableView:com-mitEditingStyle:forRowAtIndexPath: callback. A table delete removes an item from the visual table but does not alter the underlying data. Unless you manage the item removal from your data source, the “deleted” item will reappear on the next table refresh. This method offers the place for you to coordinate with your data source and respond to the row deletion that the user just performed.

Delete the item from the data structure that supplies the data source methods (in this recipe, through an NSMutableArray of image items) and handle any real-world action such as deleting files, removing contacts, and so on, that occur as a consequence of the user-led edit.

Recipe 10-3 animates its cell deletions. The beginUpdates and endUpdates method pair allows simultaneous animation of table operations such as adding and deleting rows.

Swiping Cells

Swiping provides a clean method for removing items from your UITableView instances. To enable swipes, simply provide the commit-editing-style method. The table takes care of the rest.

To swipe, users drag swiftly from one side of the cell to the other. The rectangular delete confirmation appears to the right of the cell, but the cells do not display the round remove controls on the left.

After users swipe and confirm, the tableView:commitEditingStyle:forRowAtIndexPath: method applies data updates just as if the deletion had occurred in edit mode.

Reordering Cells

You empower your users when you allow them to directly reorder the cells of a table. Figure 10-5 shows a table displaying the reorder control’s stacked gray lines. Users can apply this interaction to sort to-do items by priority or choose which songs should go first in a playlist and so on. iOS ships with built-in table reordering support that’s easy to add to your applications.

Like swipe-to-delete, cell reordering support is contingent on the presence or absence of a single method. The tableView:moveRowAtIndexPath:toIndexPath method synchronizes your data source with the onscreen changes, similar to committing edits for cell deletion. Adding this method instantly enables reordering.

Adding Cells

Recipe 10-3 uses an Add button to create new content for the table. This button takes the form of a system bar button item, which displays as a plus sign. (See the top-left corner of Figure 10-5.) The addItem: method in Recipe 10-3 appends a new random image at the end of the items array.

Recipe 10-3. Editing Tables

Click here to view code image

@implementation TestBedViewController

#pragma mark Data Source
// Number of sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)aTableView
{
return 1;
}

// Rows per section
- (NSInteger)tableView:(UITableView *)aTableView
numberOfRowsInSection:(NSInteger)section
{
return items.count;
}

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell" forIndexPath:indexPath];
cell.imageView.image = items[indexPath.row];
}

#pragma mark Edits
- (void) setBarButtonItems
{
// Expire any ongoing operations
if (self.undoManager.isUndoing ||
self.undoManager.isRedoing)
{
[self performSelector:@selector(setBarButtonItems)
withObject:nil afterDelay:0.1f];
return;
}

UIBarButtonItem *undo = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemUndo, self.undoManager, @selector(undo));
undo.enabled = self.undoManager.canUndo;
UIBarButtonItem *redo = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemRedo, self.undoManager, @selector(redo));
redo.enabled = self.undoManager.canRedo;
UIBarButtonItem *add = SYSBARBUTTON(
UIBarButtonSystemItemAdd, @selector(addItem:));

self.navigationItem.leftBarButtonItems = @[add, undo, redo];

if (self.tableView.isEditing)
self.navigationItem.rightBarButtonItem = SYSBARBUTTON(
UIBarButtonSystemItemDone, @selector(leaveEditMode));
else
{
self.navigationItem.rightBarButtonItem = SYSBARBUTTON(
UIBarButtonSystemItemEdit, @selector(enterEditMode));
self.navigationItem.rightBarButtonItem.enabled =
(items.count > 0);
}
}

- (void) setEditing: (BOOL) isEditing animated: (BOOL) animated
{
[super setEditing:isEditing animated:animated];
[self.tableView setEditing:isEditing animated:animated];

NSIndexPath *path = [self.tableView indexPathForSelectedRow];
if (path)
[self.tableView deselectRowAtIndexPath:path animated:YES];

[self setBarButtonItems];
}

- (void) updateItemAtIndexPath: (NSIndexPath *) indexPath withObject: (id) object
{
// Prepare for undo
id undoObject = object ? nil : [items objectAtIndex:indexPath.row];
[[self.undoManager prepareWithInvocationTarget:self]
updateItemAtIndexPath:indexPath withObject:undoObject];

// You cannot insert a nil item. Passing nil is a delete request.
[self.tableView beginUpdates];
if (!object)
{
[items removeObjectAtIndex:indexPath.row];
[self.tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationTop];
}
else
{
[items insertObject:object atIndex:indexPath.row];

[self.tableView insertRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationTop];
}
[self.tableView endUpdates];

[self performSelector:@selector(setBarButtonItems)
withObject:nil afterDelay:0.1f];
}

- (void) addItem: (id) sender
{
// add a new item
NSIndexPath *newPath =
[NSIndexPath indexPathForRow:items.count inSection:0];
UIImage *image = blockImage(IMAGE_SIZE);
[self updateItemAtIndexPath:newPath withObject:image];
}

- (void)tableView:(UITableView *)aTableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// delete item
[self updateItemAtIndexPath:indexPath withObject:nil];
}

// Provide re-ordering support
-(void) tableView: (UITableView *) tableView
moveRowAtIndexPath: (NSIndexPath *) oldPath
toIndexPath:(NSIndexPath *) newPath
{
if (oldPath.row == newPath.row) return;

[[self.undoManager prepareWithInvocationTarget:self]
tableView:self.tableView moveRowAtIndexPath:newPath
toIndexPath:oldPath];

id item = [items objectAtIndex:oldPath.row];
[items removeObjectAtIndex:oldPath.row];
[items insertObject:item atIndex:newPath.row];

if (self.undoManager.isUndoing || self.undoManager.isRedoing)
{
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:@[oldPath]
withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView insertRowsAtIndexPaths:@[newPath]
withRowAnimation:UITableViewRowAnimationLeft];
[self.tableView endUpdates];
}

[self performSelector:@selector(setBarButtonItems)
withObject:nil afterDelay:0.1f];
}

#pragma mark First Responder for Undo Support
- (BOOL)canBecomeFirstResponder
{
return YES;
}

- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self becomeFirstResponder];
}

- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self resignFirstResponder];
}

#pragma mark View Setup
- (void) loadView
{
[super loadView];
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
self.tableView.rowHeight = IMAGE_SIZE + 20.0f;
self.tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
self.navigationItem.rightBarButtonItem = self.editButtonItem;

items = [NSMutableArray array];

// Provide Undo Support
[UIApplication sharedApplication].applicationSupportsShakeToEdit = YES;
[self setBarButtonItems];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Recipe: Working with Sections

Many iOS applications use sections as well as rows. Sections provide another level of structure to lists, grouping items together into logical units. The most commonly used section scheme is alphabetic, although you are certainly not limited to organizing your data this way. You can use any section scheme that makes sense for your application.

Figure 10-6 shows a table that uses sections to display grouped names. Each section presents a separate header (that is, “Crayon names starting with...”), and an index on the right offers quick access to each of the sections. Notice that there are no sections listed for K, Q, X, and Z in that index. You generally want to omit empty sections from the index.

Figure 10-6. Sectioned tables present headers and an index to better find information as quickly as possible.

Building Sections

When working with groups and sections, think two dimensionally. Section arrays let you store and access the members of data in a section-by-section structure. Implement this approach by creating an array of arrays. A section array can store one array for each section, which in turn contains the titles for each cell.

Predicates help you build sections from a list of strings. The following method alphabetically retrieves items from a flat array. The beginswith predicate matches each string that starts with the given letter:

Click here to view code image

- (NSArray *) itemsInSection: (NSInteger) section
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"SELF beginswith[cd] %@", [self firstLetter:section]];
return [[crayonColors allKeys]
filteredArrayUsingPredicate:predicate];
}

Add these results iteratively to a mutable array to create a two-dimensional sectioned array from an initial flat list:

Click here to view code image
sectionArray = [NSMutableArray array];
for (int i = 0; i < 26; i++)
[sectionArray addObject:[self itemsInSection:i]];

To work, this particular implementation relies on two things: first, that the words are already sorted (each subsection adds the words in the order they’re found in the array); and, second, that the sections match the words. Entries that start with punctuation or numbers won’t work with this loop. You can trivially add an “other” section to take care of these cases, which this (simple) sample omits.

Although, as mentioned, alphabetic sections are useful and probably the most common grouping, you can use any kind of structure you like. For example, you might group people by departments, gems by grades, or appointments by date. No matter what kind of grouping you choose, an array of arrays provides the table view data source that best matches sectioned tables.

From this initial startup, it’s up to you to add or remove items using this two-dimensional structure. As you can easily see, creation is simple but maintenance gets tricky. Here’s where Core Data really helps out. Instead of working with multileveled arrays, you can query your data store on any object field, sorting it as desired. Chapter 12 introduces using Core Data with tables. And as you will read in that chapter, it greatly simplifies matters. For now, this example continues to use a simple array of arrays to introduce sections and their use.

Counting Sections and Rows

Sectioned tables require customizing two key data source methods:

• numberOfSectionsInTableView—This method specifies how many sections appear in your table, establishing the number of groups to display. When using a section array, as recommended here, return the number of items in the section array—that is, sectionArray.count. If the number of items is known in advance (26 in this case, even though some sections have no items), you can hard-code that number, but it’s better to code more generally where possible.

• tableView:numberOfRowsInSection—This method is called with a section number. Specify how many rows appear in that section. With the recommended data structure, just return the count of items at the nth subarray:

[[sectionArray objectAtIndex:sectionNumber] count]

Returning Cells

Sectioned tables use both row and section information to find cell data. Earlier recipes in this chapter used a flat array with a row number index. Tables with sections must use the entire index path to locate both the section and row index for the data populating a cell. This method, from a crayon handler helper class, first retrieves the current items for the section and then pulls out the specific item by row. Recipe 10-4 details the helper class methods that work with an array-of-arrays section data source:

Click here to view code image

// Color name by index path
- (NSString *) colorNameAtIndexPath: (NSIndexPath *) path
{
if (path.section >= sectionArray.count)
return nil;
NSArray *currentItems = sectionArray[path.section];

if (path.row >= currentItems.count)
return nil;
NSString *crayon = currentItems[path.row];

return crayon;
}

A similar method retrieves the color itself:

Click here to view code image

// Color by index path
- (UIColor *) colorAtIndexPath: (NSIndexPath *) path
{
NSString *crayon = [self colorNameAtIndexPath:path];
if (crayon)
return crayonColors[crayon];
return nil;
}

Here is the data source method that uses these calls to return a cell with the proper coloring and name:

Click here to view code image

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Stability workaround
[aTableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
UITableViewCell *cell =
[aTableView dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

// Retrieve the crayon name from the proper data source
NSString *crayonName;
if (aTableView == self.tableView)
{
crayonName = [crayons colorNameAtIndexPath:indexPath];
}
else
{
if (indexPath.row < crayons.filteredArray.count)
crayonName  = crayons.filteredArray[indexPath.row];
}

// Stability workaround
if (!crayonName)
{
NSLog(@"Unexpected error retrieving cell: [%d, %d] table: %@",
indexPath.section, indexPath.row, aTableView);
return nil;
}

// Update the cell
cell.textLabel.text = crayonName ;
cell.textLabel.textColor = [crayons colorNamed:crayonName];

// Tint the title
if ([crayonName hasPrefix:@"White"])
cell.textLabel.textColor = [UIColor blackColor];
else
cell.textLabel.textColor = [crayons colorAtIndexPath:indexPath];

return cell;
}

Creating Header Titles

It takes little work to add section headers to your grouped table. The optional tableView:titleForHeaderInSection: method supplies the titles for each section. It’s passed an integer. In return, you supply a title. If your table does not contain any items in a given section or when you’re only working with one section, return nil:

Click here to view code image

// Return the header title for a section
- (NSString *)tableView:(UITableView *)aTableView
titleForHeaderInSection:(NSInteger)section
{
NSString *sectionName = [crayons nameForSection:section];
if (!sectionName) return nil;
return [NSString stringWithFormat:@"Crayon names starting with '%@'",
sectionName];
}

If you aren’t happy using titles, you can return custom header views instead.

Customizing Headers and Footers

Sectioned table views are extremely customizable. Both the tableHeaderView property and the related tableFooterView property can be assigned to any type of view, each with its own subviews. So you might add in labels, text fields, buttons, and other controls to extend the table’s features.

Headers and footers aren’t just one each per table. Each section offers a customizable header and footer view as well. You can alter heights or swap elements out for custom views. The optional tableView:heightForHeaderInSection: (alternatively set the sectionHeaderHeight property) and tableView:viewForHeaderInSection: methods let you add individual headers to each section. Corresponding methods exist for footers as well as headers.

Creating a Section Index

Tables that implement sectionIndexTitlesForTableView: present the kind of index view that appears on the right of Figure 10-6. This method is called when the table view is created, and the array that is returned determines what items are displayed onscreen. Return nil to skip an index. Apple recommends only adding section indices to plain table views—that is, table views created using the default plain style of UITableViewStylePlain, and not grouped tables:

Click here to view code image

// Return an array of section titles for index
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
NSMutableArray *indices = [NSMutableArray array];
for (int i = 0; i < crayons.numberOfSections; i++)
{
NSString *name = [crayons nameForSection:i];
if (name) [indices addObject:name];
}
return indices;
}

Although this example uses single-letter titles, you are certainly not limited to those items. You can use words or, if you’re willing to work out the Unicode equivalents, symbols, including emoji items, that are part of the iOS character library. Here’s how you could add a small yellow smile:

[indices addObject:@"\ue057"];

Handling Section Mismatches

Indices move users along the table based on the user touch offset. As mentioned earlier in this section, this particular table does not display sections for K, Q, X, and Z. These missing letters can cause a mismatch between a user selection and the results displayed by the table.

To remedy this, implement the optional tableView:sectionForSectionIndexTitle: method. This method’s role is to connect a section index title (that is, the one returned by the sectionIndexTitlesForTableView: method) with a section number. This overrides any order mismatches and provides an exact one-to-one match between a user index selection and the section displayed:

Click here to view code image

#define ALPHA    @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- (NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title
atIndex:(NSInteger)index
{
return [ALPHA rangeOfString:title].location;
}

Delegation with Sections

As with data source methods, the trick to implementing delegate methods in a sectioned table involves using the index path section and row properties. These properties provide the double access needed to find the correct section array and then the item within that array for this example:

Click here to view code image

// On selecting a row, update the navigation bar tint
- (void)tableView:(UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIColor *color = [crayons colorAtIndexPath:indexPath];
self.navigationController.navigationBar.tintColor = color;
}

Recipe 10-4. Supporting a Section-Based Table

Click here to view code image

// Return an array of items that appear in each section
- (NSArray *) itemsInSection: (NSInteger) section
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"SELF beginswith[cd] %@", [self firstLetter:section]];
return [[crayonColors allKeys] filteredArrayUsingPredicate:predicate];
}

// Count of active sections
- (NSInteger) numberOfSections
{
return sectionArray.count;
}

// Number of items within a section
- (NSInteger) countInSection: (NSInteger) section
{
return [sectionArray[section] count];
}

// Return the letter that starts each section member's text
- (NSString *) firstLetter: (NSInteger) section
{
return [[ALPHA substringFromIndex:section] substringToIndex:1];
}

// The one letter section name
- (NSString *) nameForSection: (NSInteger) section
{
if (![self countInSection:section])
return nil;
return [self firstLetter:section];
}

// Color name by index path
- (NSString *) colorNameAtIndexPath: (NSIndexPath *) path
{
if (path.section >= sectionArray.count)
return nil;
NSArray *currentItems = sectionArray[path.section];

if (path.row >= currentItems.count)
return nil;
NSString *crayon = currentItems[path.row];

return crayon;
}

// Color by index path
- (UIColor *) colorAtIndexPath: (NSIndexPath *) path
{
NSString *crayon = [self colorNameAtIndexPath:path];
if (crayon)
return crayonColors[crayon];
return nil;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Recipe: Searching Through a Table

Search display controllers are a kind of controller that enable user-driven searches. They allow users to filter a table’s contents in real time, providing instant responsiveness to a user-driven query. It’s a great feature that lets users interactively find what they’re looking for, with the results updating as each new character is entered into the search field.

You create these controllers by initializing them with a search bar instance and a content controller, normally a table view, whose data source is searched. Recipe 10-5 demonstrates the steps involved in creating and using a search display controller in your application.

Searches are best built around predicates, enabling you to filter arrays to retrieve matching items with a simple method call. Here is how you might search through a flat array of strings to retrieve items that match the text from a search bar. The [cd] after contains refers to non-case-sensitive and non-diacritic-sensitive matching. Diacritics are small marks that accompany a letter, such as the dots of an umlaut (¨) or the tilde (~) above a Spanish n:

Click here to view code image

NSPredicate *predicate =
[NSPredicate predicateWithFormat:@"SELF contains[cd] %@",
searchBar.text];
filteredArray = [[crayonColors allKeys]
filteredArrayUsingPredicate:predicate];

The search bar in question should appear at the top of the table as its header view, as in Figure 10-7 (left). The same search bar is assigned to the search display controller, as shown in the following code snippet. Once users tap in the search box, the view shifts and the search bar moves up to the navigation bar area, as shown in Figure 10-7 (right). It remains there until the user taps Cancel, returning the user to the unfiltered table display:

Click here to view code image
self.tableView.tableHeaderView = searchBar;
searchController = [[UISearchDisplayController alloc]
initWithSearchBar:searchBar contentsController:self];

Figure 10-7. The user must scroll to the top of the table to initiate a search. The search bar appears as the first item in the table in its header view (left). Once the user taps within the search bar and makes it active, the search bar jumps into the navigation bar and presents a filtered list of items based on the search criteria (right).

Creating a Search Display Controller

Search display controllers help manage the display of data owned by another controller (in this case, a standard UITableViewController). The search display controller presents a subset of that data, usually by filtering that data source through a predicate. You initialize a search display controller by providing it with a search bar and a contents controller.

Set up the search bar’s text trait features as you would normally do but do not set a delegate. The search bar works with the search display controller without explicit delegation on your part.

When setting up the search display controller, make sure you set both its search results data source and delegate, as shown here. These usually point back to the primary table view controller subclass, which is where you’ll adjust your normal data source and delegate methods to comply with the searchable table:

Click here to view code image

// Create a search bar
searchBar = [[UISearchBar alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, width, 44.0f)];
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
searchBar.keyboardType = UIKeyboardTypeAlphabet;
self.tableView.tableHeaderView = searchBar;

// Create the search display controller
searchController = [[UISearchDisplayController alloc]
initWithSearchBar:searchBar contentsController:self];
searchController.searchResultsDataSource = self;
searchController.searchResultsDelegate = self;

Registering Cells for the Search Display Controller

Under iOS 6’s new easy dequeueing, you register cell types for each table view in your application. That includes the search display controller’s built-in table. Forgetting this step and assuming you can dequeue a cell from self.tableView sets you up for a rather nasty crash. Here’s how you might register cell classes for both tables:

Click here to view code image

// Register cell classes
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
[searchController.searchResultsTableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];

As you can see in the sample code, this recipe uses a few workarounds for those cases where iOS mixes up which table it’s requesting cells for (hence the workaround comments you’ve seen in this section).

Building the Searchable Data Source Methods

The number of items displayed in the table changes as users search. A shorter search string generally matches more items than a longer one. You report the current number of rows for each table. The number of rows changes as the user updates text in the search field. To detect whether the table view controller or the search display controller is currently in charge, check the passed table view parameter. Adjust the row count accordingly:

Click here to view code image

- (NSInteger)tableView:(UITableView *)aTableView
numberOfRowsInSection:(NSInteger)section
{
if (aTableView == searchController.searchResultsTableView)
return [crayons filterWithString:searchBar.text];
return [crayons countInSection:section];
}

Use a predicate to report the count of items that match the text in the search box. Predicates provide an extremely simple way to filter an array and return only those items that match a search string. The predicate used here performs a non-case-sensitive contains match. Each string that contains the text in the search field returns a positive match, allowing that string to remain part of the filtered array. Alternatively, you might want to use beginswith to avoid matching items that do not start with that text. The following method performs the filtering, stores the results, and returns the count of items that it found:

Click here to view code image

- (NSInteger) filterWithString: (NSString *) filter
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"SELF contains[cd] %@", filter];
filteredArray = [[crayonColors allKeys]
filteredArrayUsingPredicate:predicate];
return filteredArray.count;
}

The same table view check becomes even more critical when providing cells. Cell registration corresponds directly to the table that uses them. Use the table view check to determine how to dequeue and initialize cells. The following method return cells retrieved from either the standard or the filtered set:

Click here to view code image

- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell =
[aTableView dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

NSString *crayonName;
if (aTableView == self.tableView)
{
crayonName = [crayons colorNameAtIndexPath:indexPath];
}
else
{
if (indexPath.row < crayons.filteredArray.count)
crayonName  = crayons.filteredArray[indexPath.row];
}

cell.textLabel.text = crayonName ;
cell.textLabel.textColor = [crayons colorNamed:crayonName];
if ([crayonName hasPrefix:@"White"])
cell.textLabel.textColor = [UIColor blackColor];

return cell;
}

Delegate Methods

Search awareness is not limited to data sources. Determining the context of a user tap is critical for providing the correct response in delegate methods. As with the previous data source methods, this delegate method checks the callback’s table view parameter. Based on this comparison, it selects a color with which to color both the search bar and the navigation bar:

Click here to view code image

// Respond to user selections by updating tint colors
- (void)tableView:(UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIColor *color = nil;
if (aTableView == self.tableView)
color = [crayons colorAtIndexPath:indexPath];
else
{
if (indexPath.row < crayons.filteredArray.count)
{
NSString *colorName = crayons.filteredArray[indexPath.row];
if (colorName)
color = [crayons colorNamed:colorName];
}
}
self.navigationController.navigationBar.tintColor = color;
searchBar.tintColor = color;
}

Using a Search-Aware Index

Recipe 10-5 highlights some of the other ways you’ll want to adapt your sectioned table to accommodate search-ready tables. When you support search, the first item added to a table’s section index should be the UITableViewIndexSearch constant. Intended for use only in table indices, and only as the first item in the index, this option adds the small magnifying glass icon that indicates that the table supports searches.

Use it to provide a quick jump to the beginning of the list. Update the tableView:sectionForSectionIndexTitle:atIndex: to catch user requests. The scrollRectToVisible:animated: call used in this recipe manually moves the search bar into place when a user taps on the magnifying glass. Otherwise, users would have to scroll back from section 0, which is the section associated with the letter A.

Add a call in viewWillAppear: to scroll the search bar offscreen when the view first loads. This allows your table to start with the bar hidden from sight, ready to be scrolled up to or jumped to as the user desires.

Finally, respond to cancelled searches by proactively clearing the search text from the bar.

Recipe 10-5. Using Search Features

Click here to view code image

// Add Search to the index
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
if (aTableView == searchController.searchResultsTableView) return nil;

// Initialize with the search magnifying glass
NSMutableArray *indices = [NSMutableArray
arrayWithObject:UITableViewIndexSearch];

for (int i = 0; i < crayons.numberOfSections; i++)
{
NSString *name = [crayons nameForSection:i];
if (name) [indices addObject:name];
}

return indices;
}

// Handle both the search index item and normal sections
- (NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title
atIndex:(NSInteger)index
{
if (title == UITableViewIndexSearch)
{
[self.tableView scrollRectToVisible:searchBar.frame animated:NO];
return -1;
}
return [ALPHA rangeOfString:title].location;
}

// Handle the Cancel button by resetting the search text
- (void)searchBarCancelButtonClicked:(UISearchBar *)aSearchBar
{
[searchBar setText:@""];
}

// Titles only for the main table
- (NSString *)tableView:(UITableView *)aTableView
titleForHeaderInSection:(NSInteger)section
{
if (aTableView == searchController.searchResultsTableView)
return nil;
return [crayons nameForSection:section];
}

// Upon appearing, scroll away the search bar
- (void) viewWillAppear:(BOOL)animated
{
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:path
atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Recipe: Adding Pull-to-Refresh to Your Table

Pull-to-refresh is a widely used app feature that became popular in the App Store over the past few years. It lets you refresh tables by pulling down their tops enough to indicate a request. It is so intuitive to use that many wondered why Apple didn’t add one to its UITableViewController class. Starting in iOS 6, they did (see Figure 10-8).

Figure 10-8. You can easily add a pull-to-refresh option to your tables. Users pull down to request updated data.

The new UIRefreshControl class provides an extremely handy control that initiates a table view’s refresh. Recipe 10-6 demonstrates how to add it to your applications. Create a new instance and assign it to a table view controller’s refreshControl property. The control appears directly in the table view without any further work.

After receiving a pull event callback, start a refreshing event (startRefreshing). The pull control turns into a progress wheel. When the new data has been prepared, end the refreshing (endRefreshing) and reload the table view.

Descending from UIControl, instances use target-action to send a custom selector to clients when activated. For whatever reason, it updates with a value-changed event. Surely, it’s long past time for Apple to introduce a UIControlEventTriggered event for stateless control triggers like this one.

Using pull-to-refresh allows your applications to delay performing expensive routines. For example, you might hold off fetching new information from the Internet or computing new table elements until the user triggers a request for those operations. Pull-to-refresh places your user in control of refresh operations and provides a great balance between information-on-demand and computational overhead.

The DataManager class referred to in Recipe 10-6 loads its data asynchronously using an operation queue:

Click here to view code image

- (void) loadData
{
NSString *rss = @"http://itunes.apple.com/us/rss/topalbums/limit=30/xml";
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperationWithBlock:
^{
root = [[XMLParser sharedInstance] parseXMLFromURL:
[NSURL URLWithString:rss]];
[[NSOperationQueue currentQueue] addOperationWithBlock:^{
[self handleData];
}];
}];
}

This approach ensures that data loading won’t block the main thread. The refresh control’s progress wheel won’t be hindered, and the user will be free to interact with other UI elements in your app. After the fetch completes, move control back to the main thread:

Click here to view code image
if (delegate &&
[delegate respondsToSelector:@selector(dataIsReady:)])
[delegate performSelectorOnMainThread:@selector(dataIsReady:)
withObject:self waitUntilDone:NO];

Recipe 10-6 offers a Load button in addition to its refresh control. Most applications will skip this redundancy. I included it here to show how it would interact with the refresh control. When tapped, you still need to perform the table’s startRefreshing and endRefreshing methods. This ensures the refresh control operates synchronously with the manual reload.

Recipe 10-6. Building Pull-to-Refresh into Your Tables

Click here to view code image

- (void) dataIsReady: (id) sender
{
// Update the title
self.title = @"iTunes Top Albums";

// Reenable the bar button item
self.navigationItem.rightBarButtonItem.enabled = YES;

// End refreshing and update the table
[self.refreshControl endRefreshing];
[self.tableView reloadData];
}

- (void) loadData
{
// Provide user status update
self.title = @"Loading...";

// Disable the bar button item
self.navigationItem.rightBarButtonItem.enabled = NO;

// Start refreshing
[self.refreshControl beginRefreshing];

[manager loadData];
}

- (void) loadView
{
[super loadView];
self.tableView.rowHeight = 72.0f;
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"generic"];

// Offer a bar button item and...
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Load", @selector(loadData));

// Alternatively, use the refresh control
self.refreshControl = [[UIRefreshControl alloc] init];
[self.refreshControl addTarget:self action:@selector(loadData)
forControlEvents:UIControlEventValueChanged];

// This custom data manager to asynchronously (nonblocking)
// loads data in a secondary thread
manager = [[DataManager alloc] init];
manager.delegate = self;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Recipe: Adding Action Rows

Action rows (a.k.a. drawer cells) slide open to expose extra cell-specific functionality when users tap the cell associated with them. You may have seen this kind of functionality in commercial apps such as Tweetbot (http://tapbots.com). Recipe 10-7 builds an action row table featuring a pair of buttons in each of its drawers (see Figure 10-9). When tapped, the title button sets the title on the navigation bar to the cell text; the alert button displays the same string in a pop-up alert. iOS developer Bilal Sayed Ahmad (@Demonic_BLITZ on Twitter) suggested adding this recipe to the Cookbook, and this code is inspired from a sample project he created.

Figure 10-9. Action rows offer cell-specific actions that slide open when a user selects a cell. In this example, the user has tapped the November cell and disclosed a hidden draw with the Set Title and Alert buttons.

Recipe 10-7 works by adding a phantom cell to its table view. All other cells adjust around its presence. The implementation starts by adjusting the method that reports the number of rows per section. The drawer lives at the actionRowPath. When present, the number of cells increases by one. When hidden, the data source simply reports the normal count of its items.

Its loadView method registers two cell types: one for standard rows, one for the action row. The data source returns a custom cell when passed a path it recognizes as the custom index.

The action cell has other quirks. It cannot be selected. Recipe 10-7’s tableView:willSelectRowAtIndexPath: method ensures that by returning nil when passed the action row path.

Most of this implementation work takes place in the tableView:didSelectRowAtIndexPath: method. It moves the action drawer around by changing its path and performing table updates. Here, the code considers three possible states: The drawer is closed and a new cell is tapped, the drawer is open and the same cell is tapped, and the drawer is open and a different cell is tapped.

The action row path is always nil whenever the drawer is shut. When tapped, the method sets a path for the new drawer directly after the tapped cell. If the user taps the associated cell above the drawer when it is open, the drawer “closes” and the path is set back to nil. When the user taps a different cell, this method adjusts its math depending on whether the new cell is below the old action drawer or above it.

The beginUpdates and endUpdates method pair used here allows simultaneous animation of table operations. Use this block to smoothly introduce all the row changes created by moving, adding, and removing the action drawer.

Recipe 10-7. Adding Action Drawers to Tables

Click here to view code image

// Rows per section
- (NSInteger)tableView:(UITableView *)aTableView
numberOfRowsInSection:(NSInteger)section
{
return items.count + (self.actionRowPath != nil);
}

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.actionRowPath isEqual:indexPath])
{
// Action Row
CustomCell *cell = (CustomCell *)[self.tableView
dequeueReusableCellWithIdentifier:@"action"
forIndexPath:indexPath];
[cell setActionTarget:self];
return cell;
}
else
{
// Normal cell
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

// Adjust item lookup around action row if needed
NSInteger adjustedRow = indexPath.row;
if (_actionRowPath && (_actionRowPath.row < indexPath.row)) adjustedRow--;
cell.textLabel.text = [items objectAtIndex:adjustedRow];

cell.textLabel.textColor = [UIColor whiteColor];
cell.selectionStyle = UITableViewCellSelectionStyleGray;
return cell;
}
}

- (NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Only select normal cells
if([indexPath isEqual:self.actionRowPath]) return nil;
return indexPath;
}

// Deselect any current selection
- (void) deselect
{
NSArray *paths = [self.tableView indexPathsForSelectedRows];
if (!paths.count) return;

NSIndexPath *path = paths[0];
[self.tableView deselectRowAtIndexPath:path animated:YES];
}

// On selection, update the title and enable find/deselect
- (void)tableView: (UITableView *)aTableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray *pathsToAdd;
NSArray *pathsToDelete;

if ([self.actionRowPath.previous isEqual:indexPath])
{
// Hide action cell
pathsToDelete = @[self.actionRowPath];
self.actionRowPath = nil;
[self deselect];
}
else if (self.actionRowPath)
{
// Move action cell
BOOL before = [indexPath before:self.actionRowPath];
pathsToDelete = @[self.actionRowPath];
self.actionRowPath = before ? indexPath.next : indexPath;
pathsToAdd = @[self.actionRowPath];
}
else
{
// New action cell
pathsToAdd = @[indexPath.next];
self.actionRowPath = indexPath.next;
}

// Animate the deletions and insertions
[self.tableView beginUpdates];
if (pathsToDelete.count)
[self.tableView deleteRowsAtIndexPaths:pathsToDelete
withRowAnimation:UITableViewRowAnimationNone];
if (pathsToAdd.count)
[self.tableView insertRowsAtIndexPaths:pathsToAdd
withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
}

// Set up table
- (void) loadView
{
[super loadView];
[self.tableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
[self.tableView registerClass:[CustomCell class]
forCellReuseIdentifier:@"action"];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Coding a Custom Group Table

If alphabetic section list tables are the M. C. Eschers of the iPhone table world, with each section block precisely fitting into the negative spaces provided by other sections in the list, then freeform group tables are the Marc Chagalls. Every bit is drawn as a freeform handcrafted work of art.

It’s relatively easy to code up all the tables you’ve seen so far in this chapter after you’ve mastered the knack. Perfecting group table coding (usually called preferences table by devotees because that’s the kind of table used in the Settings application) remains an illusion.

Building group tables in code is all about the collage. They’re all about handcrafting a look, piece by piece. Creating a presentation like this in code involves a lot of detail work.

Creating Grouped Preferences Tables

There’s nothing special involved in terms of laying out a new UITableViewController for a preferences table. You allocate it. You initialize it with the grouped table style. That’s pretty much the end of it. It’s the data source and delegate methods that provide the challenge. Here are the methods you’ll need to define:

• numberOfSectionsInTableView:—All preferences tables contain groups of items. Each group is visually contained in a rounded rectangle. Return the number of groups you’ll be defining as an integer.

• tableView:titleForHeaderInSection:—Add the titles for each section into this optional method. Return an NSString with the requested section name.

• tableView:numberOfRowsInSection:—Each section may contain any number of cells. Have this method return an integer indicating the number of rows (that is, cells) for that group.

• tableView:heightForRowAtIndexPath:—Tables that use flexible row heights cost more in terms of computational intensity. If you need to use variable heights, implement this optional method to specify what those heights will be. Return the value by section and by row.

• tableView:cellForRowAtIndexPath:—This is the standard cell-for-row method you’ve seen throughout this chapter. What sets it apart is its implementation. Instead of using one kind of cell, you’ll probably want to create different kinds of reusable cells (with different reuse tags) for each cell type. Make sure you manage your reuse queue carefully and use as many Interface Builder (IB)-integrated elements as possible.

• tableView:didSelectRowAtIndexPath:—You provide case-by-case reactions to cell selection in this optional delegate method depending on the cell type selected.

Note

The open-source llamasettings project at Google Code (http://llamasettings.googlecode.com) automatically produces grouped tables from property lists meant for iPhone settings bundles. It allows you to bring settings into your application without forcing your user to leave the app. The project can be freely added to commercial iOS SDK applications without licensing fees.

Recipe: Building a Multiwheel Table

Sometimes you’d like your users to pick from long lists or from several lists simultaneously. That’s where UIPickerView instances really excel. UIPickerView objects produce tables offering individually scrolling “wheels,” as shown in Figure 10-10. Users interact with one or more wheels to build their selection.

Figure 10-10. UIPickerView instances enable users to select from independently scrolling wheels.

These tables, although superficially similar to standard UITableView instances, use distinct data and delegate protocols:

• There is no UIPickerViewController class. UIPickerView instances act as subviews to other views. They are not intended to be the central focus of an application view. You can build a UIPickerView instance onto another view.

• Picker views use numbers, not objects. Components (that is to say, the wheels) are indexed by numbers and not by NSIndexPath instances. It’s a more informal class than the UITableView.

You can supply either titles strings or views via the data source. Picker views can handle both approaches.

Creating the UIPickerView

When creating the picker, remember two key points. First, you want to enable the selection indicator. That is the blue bar that floats over the selected items. So set showsSelectionIndicator to YES. If you add the picker in IB, this is already set as the default.

Second, don’t forget to assign the delegate and data source. Without this support, you cannot add data to the view, define its features, or respond to selection changes. Your primary view controller should implement the UIPickerViewDelegate and UIPickerViewDataSource protocols.

Data Source and Delegate Methods

Implement three key data source methods for your UIPickerView to make it function properly at a minimum level. These methods are as follows:

• numberOfComponentsInPickerView—Return an integer, the number of columns.

• pickerView:numberOfRowsInComponent:—Return an integer, the maximum number of rows per wheel. These numbers do not need to be identical. You can have one wheel with many rows and another with very few.

• pickerView:titleForRow:forComponent or pickerView:viewForRow:for-Component:reusingView:—These methods specify the text or view used to label a row on a given component.

In addition to these data source methods, you might want to supply one further delegate method. This method responds to user interactions via wheel selection:

• pickerView:didSelectRow:inComponent—Add any application-specific behavior to this method. If needed, you can query the pickerView to return the selectedRowInComponent: for any of the wheels in your view.

Using Views with Pickers

Picker views use a basic view-reuse scheme, caching the views supplied to it for possible reuse. When the final parameter for the pickerView:viewForRow:forComponent:reusingView: method is not nil, you can reuse the passed view by updating its settings or contents. Check for the view and allocate a new one only if one has not been supplied.

The height need not match the actual view. Implement pickerView:rowHeightForComponent: to set the row height used by each component. Recipe 10-8 uses a row height of 120 points, providing plenty of room for each image and laying the groundwork for the illusion that the picker could be continuous rather than having a starting and ending point.

Notice the high number of components, namely one million. The reason for this high number lies in a desire to emulate real cylinders. Normally, picker views have a first element and a last, and that’s where they end. This recipe takes another approach, asking “What if the components were actual cylinders, so the last element was connected to the first?”

To emulate this, the picker uses a much higher number of components than any user will ever be able to access. It initializes the picker to the middle of that number by calling selectRow:inComponent:Animated:. Each component “row” is derived by the modulo of the actual reported row and the number of individual elements to display (in this case, % 4). Although the code knows that the picker actually has a million rows per wheel, the user experience offers a cylindrical wheel of just four rows.

Recipe 10-8. Creating the Illusion of a Repeating Cylinder

Click here to view code image

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 3; // three columns
}

- (NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
return 1000000; // arbitrary and large
}

- (CGFloat)pickerView:(UIPickerView *)pickerView
rowHeightForComponent:(NSInteger)component
{
return 120.0f;
}

- (UIView *)pickerView:(UIPickerView *)pickerView viewForRow:(NSInteger)row
forComponent:(NSInteger)component reusingView:(UIView *)view
{
// Load up the appropriate row image
NSArray *names = @[@"club.png", @"diamond.png", @"heart.png", @"spade.png"];
UIImage *image = [UIImage imageNamed:names[row%4]];

// Create an image view if one was not supplied
UIIImageView *imageView = (UIImageView *) view;
imageView.image = image;
if (!imageView)
imageView = [[UIImageView alloc] initWithImage:image];

return imageView;
}

- (void) pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
// Respond to selection by setting the view controller's title
NSArray *names = @[@"C", @"D", @"H", @"S"];
self.title = [NSString stringWithFormat:@"%@•%@•%@",
names[[pickerView selectedRowInComponent:0] % 4],
names[[pickerView selectedRowInComponent:1] % 4],
names[[pickerView selectedRowInComponent:2] % 4]];
}

- (void) viewDidAppear:(BOOL)animated
{
// Set random selections as the view appears
[picker selectRow:50000 + (rand() % 4) inComponent:0 animated:YES];
[picker selectRow:50000 + (rand() % 4) inComponent:1 animated:YES];
[picker selectRow:50000 + (rand() % 4) inComponent:2 animated:YES];
}

- (void) loadView
{
[super loadView];

// Create the picker and center it
picker = [[UIPickerView alloc] initWithFrame:CGRectZero];
[self.view addSubview:picker];
PREPCONSTRAINTS(picker);
CENTERH(self.view, picker);
CENTERV(self.view, picker);

// Initialize the picker properties
picker.delegate = self;
picker.dataSource = self;
picker.showsSelectionIndicator = YES;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 10.

Using the UIDatePicker

When you want to ask your user to enter date information, Apple supplies a tidy subclass of UIPickerView to handle several kinds of time entry. Figure 10-11 shows the four built-in styles of UIDatePickers you can choose from. These include selecting a time, selecting a date, selecting a combination of the two, and a countdown timer.

Figure 10-11. The iPhone offers four stock date picker models. Use the datePickerMode property to select the picker you want to use in your application.

Creating the Date Picker

Lay out a date picker exactly as you would a UIPickerView. The geometry is identical. After that, things get much, much easier. You need not set a delegate or define data source methods. You do not have to declare any protocols. Just assign a date picker mode. Choose from UIDatePickerModeTime, UIDatePickerModeDate, UIDatePickerModeDateAndTime, and UIDatePickerModeCountDownTimer:

Click here to view code image

[datePicker setDate:[NSDate date]]; // set date
datePicker.datePickerMode = UIDatePickerModeDateAndTime; // set style

Optionally, add a target for when the selection changes (UIControlEventValueChanged) and create the callback method for the target-action pair.

Here are a few properties you’ll want to take advantage of in the UIDatePicker class:

• date—Set the date property to initialize the picker or to retrieve the information set by the user as he or she manipulates the wheels.

• maximumDate and minimumDate—These properties set the bounds for date and time picking. Assign each one a standard NSDate. With these, you can constrain your user to pick a date from next year rather than just enter a date and then check whether it falls within an accepted time frame.

• minuteInterval—Sometimes you want to use 5-, 10-, 15-, or 30-minute intervals on your selections, such as for applications used to set appointments. Use the minuteInterval property to specify that value. Whatever number you pass, it has to be evenly divisible into 60.

• countDownDuration—Use this property to set the maximum available value for a countdown timer. You can go as high as 23 hours and 59 minutes (that is, 86,399 seconds).

Summary

This chapter introduced iOS tables from the simple to the complex. You saw all the basic iOS table features—from simple tables, to edits, to reordering and undo. You also learned about a variety of advanced elements—from indexed alphabetic listings, to refresh controls, to picker views. The skills covered in this chapter enable you to build a wealth of table-based applications for the iPhone, iPad, and iPod touch. Here are some key points to take away from this chapter:

• When it comes to understanding tables, make sure you know the difference between data sources and delegate methods. Data sources fill up your tables with meaningful content. Delegate methods respond to user interactions.

• UITableViewControllers simplify applications built around a central UITableView. Do not hesitate to use UITableView instances directly, however, if your application requires them—especially in popovers or with split view controllers. Just make sure to explicitly support the UITableViewDelegate and UITableViewDataSource protocols when needed.

• Index controls provide a great way to navigate quickly through large ordered lists. Take advantage of their power when working with tables that would otherwise become unnavigable. Stylistically, it’s best to avoid index controls when working with grouped tables.

• Dive into edits. Giving the user control over the table data is easy to do, and your code can be reused over many projects. Don’t hesitate to design for undo support from the start. Even if you think you may not need undo at first, you may change your mind over time.

• It’s easy to convert flat tables into sectioned ones. Don’t hesitate to use the predicate approach introduced in this chapter to create sections from simple arrays. Sectioned tables allow you to present data in a more structured fashion, with index support and easy search integration.

• Date pickers are highly specialized and very good at what they do: soliciting your users for dates and times. Picker views provide a less-specialized solution but require more work on your end to bring them to life.

11. Collection Views

New to iOS 6, collection views present organized grids that lay out cells. These collections go well beyond standard table views and their vertically scrolling lists of cells. Collection views use many of the same concepts as tables but provide more power and more flexibility. With collection views, you create side-scrolling lists, grids, one-of-a-kind layouts like circles, and more. Plus, this new class offers integrated visual effects through layout specifications and lots of great features like snapping into place after scrolling.

As with tables, you can add an enormous range of implementation details to collection views. This chapter introduces you to the basics: to the collection view, its client sources, its special-purpose controller, and its cells. You read about how to develop standard and customized collections, how you can start adding special effects to your presentations, and how you can take advantage of the built-in animation support to create the most effective interaction possible.

That said, collection views are more powerful than any single chapter can properly cover. This chapter offers fundamental collection view concepts. From here, how sharp you hone your collection view is up to you.

Collection Views Versus Tables

UICollectionView instances present an ordered collection of data items. Like table views, collections are made up of cells, headers, and footers powered by data sources and delegates. Unlike tables, collections introduce a layout, a class that specifies how items are placed onscreen. Layouts organize the location of each cell, so items appear exactly where needed.

Table 11-1 compares these two layout families. As you see, each family offers a core view class and a prebuilt controller class. These classes rely on a data source, which feeds cells on demand and provides other content information. They use a delegate to respond to user interactions.

Table 11-1. Collection Views Versus Tables

There are also several fundamental differences, starting with the humble index path. Both classes are organized by section as their primary grouping, with each section containing indexed individual cells. Because collection views can scroll either direction, vertical or horizontal, terminology has changed. Table views use sections and rows; collection views use sections and items. The NSIndexPath class has been updated in iOS 6 to reflect this new scheme.

Collection views introduce a new kind of content called “decoration” views, which provide visual enhancements like backdrops. This new class understands that cells and scrolling is just the starting point for the class. That you’ll want to customize the entire look to create coherent presentations using any metaphor you can imagine. Collection views also rethink headers and footers, transforming those into supplementary views with a little more API flexibility than those found in tables.

Practical Implementation Differences

Expect a few practical differences between building table views and collection views. Collection views are less tolerant of lazy data loading. As a rule, when you create a collection view, make sure the data source that powers that view is fully prepared to go—even if it’s prepared with a minimal or empty set of cells as you load data elsewhere in your application.

You cannot wait until your initialization, loadView or viewDidLoad methods, to prepare content. Get those ready and going first, whether in your application delegate or before you instantiate and add your collection view or push a new child collection view controller. If your data is not ready to go, your app will crash; this is not the user experience you should be aiming toward.

Make sure you fully establish your collection view’s layout object before presenting the collection. As you’ll see in recipes in this chapter, you set up all layout details, including the scroll direction and any properties that don’t rely on delegate callbacks. Only then, create and initialize your collection view, as shown here:

Click here to view code image

MyCollectionController *mcc = [[MyCollectionController alloc]
initWithCollectionViewLayout:layout];

Passing a nil layout produces an exception.

Establishing Collection Views

As with tables, collections come in two flavors: views and prebuilt controllers. You either build an individual collection view instance and add it to your presentation or use a UICollectionViewController object that offers a view controller prepopulated with a collection view. The controller automatically sets the view’s data source, delegate, and layout delegate to itself and declares all three protocols. Embed the collection view controller as a child of any container (such as navigation controllers, tab bar controllers, split view controllers, page view controllers, and so on) or present it on its own.

Note

Like table views, collection views have delegate and dataSource properties. The layout delegate protocol (UICollectionViewDelegateFlowLayout) uses the object assigned to the delegate property.

Controllers

To build a controller, create and set up a layout, allocate the new instance, and initialize it with the prepared layout:

Click here to view code image

UICollectionViewFlowLayout *layout =
[[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

MyCollectionController *mcc = [[MyCollectionController alloc]
initWithCollectionViewLayout:layout];

This snippet used a collection view flow layout in its default form, only setting the scroll direction. As you’ll see through this chapter, you can do a lot more with layouts. Typically, you set additional properties or subclass system-supplied layouts and add your own behavior.

As a rule, you use the UICollectionViewFlowLayout class. It’s the layout workhorse for collection views. Use it to build any basic presentation. In its default form, each section automatically wraps items to fit the screen, and you can specify how much space appears between sections, between lines, between items, and so forth. It’s insanely customizable, as you’ll see in the next section, which details many tweaks you can apply to flow layouts.

Its parent class, UICollectionViewLayout, offers an abstract base class for subclassing (which you mostly don’t; nearly every time, you’ll want to subclass the flow layout version instead) and isn’t meant for direct use.

Note

When looking at subclassing layouts, refer to UICollectionViewLayout. The parent of the UICollectionViewFlowLayout class, its documentation provides the canonical list of customizable methods.

Views

To create a collection view for embedding into another view, establish a layout, create the view using the layout and set the data source and delegate. The flow layout delegate defaults to the object you set as the delegate property:

Click here to view code image

UICollectionViewFlowLayout *layout =
[[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;

collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:layout];
collectionView.dataSource = self;
collectionView.delegate = self;

Data Sources and Delegates

View controllers coordinating collection views declare UICollectionViewDataSource and UICollectionViewDelegate. Unlike table views, the software development kit (SDK) introduces a third protocol for collection views, which is UICollectionViewDelegateFlowLayout.

The delegate flow layout protocol coordinates layout information with your collection’s layout instance through a series of callbacks. Your collection view’s delegate adopts this protocol—that is, you do not have to specify a third collection view property like delegateFlowLayout or anything like that.

As with table views, the data source provides section and item information and returns cells and other collection view items on demand. The delegate handles user interactions and provides meaningful responses to user changes. The flow layout delegate introduces section-by-section layout details and is, for the most part, completely optional. You read about flow layouts and their delegate callbacks in the next section.

Flow Layouts

Flow layouts provided by the UICollectionViewFlowLayout class create organized grid presentations in your application. They provide built-in properties that you edit directly or establish via delegate callbacks. These properties specify how the flow sets itself up to place items onscreen. In its most basic form, the layout properties provide you with a geometric vocabulary, where you talk about row spacing, indentation, and item-to-item margins.

Scroll Direction

The scrollDirection property controls whether sections are lined up horizontally (UICollectionViewScrollDirectionHorizontal) or vertically (UICollectionViewScrollDirectionVertical). Figure 11-1 demonstrates otherwise identical layouts with horizontal (left) and vertical (right) flows. The members of each grouped section wrap to available space based on the current flow. Because there is more vertical space than horizontal space in the iPhone portrait presentation, section groups are longer and thinner in the horizontal flow than the vertical flow.

Figure 11-1. Horizontal (left) and vertical (right) flows determine the collection view’s overall scrolling direction. The left image scrolls left-right. The right image scrolls up-down. For each example, a flow layout automatically handles wrapping duties at the end of each line. Each section includes 12 items.

Item Size and Line Spacing

Use the itemSize property to specify the default size for each onscreen item, like the small squares in Figure 11-1. The minimumLineSpacing and minimumInteritemSpacing properties specify how much space you need wrapped between objects within each section. Line spacing always goes between each line in the direction of flow. For example, line spacing refers to the space between S0(0) and S0(6) in Figure 11-1 (left) or between S0(0) and S0(4) in Figure 11-1 (right). Item spacing is orthogonal (at right angles) to lines, specifying the gap to leave between each item, such as between S0(0) and S0(1), and between S0(1) and S0(2).

Figure 11-2 shows these properties in action, in this case using a vertical flow. The left figure shows consistent spacing of 10 points. The middle figure expands line spacing to 30 points. This space appears between lines of items, where the flow wraps from one line to the next. The right figure expands item spacing to 30 points. Item spaces appear along each row, adding spacers between each object.

Figure 11-2. Minimum line and inter-item spacing control how items are wrapped within each section. Item sizes specify the dimensions for each cell. The left image uses default spacing. The center image increases line spacing to 50 points. The right increases inter-item spaces to 30 points.

As with many new layout items in iOS 6, these settings are requests. Specifically, the spacing may exceed whatever value you specify, but the layout tries to respect the minimums you assign.

You can set the mentioned layout properties directly to assign default values applied across an entire collection. You can also use flow layout delegate callback methods to specify values from code. Setting these values at runtime offers far more nuance than the default settings, as they are applied on a section-by-section and item-by-item basis rather than globally. The following methods handle item size and minimum spacing:

• collectionView:layout:sizeForItemAtIndexPath: corresponds to the itemSize property, on an item-by-item basis.

• collectionView:layout:minimumLineSpacingForSectionAtIndex: corresponds to the minimumLineSpacing property, but controls it on a section-by-section basis.

• collectionView:layout:minimumInteritemSpacingForSectionAtIndex: corresponds to the minimumInteritemSpacing property, again on a section-by-section basis.

Of these, the first method for item sizes offers the adaptation most typically used in iOS development. It enables you to build collections whose items, unlike those shown in Figure 11-2, vary in dimension. Figure 11-4, which follows later in this chapter, shows a flow layout that adjusts itself to multisized contents.

Header and Footer Sizing

The headerReferenceSize and footerReferenceSize properties define how wide or how high header and footer items should be. Notice the difference between the extents for these items in Figure 11-3 in the top two and bottom two screen shots. The horizontal flow at the top uses 60-point wide spacing for these two items. The vertical flow at the bottom uses 30-point high spacing. Although you supply a full CGSize to these properties, the layout uses only one field at any time based on the flow direction. For horizontal flow, it’s the width field; for vertical, it’s the height.

Figure 11-3. Section insets control the space that leads up to and away from a section’s items. The top images show a horizontal flow, the bottom images a vertical flow. All images use a top spacing of 50 points and a bottom spacing of 30 points, along with 10 point left and right spacing.

Here are the two callbacks used to generate the Figure 11-3 layouts. They return complete size structures even though only one field is used at any time. There are no corresponding properties in the flow layout class for these methods:

Click here to view code image

- (CGSize) collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section
{
return CGSizeMake(60.0f, 30.0f);
}

- (CGSize) collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForFooterInSection:(NSInteger)section
{
return CGSizeMake(60.0f, 30.0f);
}

Insets

The two minimum spacing properties define how each in-section item relates to other items within a section. In contrast, the sectionInset property describes how the outer edges of a section add padding. This padding affects how sections relate to their optional headers and footers, and how sections move apart from each other in general.

Edge insets consist of a set of {top, left, bottom, right} values. Figure 11-3 shows how this works with collection views. Each shot in Figure 11-3 presents a flow using the same edge insets of 50 points at the top, 30 points at the bottom, and 10 points left and right:

UIEdgeInsetsMake(50.0f, 10.0f, 30.0f, 10.0f)

The top row shows a horizontal flow, the bottom row a vertical flow. In each case, you see how the insets affect layout. In the horizontal flow, the headers and footers adjust vertically to allow for the top spacing. In the vertical flow, the extra space happens below any headers and above any footers. Similarly, the left and right spacing are incorporated between headers and footers in the horizontal flow and adjust the entire layout in vertical flow.

Be aware that this layout behavior changed significantly during the beta rollout and is a likely candidate for updates in future iOS releases. When in doubt, test your layouts to ensure their behavior matches what you specified.

Recipe: Basic Collection View Flows

Recipe 11-1 introduces a basic collection view controller implementation, with support for optional headers and footers. This recipe implements the essential data source and delegate methods you need for a simple grid-based flow layout. You can modify the source to adjust the number of sections to be viewed, the items per section, and any other layout details that control the overall flow.

You control whether a collection view uses headers and footers by implementing the first two reference size requests in Recipe 11-1. These are the one for “header in section” and the one for “footer in section.” You’ll find these two methods just after the Flow Layout bookmark. Returning a zero size to the header or footer flow delegate method tells the collection view to omit those features for the section in question. When you return any other size, the data source moves on to requesting the supplementary elements for either a header or footer.

Make sure to register all cell and supplementary view classes before using them in your data source. Recipe 11-1 registers its classes in its viewDidLoad method. Once registered, you can dequeue instances on demand. In the iOS 6 SDK, you no longer have to check whether a dequeuing request returns a usable instance. The methods create and initialize instances for you when needed.

I encourage you to dive into the sample code for Recipe 11-1 and tweak each layout value and callback, as I did to create the figures you’ve already seen in this section, to see how they affect overall flow and appearance. Recipe 11-1 offers a great jumping off point for testing collection views and seeing how each property influences the final presentation.

Recipe 11-1. Basic Collection View Controller with Flow Layout

Click here to view code image

@implementation TestBedViewController

#pragma mark Flow Layout
- (CGSize) collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForHeaderInSection:(NSInteger)section
{
return useHeaders ? CGSizeMake(60.0f, 30.0f) : CGSizeZero;
}

- (CGSize) collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout *)collectionViewLayout
referenceSizeForFooterInSection:(NSInteger)section
{
return useFooters ? CGSizeMake(60.0f, 30.0f) : CGSizeZero;
}

#pragma mark Data Source
// Number of sections total
- (NSInteger)numberOfSectionsInCollectionView:
(UICollectionView *)collectionView
{
return 10;
}

// Number of items per section
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return 12;
}

// Dequeue and prepare a cell
- (UICollectionViewCell *)collectionView:
(UICollectionView *)aCollectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView
dequeueReusableCellWithReuseIdentifier:@"cell"
forIndexPath:indexPath];

cell.backgroundColor = [UIColor whiteColor];
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.5f];

return cell;
}

// If using headers and footers, dequeue and prepare a view
- (UICollectionReusableView *)collectionView:
(UICollectionView *)aCollectionView
viewForSupplementaryElementOfKind:(NSString *)kind
atIndexPath:(NSIndexPath *)indexPath
{
if (kind == UICollectionElementKindSectionHeader)
{
UICollectionReusableView *header = [self.collectionView
dequeueReusableSupplementaryViewOfKind:
UICollectionElementKindSectionHeader
withReuseIdentifier:@"header" forIndexPath:indexPath];
header.backgroundColor = [UIColor blackColor];
return header;
}
if (kind == UICollectionElementKindSectionFooter)
{
UICollectionReusableView *footer = [self.collectionView
dequeueReusableSupplementaryViewOfKind:
UICollectionElementKindSectionFooter
withReuseIdentifier:@"footer" forIndexPath:indexPath];
footer.backgroundColor = [UIColor darkGrayColor];
return footer;
}
return nil;
}

#pragma mark Delegate methods
- (void)collectionView:(UICollectionView *)aCollectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Selected %@", indexPath);
}

- (void)collectionView:(UICollectionView *)aCollectionView
didDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(@"Deselected %@", indexPath);
}

#pragma mark Setup
- (void) viewDidLoad
{
// Register any cell and header/footer classes for re-use queues
[self.collectionView
registerClass:[UICollectionViewCell class]
forCellWithReuseIdentifier:@"cell"];
[self.collectionView
registerClass:[UICollectionReusableView class]
forSupplementaryViewOfKind:UICollectionElementKindSectionHeader
withReuseIdentifier:@"header"];
[self.collectionView
registerClass:[UICollectionReusableView class]
forSupplementaryViewOfKind:UICollectionElementKindSectionFooter
withReuseIdentifier:@"footer"];

self.collectionView.backgroundColor = [UIColor lightGrayColor];

// Allow users to select/deselect items by tapping
self.collectionView.allowsMultipleSelection = YES;
}
@end

// From the application delegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
UICollectionViewFlowLayout *layout =
[[UICollectionViewFlowLayout alloc] init];
layout.sectionInset = UIEdgeInsetsMake(10.0f, 10.0f, 50.0f, 10.0f);
layout.minimumLineSpacing = 10.0f;
layout.minimumInteritemSpacing = 10.0f;
layout.itemSize = CGSizeMake(50.0f, 50.0f);
layout.scrollDirection = UICollectionViewScrollDirectionVertical;

TestBedViewController *tbvc = [[TestBedViewController alloc]
initWithCollectionViewLayout:layout];

UINavigationController *nav = [[UINavigationController alloc]
initWithRootViewController:tbvc];
window.rootViewController = nav;

[window makeKeyAndVisible];
return YES;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Custom Cells

Recipe 11-1 created uniformly sized objects, but there’s no reason your collections cannot be filled with items of any dimension. Flow layouts allow you to create far more nuanced presentations, as shown in Figure 11-4. Recipe 11-2 adapts its collection view to provide this juiced-up presentation by creating custom cells. These cells add image views, and the image’s size powers the “size for item at index path” callback to the collection view’s data source:

Click here to view code image

- (CGSize) collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
UIImage *image = artDictionary[indexPath];
return image.size;
}

Figure 11-4. Flow layouts work with items that present varying heights and widths, not just basic grids.

To create custom cells, subclass UICollectionViewCell and add any new views to the cell’s contentView. This recipe adds a single image view subview, and exposes it through an imageView property. When providing cells, the delegate adds custom images to the image view and the layout delegate specifies their sizes.

Recipe 11-2. Custom Collection View Cells

Click here to view code image

@interface ImageCell : UICollectionViewCell
{
UIImageView *imageView;
}
@property (nonatomic) UIImageView *imageView;
@end

@implementation ImageCell
- (id) initWithFrame:(CGRect)frame
{
if (![super initWithFrame:frame]) return nil;

_imageView = [[UIImageView alloc] initWithFrame:(CGRect){
.origin = CGPointMake(4.0f, 4.0f),
.size=CGRectInset(frame, 4.0f, 4.0f).size}];
_imageView.autoresizingMask =
UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.contentView addSubview:_imageView];

return self;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Scrolling Horizontal Lists

Collection views offer the ability to create horizontal scrolling lists, a counter point to table views that only scroll vertically. To accomplish this, you need to take a few things into account, primarily that flow layouts in their default state naturally wrap their sections. Consider Figure 11-5. It shows two collection views, both of which scroll horizontally. The top image consists of a single section with 100 items; the bottom has 100 sections of a single item each.

Figure 11-5. Top: A single section with 100 items. Bottom: 100 sections with a single item each.

You could force the top layout not to wrap by adding large left and right section margins, but getting these to work correctly is messy; the margins depend on both device and orientation. Assigning one item per section is a much easier solution and ensures a single line of items outside any issues of size.

Recipe 11-3 creates a horizontally scrolling collection as a stand-alone view rather than as a view controller. This approach allows the view to be inset as a subview, neatly avoiding the big empty area at the bottom of the screen shown in Figure 11-5 (bottom).

This recipe’s InsetCollectionView class provides its own data source and exposes its collection view as a readonly property to allow clients to provide delegation. Figure 11-6 shows this recipe in action, providing an embedded horizontally scrolling list.

Figure 11-6. Recipe 11-3 creates an embeddable horizontally scrolling collection view.

Recipe 11-8, which appears later in this chapter, introduces a fully customized layout subclass that offers true grid layouts. Recipe 11-3 offers a handy shortcut for anyone who wants to use the default flow layout, as shipped. Plus, it demonstrates how to create a collection view outside the context of a prebuilt controller.

Recipe 11-3. Horizontal Scroller Collection View

Click here to view code image

@interface InsetCollectionView : UIView
<UICollectionViewDataSource>
{
UICollectionView *collectionView;
}
@property (strong, readonly) UICollectionView *collectionView;
@end

@implementation InsetCollectionView

// 100 sections of 1 item each
- (NSInteger)numberOfSectionsInCollectionView:
(UICollectionView *)collectionView
{
return 100;
}

- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
return 1;
}

// This is a little utility that returns a view showing the
// section and item numbers for an index path
- (UIImageView *) viewForIndexPath: (NSIndexPath *) indexPath
{
NSString *string = [NSString stringWithFormat:
@"S%d(%d)", indexPath.section, indexPath.item];
UIImage *image = blockStringImage(string, 16.0f);
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
return imageView;
}

// Return an initialized cell
- (UICollectionViewCell *)collectionView:(UICollectionView *)_collectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView
dequeueReusableCellWithReuseIdentifier:@"cell"
forIndexPath:indexPath];

cell.backgroundColor = [UIColor whiteColor];
cell.selectedBackgroundView =
[[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor =
[[UIColor blackColor] colorWithAlphaComponent:0.5f];

// Show the section and item in a custom subview
if ([cell viewWithTag:999])
[[cell viewWithTag:999] removeFromSuperview];
UIImageView *imageView = [self viewForIndexPath:indexPath];
imageView.tag = 999;
[cell.contentView addSubview:imageView];

return cell;
}
#pragma mark Setup
- (id) initWithFrame:(CGRect)frame
{
if (!([super initWithFrame:frame])) return nil;

// Setup horizontal layout
UICollectionViewFlowLayout *layout =
[[UICollectionViewFlowLayout alloc] init];
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
layout.sectionInset = UIEdgeInsetsMake(10.0f, 10.0f, 40.0f, 10.0f);
layout.minimumLineSpacing = 10.0f;
layout.minimumInteritemSpacing = 10.0f;
layout.itemSize = CGSizeMake(100.0f, 100.0f);

// Create collection view
collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero
collectionViewLayout:layout];
collectionView.backgroundColor = [UIColor darkGrayColor];
collectionView.dataSource = self;

// Register cells
[collectionView registerClass:[UICollectionViewCell class]
forCellWithReuseIdentifier:@"cell"];

return self;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Introducing Interactive Layout Effects

Flow layouts are fully controllable. When subclassing UICollectionViewFlowLayout, you gain immediate real-time control over how items are sized and placed onscreen. This provides incredible power to you as a developer letting you specify item presentation with great delicacy. You can use this power to develop flows that seem to work in three dimensions, or ones that break the linear mold and transform columns and rows into circles, piles, Bezier curves, and more.

Customizable layout attributes include standard layout elements (frame, center, and size), transparency (alpha and hidden), position on the z-axis (zIndex), and transform (transform3d). You adjust these when the flow layout requests element attributes, as demonstrated in Recipe 11-4.

This recipe creates a flow that zooms items out towards the user in the center of the screen, and shrinks them as they move away to the left or right. It calculates how far away each item is from the horizontal center of the screen. It applies its scaling based on a cosine function (that is, one that maxes out as the distance from the center decreases).

Figure 11-7 shows this effect, although it’s much better to run the recipe yourself and see the changes in action.

Figure 11-7. The custom layout defined by Recipe 11-4 flow zooms items as they move toward the horizontal center of the screen.

Recipe 11-4. Interactive Layout Effects

Click here to view code image

@interface PunchedLayout : UICollectionViewFlowLayout
@end
@implementation PunchedLayout

// Allow the presentation to resize as needed
- (BOOL)shouldInvalidateLayoutForBoundsChange: (CGRect) oldBounds
{
return YES;
}

// Layout elements
- (NSArray *)layoutAttributesForElementsInRect: (CGRect) rect
{
// Retrieve the default layout
NSArray *array = [super layoutAttributesForElementsInRect:rect];
for (UICollectionViewLayoutAttributes* attributes in array)
{
// Only handle layouts for visible items
if (!CGRectIntersectsRect(attributes.frame, rect)) continue;

// Calculate the distance from the view center
CGSize boundsSize = self.collectionView.bounds.size;
CGFloat midX = boundsSize.width / 2.0f;
CGPoint contentOffset = self.collectionView.contentOffset;
CGPoint itemCenter = CGPointMake(
attributes.center.x - contentOffset.x,
attributes.center.y - contentOffset.y);
CGFloat distance = ABS(midX - itemCenter.x);

// Normalize the distance and calculate the zoom factor
CGFloat normalized = distance / midX;
normalized = MIN(1.0f, normalized);
CGFloat zoom = cos(normalized * M_PI_4);

// Set the transform
attributes.transform3D = CATransform3DMakeScale(zoom, zoom, 1.0f);
}
return array;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Scroll Snapping

Because Recipe 11-4 focuses user attention at the center of the screen, why not ensure that the central object moves to the most optimal position? You accomplish this by implementing a layout method that snaps to specific boundaries. Recipe 11-5 shows how.

This targetContentOffsetForProposedContentOffset: method, which is called during scrolling, specifies where the scroll would naturally stop. It iterates through all the onscreen objects, finds the one closest to the view’s horizontal center, and adjusts the offset so that object’s center coincides with the view’s.

Recipe 11-5. Customizing the Target Content Offset

Click here to view code image

- (CGPoint) targetContentOffsetForProposedContentOffset: (CGPoint)
proposedContentOffset
withScrollingVelocity: (CGPoint) velocity
{
CGFloat offsetAdjustment = MAXFLOAT;

// Retrieve all onscreen items at the proposed starting point
CGRect targetRect = CGRectMake(proposedContentOffset.x, 0.0,
boundsSize.width, boundsSize.height);
NSArray *array = [super layoutAttributesForElementsInRect:targetRect];

// Determine the proposed center x-coordinate
CGFloat proposedCenterX = proposedContentOffset.x + midX;

// Search for the minimum offset adjustment
for (UICollectionViewLayoutAttributes* layoutAttributes in array)
{
CGFloat distance = layoutAttributes.center.x - proposedCenterX;
if (ABS(distance) < ABS(offsetAdjustment))
offsetAdjustment = distance;
}

// Offset the content by the minimal centering
return CGPointMake(proposedContentOffset.x + offsetAdjustment,
proposedContentOffset.y);
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Creating a Circle Layout

Circle layouts offer an eye-catching way to arrange views around a central area. Recipe 11-6 is heavily based on Apple’s sample code, which was first presented at WWDC 2012. This layout provides an excellent introduction to the way items can animate into place upon creation and deletion.

Recipe 11-6’s layout flow uses a fixed content size via the collectionViewContentSize method. This prevents collection view scrolling as it creates a layout area with well-understood geometry. The code further limits its layout to an inset area, calculated in the prepareLayout method. The height or width of the screen, whichever is currently smaller, determines the circle’s radius. This remains fixed regardless of device orientation.

The layout calculates each item’s position by its index path. This presentation uses a single section, and the order of the item within that section (that is, whether it is the third or fifth item) sets its progress along the circle:

Click here to view code image

CGFloat progress = (float) path.item / (float) numberOfItems;
CGFloat theta = 2.0f * M_PI * progress;

You can easily extend this to any shape or path whose progress can be normalized within the range [0.0, 1.0]. For a circle, this goes from 0 to 2 Pi. A spiral might go out 3, 4, or even 5 Pi. For a Bezier curve, you’d iterate along whatever control points define the curve and interpolate between them as needed.

Creation and Deletion Animation

Of particular interest in Recipe 11-6 are the methods that specify the initial attributes for newly inserted items and final attributes for newly deleted ones. These properties allow your collection views to animate item creation and deletion from the previous layout to the new layout after those items have been added or removed.

In this recipe, as in Apple’s original sample code, new items start off transparent in the center of the circle and fade into view as they move out to their assigned position. Deleted items shrink, fade, and move to the center. When you run the sample code, you’ll see these animations take effect.

As of late iOS 6 betas, the starting and ending attribute requests are called on all items, not just the added and deleted ones. Because of this, Recipe 11-6 sorts items into collections: added index paths and deleted index paths. It limits its custom insertion and deletion attributes to those items.

This mechanism offers a way to animate layout attributes for all items, enabling you to add extra animations as needed. For example, you might animate an object moving from the end of row 3 to the start of row 4 as a new item is inserted into row 3. This approach allows you to animate the cell off screen to the right of row 3 and then onscreen from the left of row 4 versus the default behavior, which has it move diagonally from its old position to the new one.

Unfortunately, this recipe is subject to future changes; it appears that Apple has not fully finished designing layout classes.

Powering the Circle Layout

I made a number of changes to Apple’s original sample in putting together this recipe. For one thing, Recipe 11-6 uses Add and Delete bar buttons rather than gestures. For another, each view is distinct and identifiable by its color. Instead of deleting “any item” or adding “some item,” Recipe 11-6 uses selections. The user chooses an item to focus on. That selection controls which item is deleted (the selected item) or where new items should be added (just after the selected item).

Here is the deletion code. It retrieves the currently selected item, deletes it, and selects the next item. Then it enables or disables the Add and Delete buttons depending on how many items are currently onscreen:

Click here to view code image

- (void) delete
{
if (!count) return;

// Decrement the count
count -= 1;

// Determine which item to delete
NSArray *selectedItems = [self.collectionView indexPathsForSelectedItems];
NSInteger itemNumber = selectedItems.count ?
((NSIndexPath *)selectedItems[0]).item : 0;

NSIndexPath *itemPath = [NSIndexPath indexPathForItem:itemNumber inSection:0];

// Perform deletion
[self.collectionView performBatchUpdates:^{
[self.collectionView deleteItemsAtIndexPaths:@[itemPath]];
} completion:^(BOOL done){
if (count)
[self.collectionView selectItemAtIndexPath:
[NSIndexPath indexPathForItem:MAX(0, itemNumber - 1) inSection:0]
animated:NO scrollPosition:UICollectionViewScrollPositionNone];
self.navigationItem.rightBarButtonItem.enabled = (count > 0);
self.navigationItem.leftBarButtonItem.enabled =
(count < (IS_IPAD ? 20 : 8));
}];
}

In the real world, there are very few use-cases for adding and deleting interchangeable views, but there are many for views that have meaning. These changes provide a more solid jumping-off point for extending this recipe to practical applications.

The Layout

Figure 11-8 shows the layout built by Recipe 11-6. As users add new items, the circle grows more crowded, up to a maximum count of 20 items on the iPad and 8 on the iPhone. You can easily modify these limits in the add and delete methods to match the view sizes for your particular application.

Figure 11-8. This circle layout flow is inspired by sample code provided by Apple and was encouraged by the efforts of developer Greg Hartstein.

Recipe 11-6. Laying Out Views in a Circle

Click here to view code image

@implementation CircleLayout

- (void) prepareLayout
{
[super prepareLayout];

CGSize size = self.collectionView.frame.size;
numberOfItems = [self.collectionView numberOfItemsInSection:0];
centerPoint = CGPointMake(size.width / 2.0f, size.height / 2.0f);
radius = MIN(size.width, size.height) / 3.0f;
}

// Fix the content size to the frame size
- (CGSize) collectionViewContentSize
{
return self.collectionView.frame.size;
}

// Calculate position for each item
- (UICollectionViewLayoutAttributes *)
layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes *attributes =
[UICollectionViewLayoutAttributes
layoutAttributesForCellWithIndexPath:path];
CGFloat progress = (float) path.item / (float) numberOfItems;
CGFloat theta = 2.0f * M_PI * progress;
CGFloat xPosition = centerPoint.x + radius * cos(theta);
CGFloat yPosition = centerPoint.y + radius * sin(theta);
attributes.size = [self itemSize];
attributes.center = CGPointMake(xPosition, yPosition);
return attributes;
}

// Calculate layouts for all items
- (NSArray *) layoutAttributesForElementsInRect: (CGRect) rect
{
NSMutableArray *attributes = [NSMutableArray array];
for (NSInteger index = 0 ; index < numberOfItems; index++)
{
NSIndexPath *indexPath =
[NSIndexPath indexPathForItem:index inSection:0];
[attributes addObject:
[self layoutAttributesForItemAtIndexPath:indexPath]];
}
return attributes;
}

// Build insertion and deletion collections from updates
- (void)prepareForCollectionViewUpdates: (NSArray *)updates
{
[super prepareForCollectionViewUpdates:updates];

for (UICollectionViewUpdateItem* updateItem in updates)
{
if (updateItem.updateAction == UICollectionUpdateActionInsert)
[insertedIndexPaths
addObject:updateItem.indexPathAfterUpdate];
else if (updateItem.updateAction ==
UICollectionUpdateActionDelete)
[deletedIndexPaths
addObject:updateItem.indexPathBeforeUpdate];
}
}

// Establish starting attributes for added item
- (UICollectionViewLayoutAttributes *)
insertionAttributesForItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
UICollectionViewLayoutAttributes *attributes =
[self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.alpha = 0.0;
attributes.center = centerPoint;
return attributes;
}

// Establish ending attributes for deleted item
- (UICollectionViewLayoutAttributes *)
deletionAttributesForItemAtIndexPath:(NSIndexPath *)itemIndexPath
{
UICollectionViewLayoutAttributes *attributes =
[self layoutAttributesForItemAtIndexPath:itemIndexPath];
attributes.alpha = 0.0;
attributes.center = centerPoint;
attributes.transform3D = CATransform3DMakeScale(0.1, 0.1, 1.0);
return attributes;
}

// Handle insertion animation for all items
- (UICollectionViewLayoutAttributes*)
initialLayoutAttributesForAppearingItemAtIndexPath:
(NSIndexPath*)indexPath
{
return [insertedIndexPaths containsObject:indexPath] ?
[self insertionAttributesForItemAtIndexPath:indexPath] :
[super initialLayoutAttributesForAppearingItemAtIndexPath:
indexPath];
}

// Handle deletion animation for all items
- (UICollectionViewLayoutAttributes*)
finalLayoutAttributesForDisappearingItemAtIndexPath:
(NSIndexPath*)indexPath
{
return [deletedIndexPaths containsObject:indexPath] ?
[self deletionAttributesForItemAtIndexPath:indexPath] :
[super finalLayoutAttributesForDisappearingItemAtIndexPath:
indexPath];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Adding Gestures to Layout

Recipe 11-7 builds on Recipe 11-6 by adding interactive gestures that adjust presentation layout. It uses two recognizers, a pinch recognizer and a rotation recognizer, to enable users to scale and rotate the circle of views. These items are set up to recognize simultaneously, so users can pinch and rotate at the same time.

The rotate recognizer uses a slightly more sophisticated approach than the pinch one. Unlike pinch values, rotations are relative. You rotate by an amount, not to a specific angle. To accommodate this, Recipe 11-7 implements callbacks to handle two states. The first is called as rotations happen, updating the presentation to match each movement. The second resets the rotation baseline as the gesture ends, so the next interaction will take up where the last left off:

Click here to view code image

- (void) pinch: (UIPinchGestureRecognizer *) pinchRecognizer
{
CircleLayout *layout =
(CircleLayout *)self.collectionView.collectionViewLayout;
[layout scaleTo:pinchRecognizer.scale];
[layout invalidateLayout];
}

- (void) rotate: (UIRotationGestureRecognizer *) rotationRecognizer
{
CircleLayout *layout =
(CircleLayout *)self.collectionView.collectionViewLayout;

if (rotationRecognizer.state == UIGestureRecognizerStateEnded)
[layout rotateTo:rotationRecognizer.rotation];
else
[layout rotateBy:rotationRecognizer.rotation];
[layout invalidateLayout];
}

Notice how these callbacks invalidate the layout so that the presentation is updated in real time. This recipe is best tested on-device due to the high graphical load.

Recipe 11-7 calculates the effect of user gestures on the layout by adjusting the view radius (it scales from a minimum of 0.5 to a maximum of 1.3 times the original layout) and the layout’s start angle, which is initially at 0 degrees but is adjusted each time the rotation updates. The scaled radius and the adjusted angle value form the basis for the new presentation.

Recipe 11-7. Adding Gestures to Collection View Layouts

Click here to view code image

// Intermediate rotation
- (void) rotateBy: (CGFloat)theta { currentRotation = theta; }

// Final rotation
- (void) rotateTo: (CGFloat) theta
{
rotation += theta;
currentRotation = 0.0f;
}

// Scaling
- (void) scaleTo: (CGFloat) factor
{
scale = factor;
}

// Calculate position for each item
- (UICollectionViewLayoutAttributes *)
layoutAttributesForItemAtIndexPath:(NSIndexPath *)path
{
UICollectionViewLayoutAttributes *attributes =
[UICollectionViewLayoutAttributes
layoutAttributesForCellWithIndexPath:path];
CGFloat progress = (float) path.item / (float) numberOfItems;
CGFloat theta = 2.0f * M_PI * progress;

// Update the scaling and rotation to match the current gesture
CGFloat scaledRadius = MIN(MAX(scale, 0.5f), 1.3f) * radius;
CGFloat rotatedTheta = theta + rotation + currentRotation;

// Calculate the new positions
CGFloat xPosition =
centerPoint.x + scaledRadius * cos(rotatedTheta);
CGFloat yPosition =
centerPoint.y + scaledRadius * sin(rotatedTheta);
attributes.size = [self itemSize];
attributes.center = CGPointMake(xPosition, yPosition);
return attributes;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Creating a True Grid Layout

The default flow layout wraps its rows to fit into a scrolling view that moves in just one direction. If you’re willing to do the math—and I warn you there’s quite a bit of it, and I’m not entirely convinced that I’ve gotten it all exactly right—you can create a custom layout subclass that scrolls in both directions and doesn’t wrap its lines. Figure 11-9 shows this layout.

Figure 11-9. This custom layout grid enables users to scroll in both directions.

Recipe 11-8 fully customizes its layout subclass, overriding collectionViewContentSize and layoutAttributesForItemAtIndexPath: to manually place each item. This implementation fully respects all spacing requests and delegate callbacks. In contrast, the normal flow layout attempts to fit items in while meeting various minimum values. This layout uses those values exactly, instead, adjusting the underlying scrolling view’s content size to precisely match sizing needs.

This recipe works by exhaustively calculating each layout element. What it doesn’t use, however, is the line-spacing property that describes how to wrap rows. This grid presentation never wraps any rows, so it ignores that entirely.

It also adds a new custom layout property, alignment. This property controls whether each grid row aligns at the top, center, or bottom. It accomplishes this by looking at the overall height for an entire row, and then optionally offsetting items that are smaller than that height.

I’ve included the entire layout code to give you a sense of how much effort is involved for a complete custom subclass. The trick is, of course, in the details. Test layouts as thoroughly as possible over a wide range of source objects.

Recipe 11-8. Grid Layout Customization

Click here to view code image

@implementation GridLayout

#pragma mark Items
// Does a delegate provide individual sizing?
- (BOOL) usesIndividualSizing
{
return [self.collectionView.delegate respondsToSelector:
@selector(collectionView:layout:sizeForItemAtIndexPath:)];
}

// Return cell size for an item
- (CGSize) sizeForItemAtIndexPath: (NSIndexPath *) indexPath
{
BOOL individuallySized = [self usesIndividualSizing];
CGSize itemSize = self.itemSize;
if (individuallySized)
itemSize = [(id <UICollectionViewDelegateFlowLayout>)
self.collectionView.delegate
collectionView:self.collectionView
layout:self sizeForItemAtIndexPath:indexPath];
return itemSize;
}

#pragma mark Insets
// Individual insets?
- (BOOL) usesIndividualInsets
{
return [self.collectionView.delegate respondsToSelector:
@selector(collectionView:layout:insetForSectionAtIndex:)];
}

// Return insets for section
- (UIEdgeInsets) insetsForSection: (NSInteger) section
{
UIEdgeInsets insets = self.sectionInset;
if ([self usesIndividualInsets])
insets = [(id <UICollectionViewDelegateFlowLayout>)
self.collectionView.delegate
collectionView:self.collectionView
layout:self insetForSectionAtIndex:section];
return insets;
}

#pragma mark Item Spacing
// Individual item spacing?
- (BOOL) usesIndividualItemSpacing
{
return [self.collectionView.delegate respondsToSelector:
@selector(layout:minimumInteritemSpacingForSectionAtIndex:)];
}

// Return spacing for section
- (CGFloat) itemSpacingForSection: (NSInteger) section
{
CGFloat spacing = self.minimumInteritemSpacing;
if ([self usesIndividualItemSpacing])
spacing = [(id <UICollectionViewDelegateFlowLayout>)
self.collectionView.delegate
collectionView:self.collectionView
layout:self
minimumInteritemSpacingForSectionAtIndex:section];
return spacing;
}

#pragma mark Layout Geometry
// Find the tallest subview
- (CGFloat) maxItemHeightForSection: (NSInteger) section
{
CGFloat maxHeight = 0.0f;
NSInteger numberOfItems =
[self.collectionView numberOfItemsInSection:section];
for (int i = 0; i < numberOfItems; i++)
{
NSIndexPath *indexPath = INDEXPATH(section, i);
CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];
maxHeight = MAX(maxHeight, itemSize.height);
}
return maxHeight;
}

// "Horizontal" row-based extent from the start of the section to its end
- (CGFloat) fullWidthForSection: (NSInteger) section
{
UIEdgeInsets insets = [self insetsForSection:section];
CGFloat horizontalInsetExtent = insets.left + insets.right;
CGFloat collectiveWidth = horizontalInsetExtent;

NSInteger numberOfItems =
[self.collectionView numberOfItemsInSection:section];
for (int i = 0; i < numberOfItems; i++)
{
NSIndexPath *indexPath = INDEXPATH(section, i);
CGSize itemSize = [self sizeForItemAtIndexPath:indexPath];

collectiveWidth += itemSize.width;
collectiveWidth += [self itemSpacingForSection:section];
}

// Take back one spacer, n-1 fence post
collectiveWidth -= [self itemSpacingForSection:section];

return collectiveWidth;
}

// Bounding size for each section
- (CGSize) fullSizeForSection: (NSInteger) section
{
CGFloat headerExtent = (self.scrollDirection ==
UICollectionViewScrollDirectionHorizontal) ?
self.headerReferenceSize.width :
self.headerReferenceSize.height;
CGFloat footerExtent =(self.scrollDirection ==
UICollectionViewScrollDirectionHorizontal) ?
self.footerReferenceSize.width :
self.footerReferenceSize.height;

UIEdgeInsets insets = [self insetsForSection:section];
CGFloat verticalInsetExtent = insets.top + insets.bottom;
CGFloat maxHeight = [self maxItemHeightForSection:section];

CGFloat fullHeight = headerExtent + footerExtent +
verticalInsetExtent + maxHeight;
CGFloat fullWidth = [self fullWidthForSection:section];

return CGSizeMake(fullWidth, fullHeight);
}

// How far is each item offset within the section
- (CGFloat) horizontalInsetForItemAtIndexPath: (NSIndexPath *) indexPath
{
UIEdgeInsets insets = [self insetsForSection:indexPath.section];
float horizontalOffset = insets.left;
if (indexPath.item > 0)
{
for (int i = 0; i < indexPath.item; i++)
{
CGSize itemSize = [self sizeForItemAtIndexPath:
INDEXPATH(indexPath.section, i)];
horizontalOffset += (itemSize.width +
[self itemSpacingForSection:indexPath.section]);
}
}
return horizontalOffset;
}

// How far is each item down
- (CGFloat) verticalInsetForItemAtIndexPath: (NSIndexPath *) indexPath
{
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];
CGFloat verticalOffset = 0.0f;

// Previous sections
if (indexPath.section > 0)
{
for (int i = 0; i < indexPath.section; i++)
verticalOffset += [self fullSizeForSection:i].height;
}

// Header
CGFloat headerExtent = (self.scrollDirection ==
UICollectionViewScrollDirectionHorizontal) ?
self.headerReferenceSize.width : self.headerReferenceSize.height;
verticalOffset += headerExtent;

// Top inset
UIEdgeInsets insets = [self insetsForSection:indexPath.section];
verticalOffset += insets.top;

// Vertical centering
CGFloat maxHeight = [self maxItemHeightForSection:indexPath.section];
CGFloat fullHeight = (maxHeight - thisItemSize.height);
CGFloat midHeight = fullHeight / 2.0f;

switch (self.alignment)
{
case GridRowAlignmentNone:
case GridRowAlignmentTop:
break;
case GridRowAlignmentCenter:
verticalOffset += midHeight;
break;
case GridRowAlignmentBottom:
verticalOffset += fullHeight;
break;
default:
break;
}

return verticalOffset;
}

#pragma mark Layout Attributes
// Provide per-item placement
- (UICollectionViewLayoutAttributes *) layoutAttributesForItemAtIndexPath:
(NSIndexPath *) indexPath
{
UICollectionViewLayoutAttributes *attributes =
[UICollectionViewLayoutAttributes
layoutAttributesForCellWithIndexPath:indexPath];
CGSize thisItemSize = [self sizeForItemAtIndexPath:indexPath];

float verticalOffset =
[self verticalInsetForItemAtIndexPath:indexPath];
float horizontalOffset =
[self horizontalInsetForItemAtIndexPath:indexPath];

if (self.scrollDirection == UICollectionViewScrollDirectionVertical)
attributes.frame = CGRectMake(horizontalOffset,
verticalOffset, thisItemSize.width, thisItemSize.height);
else
attributes.frame = CGRectMake(verticalOffset,
horizontalOffset, thisItemSize.width, thisItemSize.height);

return attributes;
}

// Return full extent
- (CGSize) collectionViewContentSize
{
NSInteger sections = self.collectionView.numberOfSections;

CGFloat maxWidth = 0.0f;
CGFloat collectiveHeight = 0.0f;

for (int i = 0; i < sections; i++)
{
CGSize sectionSize = [self fullSizeForSection:i];
collectiveHeight += sectionSize.height;
maxWidth = MAX(maxWidth, sectionSize.width);
}

if (self.scrollDirection == UICollectionViewScrollDirectionVertical)
return CGSizeMake(maxWidth, collectiveHeight);
else
return CGSizeMake(collectiveHeight, maxWidth);
}

// Provide grid layout attributes
- (NSArray *) layoutAttributesForElementsInRect: (CGRect) rect
{
NSMutableArray *attributes = [NSMutableArray array];
for (NSInteger section = 0;
section < self.collectionView.numberOfSections; section++)
for (NSInteger item = 0 ;
item < [self.collectionView numberOfItemsInSection: section];
item++)
{
UICollectionViewLayoutAttributes *layout =
[self layoutAttributesForItemAtIndexPath:
INDEXPATH(section, item)];
[attributes addObject:layout];
}
return attributes;
}

- (BOOL) shouldInvalidateLayoutForBoundsChange: (CGRect) oldBounds
{
return YES;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Recipe: Custom Item Menus

I’m quite sure there’s a better way to handle nonstandard collection view menus, like the one shown in Figure 11-10. Alas, my attempts to make them happen using the delegate method collectionView:shouldShowMenuForItemAtIndexPath: failed to produce any meaningful results. Instead, I created a custom cell class and added a double-tap gesture recognizer.

Figure 11-10. These custom item-by-item menus require cells to become first responder.

When activated, the callback sets the cell as the first responder (usually with a complaint about not knowing the type of the collection view’s first responder) and presents a standard menu.

Recipe 11-9 shows the relevant details. The cell subclass declares that it can become first responder, a necessary precondition for presenting menus. It sets the menu items it wants to work with and then adds the canPerformAction:withSender: support that confirms each item’s appearance. Figure 11-10 displays the menu created by this code.

Recipe 11-9. Custom Collection View Cell Menus

Click here to view code image

- (BOOL) canBecomeFirstResponder
{
return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
if (action == @selector(ghostSelf)) return YES;
if (action == @selector(popSelf)) return YES;
if (action == @selector(rotateSelf)) return YES;
if (action == @selector(colorize)) return YES;
return NO;
}

- (void) tapped: (UIGestureRecognizer *) uigr
{
if (uigr.state != UIGestureRecognizerStateRecognized) return;

[[UIMenuController sharedMenuController] setMenuVisible:NO animated:YES];
[self becomeFirstResponder];

UIMenuController *menu = [UIMenuController sharedMenuController];
UIMenuItem *pop = [[UIMenuItem alloc]
initWithTitle:@"Pop" action:@selector(popSelf)];
UIMenuItem *rotate = [[UIMenuItem alloc]
initWithTitle:@"Rotate" action:@selector(rotateSelf)];
UIMenuItem *ghost = [[UIMenuItem alloc]
initWithTitle:@"Ghost" action:@selector(ghostSelf)];
UIMenuItem *colorize = [[UIMenuItem alloc]
initWithTitle:@"Colorize" action:@selector(colorize)];

[menu setMenuItems:@[pop, rotate, ghost, colorize]];
[menu update];
[menu setTargetRect:self.bounds inView:self];
[menu setMenuVisible:YES animated:YES];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 11.

Summary

This chapter introduced collection views, along with their custom layout flows. You read how to create both basic collection view controllers as well as their stand-alone views. You discovered how to set critical layout properties. You learned about creating live effect feedback and insertion and deletion dynamic effects. Before moving on to the next chapter, here are a few points to consider about collection views:

• Collection views offer an amazing amount of power without requiring a lot of coding. Most things that were maddening and nearly impossible with table views are now possible with a much more powerful set of APIs.

• This chapter barely touched on header and footer views, and didn’t use decoration views at all. See the sample code included with this chapter for more details on the fine points of creating custom supplementary view classes.

• Transform-based updates help bring life to your collection view layouts. Don’t be afraid to let your interfaces animate to respond to user interactions. At the same time, avoid adding effects simply for the sake of adding effects. A little animation goes a long way.

• Speaking of animations, the same inserted and deleted attribute methods this chapter used for items are available for supplementary elements. This feature lets you animate the arrival and departure of new sections in your collection.

• On a similar note, integrate gestures meaningfully. If a user isn’t likely to discover your long-press or triple-tap add or deletion request, skip it. Instead, use pop-ups, menus, floating overlays, or simple buttons to communicate how items can be managed and changed.

• When exploring layout, don’t depend on the flow layout documentation. Look instead through the UICollectionViewLayout abstract parent class. It details all the core methods you override.

• Finally, always test on devices. Layouts, especially ones that update frequently or use transforms, can tax the simulator. Device testing, along with Instruments, will better reflect whether you’re actually asking too much from your presentation.

12. A Taste of Core Data
iOS’s Core Data framework provides persistent data solutions. Its managed data stores can be queried and updated from your application. With Core Data, you gain a Cocoa Touch-based object interface that brings relational data management out from SQL queries and into the Objective-C world of iOS development. Core Data delivers the perfect technology to power your table view and collection view instances.

This chapter introduces Core Data. It provides just enough how-to to give you a taste of the technology, offering a jumping-off point for further Core Data learning. By the time you finish reading through this chapter, you’ll have seen Core Data for iOS in action and have gained an overview of the technology.

Introducing Core Data

Core Data simplifies the way your applications create and use managed objects. Until the 3.x software development kit (SDK), all data management and SQL access were left to a fairly low-level library. It wasn’t pretty, and it wasn’t easy to use. Since then, Core Data has joined the Cocoa Touch framework family, bringing powerful data management solutions to iOS. Core Data provides a flexible infrastructure, offering tools for working with persistent data stores and generating solutions for the complete object life cycle.

Core Data lives in the Model portion of the Model-View-Controller paradigm. It understands that application-specific data must be defined and controlled outside the application’s GUI, even as it powers that interface. Core Data integrates beautifully with table view and collection view instances. Cocoa Touch’s fetched-results controller class was designed and built with these kinds of classes in mind. It offers useful properties and methods that support data source and delegate integration.

Entities and Models

Entities live at the top of the Core Data hierarchy. They describe objects stored inside your database. Entities provide the virtual cookie cutters that specify how each data object is created. When you build new objects, entities detail the attributes and relationships that make up each object. Every entity has a name, which Core Data uses to retrieve entity descriptions as your application runs.

You build entities inside model files. Each project that links against the Core Data framework includes one or more model files. These .xcdatamodel files define entities, their attributes, and their relationships.

Building a Model File

Create your model in Xcode by laying out a new data model file. Some iOS templates allow you to include Core Data as part of the project. Otherwise, you create these Xcode model files by selecting File, New, File, iOS, Core Data, Data Model, Next. Enter a name for your new file (this example uses Person), check the targets for your project, and click Save. Xcode creates and then adds the new model file to your project (for example, Person.xcdatamodel). Click the xcdatamodel file in the File Navigator to open it in the editor window shown in Figure 12-1.

Figure 12-1. Xcode’s editor enables you to build managed object definitions for your Core Data applications.

You add new entities (basically classes of objects) to the left list in the editor window by clicking the Add Entity button near the bottom left. Add attributes (essentially instance variables for entities) by clicking the Add Attribute button at the bottom right. Double-click any individual entity or attribute name to change it; use the type pop-up to set an attribute’s type.

The center portion of the editor customizes your attributes and relationships, which are the optional way entities relate to each other in the database. An inspector to the right provides context-specific settings. Here, it’s showing details for the Person entity’s emailaddress attribute.

The Entity editor provides two layout styles. Toggle between the table view and an object graph by tapping the buttons at the bottom right of the editor pane.

The detail table style shown in Figure 12-1 provides a list of each entity, attribute, and relationship defined in the model. The object graph offers a grid-based visual presentation of the entities you have defined, allowing you to visualize and edit entity relationships, the way entities relate to each other. For example, a parent can have several children and one spouse. A department may include members, and a manager may serve on several committees.

Attributes and Relationships

Each entity may include attributes, which store information such as a name, a birth date, a designation, and so forth. The Objective-C object that corresponds to this entity expresses properties defined by these attributes.

Each entity may also define relationships, which are links between one object and another. These relationships can be single, using a one-to-one relationship (spouse, employer), or they can be multiple (children, credit card accounts), using a one-to-many relationship. In addition, relationships should be reciprocal, providing an inverse relationship (my child, his parent).

Select an entity to start adding attributes. With the entity selected, tap the Add Attribute button at the bottom right of the editor pane. (Tap-and-hold this button to choose between Add Attribute, Add Relationship, and Add Fetched Property.) Each attribute has a name and a data type, just as you would define an instance variable.

Relationships provide pointers to other objects. When working with the graph editor, you can Control-drag to create them. Arrows represent the relationships between the various kinds of entities in your project.

At the simplest level, you can work with just one entity and without relationships, even though Core Data offers a fully powered relational database. Most iOS applications do not require a high level of sophistication. A flat database with section attributes is all you need to power table views and collection views.

To build the model in Figure 12-1, create a Person entity and add the seven attributes: email-address, gender, givenname, middleinitial, occupation, surname, and section. Set each type to String.

Building Object Classes

After creating your entity definition, save your changes to the data model file. Then select the entity from the column on the left and choose Editor, Create NSManagedObject Subclass. Save to your project folder, select the group you want to add the classes to, and click Create. Xcode generates class files from your entity description. Here is what the automatically generated Person class looks like:

Click here to view code image

@interface Person : NSManagedObject

@property (nonatomic, retain) NSString * section;
@property (nonatomic, retain) NSString * emailaddress;
@property (nonatomic, retain) NSString * gender;
@property (nonatomic, retain) NSString * givenname;
@property (nonatomic, retain) NSString * middleinitial;
@property (nonatomic, retain) NSString * occupation;
@property (nonatomic, retain) NSString * surname;

@end

@implementation Person

@dynamic section;
@dynamic emailaddress;
@dynamic gender;
@dynamic givenname;
@dynamic middleinitial;
@dynamic occupation;
@dynamic surname;

@end

Each attribute corresponds to a string property. When you use other attribute types, their properties will correspond accordingly (for example, NSDate, NSNumber, NSData). If you were to add a one-to-many relationship, you’d see a set. The @dynamic directive creates property accessors at runtime.

Creating Contexts

In Core Data, entities provide descriptions. Objects are actual class instances that you create from entity specifications. These instances all descend from the NSManagedObject class and represent entries in the database.

Core Data objects live within a managed object context. These contexts, which are instances of NSManagedObjectContext, represent an object space within your application. This chapter uses a single object context, although you can create more complex implementations in your own apps.

You establish your context as you start up your application and use that context for all object fetch requests from the stored data. The context story begins by loading any models you have created from the application bundle. You do not need to specify any names. Core Data does that work for you:

Click here to view code image

// Init the model
NSManagedObjectModel *managedObjectModel =
[NSManagedObjectModel mergedModelFromBundles:nil];

Next, create a store coordinator and connect it to a file (a store) in the app sandbox. The coordinator manages the relationship between the managed object model in your application with a local file. You provide a file URL, specifying where to save the data. This snippet uses an NSSQLiteStoreType, which creates a file using the standard SQLite binary format:

Click here to view code image

// Create the store coordinator
NSPersistentStoreCoordinator *persistentStoreCoordinator =
[[NSPersistentStoreCoordinator alloc]
initWithManagedObjectModel:managedObjectModel];

// Connect to the data store (on disk)
NSURL *url = [NSURL fileURLWithPath:dataPath];
if (![persistentStoreCoordinator
addPersistentStoreWithType: NSSQLiteStoreType
configuration:nil URL:url options:nil error:&error])
{
NSLog(@"Error creating persistent store coordinator: %@",
error.localizedFailureReason);
return;
}

Last, you create the actual context and set a property to the coordinator you just created:

Click here to view code image

// Create establish the context
_context = [[NSManagedObjectContext alloc] init];
_context.persistentStoreCoordinator = persistentStoreCoordinator;

Adding Data

The NSEntityDescription class enables you to insert new objects into your context. This lets you add new data entries to populate your file. Provide an entity name and the context you’re working with, and the request returns a new managed object for you to work with:

Click here to view code image

// Create new object
- (NSManagedObject *) newObject
{
NSManagedObject *object = [NSEntityDescription
insertNewObjectForEntityForName:_entityName
inManagedObjectContext:_context];
return object;
}

After you receive the new managed object, you customize it however you like and then save the context:

Click here to view code image

// Save
- (BOOL) save
{
NSError __autoreleasing *error;
BOOL success;
if (!(success = [_context save:&error]))
NSLog(@"Error saving context: %@", error.localizedFailureReason);
return success;
}

A typical call pattern goes like this: you create one or more new objects, you set their properties, and you save. You could use the above methods to insert a new Person in the database as follows:

Click here to view code image

Person *person = (Person *)[dataHelper newObject];
person.givenname = @"Chris";
person.surname = @"Zahn";
person.section = [[person.surname substringFromIndex:0] substringToIndex:1];
person.occupation = @"Editor";
[dataHelper save]

Notice that the section property here derives from the surname. In nearly every basic iOS application, you’ll want to add a section property to allow Core Data to group entries together by some common connection. The property name does not matter; you pass it as an argument. I call mine section because it’s easy to recognize and remember. Advanced users will write a method to provide their grouping criteria instead of hard-coding it as I do here.

This snippet created a group-by-surname-initial approach. When you want to group by some other property, either iterate through your data to update the property you use for sections or supply a different attribute to your fetch request. This flexibility makes it easy to change from group by last initial to group by occupation.

Don’t confuse iOS sections (used for table views and collection views) with sorting, another concept you encounter with Core Data. Sections specify groupings within your object collection. Sorting controls how items are ordered within each section.

Examining the Data File

If you run your code in the simulator, you can easily inspect the SQLite file that Core Data created from this recipe. Navigate to the simulator folder (~/Library/Application Support/iPhone Simulator/Firmware/User/Applications, where Firmware is the current firmware release; for example, 6.0) and into the folder for the application itself.

Stored in the Library folder (depending on the URL used to create the persistent store), a SQLite file contains the database representation you’ve created. The command-line sqlite3 utility enables you to inspect the contents by performing a .dump operation:

Click here to view code image

% sqlite3 Person.sqlite
SQLite version 3.7.12 2012-04-03 19:43:07
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .dump
PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE ZPERSON ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER,
ZEMAILADDRESS VARCHAR, ZGENDER VARCHAR, ZGIVENNAME VARCHAR, ZMIDDLEINITIAL VARCHAR,
ZOCCUPATION VARCHAR, ZSECTION VARCHAR, ZSURNAME VARCHAR );
INSERT INTO "ZPERSON"
VALUES(1,1,1,'ChristopherLRobinson@foomail.com','male','Christopher','L','Home care
aide','C','Robinson');
INSERT INTO "ZPERSON"
VALUES(2,1,1,'NicholasJGrant@spambob.com','male','Nicholas','J','Steadicam
operator','N','Grant');
INSERT INTO "ZPERSON"
VALUES(3,1,1,'JosephJTreece@spambob.com','male','Joseph','J','Shoe machine
operator','J','Treece');
INSERT INTO "ZPERSON"
VALUES(4,1,1,'HelenEShaffer@dodgit.com','female','Helen','E','Coin vending and
amusement machine servicer repairer','H','Shaffer');
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY, Z_NAME VARCHAR, Z_SUPER
INTEGER, Z_MAX INTEGER);
INSERT INTO "Z_PRIMARYKEY" VALUES(1,'Person',0,3000);
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY KEY, Z_UUID VARCHAR(255),
Z_PLIST BLOB);
INSERT INTO "Z_METADATA" VALUES(1,'85E928DB-1464-4C3B-BCEA-
9277B8817A04',X'62706C6973743030D601020304050607090A0D0E0F5F101E4E5353746F72654D6F6
4656C56657273696F6E4964656E746966696572735F101D4E5350657273697374656E63654672616D65
776F726B56657273696F6E5F10194E5353746F72654D6F64656C56657273696F6E4861736865735B4E5
353746F7265547970655F10125F4E534175746F56616375756D4C6576656C5F10204E5353746F72654D
6F64656C56657273696F6E48617368657356657273696F6EA1085011019AD10B0C56506572736F6E4F1
020D261E3854795D61A5D69048846ECC3DCFEAC4861D9FCD1540A071C875FE89EA95653514C69746551
32100308153656727E93B6B8B9BCBFC6E9F0F2000000000000010100000000000000100000000000000
00000000000000000F4');
COMMIT;
sqlite> .quit
%

Here you see several SQL table definitions that store the information for each object plus the insert commands used to store the instances built in your code. Although you are thoroughly cautioned against directly manipulating the Core Data store with sqlite3, it offers a valuable insight into what’s going on under the Core Data hood.

Querying the Database

Retrieve objects from the database by performing fetch requests. A fetch request describes your search criteria for selecting objects. It’s passed through and used to initialize a results object that contains an array of fetched objects that meet those criteria. Here is a sample fetch method, which is void. It saves the resulting fetched results to a local instance variable (_fetchedResultsController) associated with a helper class property:

Click here to view code image

- (void) fetchItemsMatching: (NSString *) searchString
forAttribute: (NSString *) attribute
sortingBy: (NSString *) sortAttribute
{
// Build an entity description
NSEntityDescription *entity = [NSEntityDescription
entityForName:_entityName inManagedObjectContext:_context];

// Init a fetch request
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];

fetchRequest.entity = entity;
[fetchRequest setFetchBatchSize:0];

// Apply an ascending sort for the items
NSString *sortKey = sortAttribute ? : _defaultSortAttribute;
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc]
initWithKey:sortKey ascending:YES selector:nil];
NSArray *descriptors = @[sortDescriptor];
fetchRequest.sortDescriptors = descriptors;

// Optional setup predicate
if (searchString && attribute) fetchRequest.predicate =
[NSPredicate predicateWithFormat:@"%K contains[cd] %@",
attribute, searchString];

// Perform the fetch
NSError __autoreleasing *error;
_fetchedResultsController = [[NSFetchedResultsController alloc]
initWithFetchRequest:fetchRequest managedObjectContext:_context
sectionNameKeyPath:@"section" cacheName:nil];
if (![_fetchedResultsController performFetch:&error])
NSLog(@"Error fetching data: %@", error.localizedFailureReason);
}

Setting Up the Fetch Request

A fetch request describes how you want to search through data. This process starts by retrieving an entity description for a given entity name. For the Person entity, that name is @"Person". The description specifies what kinds of data you want to search for.

Create a new fetch request, initializing it with the entity description you just retrieved and a batch size. A 0 batch size corresponds to an indefinite request. If you want to limit the number of returned results, set the batch size to a positive number.

Each request must contain at least one sort descriptor. This method sorts in ascending order (ascending:YES) using a sort key. As with the entity name, the sort key is a string (for example, @"surname"). Set the fetch request’s sortDescriptors property with an array of descriptors.

Fetch requests use optional predicates to narrow down the results to items that match certain rules. When callers supply the appropriate searchString and attribute parameters, this method creates a predicate of the form attribute contains[cd] string.

This form creates a non-case-sensitive text match; the [cd] after contains refers to non-case-sensitive and non-diacritic-sensitive matching. Diacritics are small marks that accompany a letter, such as the dots of an umlaut or the tilde above a Spanish n.

The %@ format includes an item directly into the predicate, such as the search string used here. The %K format specifies an entity attribute. If you fail to use it, the predicate ’surname’ contains[cd] ‘u’ always return true because the second letter in surname is u. Use %K to match the property, not the name of the property.

For more complex queries, you could assign a compound predicate. Compound predicates allow you to combine simple predicates using standard logical operations such as AND, OR, and NOT. The NSCompoundPredicate class builds compound predicates out of component predicates. You can also skip the compound predicate class and include AND, OR, and NOT notation directly in simple NSPredicate text.

Performing the Fetch

Create a new fetched results controller for each query. Initialize it with the fetch request, the context, and the section name key path. For my apps, I just always use @"section" and make sure I define a section attribute for my objects; my needs are not complex.

The controller also uses a cache name parameter. Caching reduces overhead associated with producing data that’s structured with sections and indices. Multiple fetch requests are ignored when the data has not changed, minimizing the cost associated with fetch requests over the lifetime of an application. The name used for the cache is arbitrary. Either use nil to prevent caching or supply a name in the form of a string. This method uses nil to avoid errors related to mutating a fetch request.

Finally, you perform the fetch. If successful, the method returns true. If not, it updates the error that you pass by reference, so you can see why the fetch failed.

The fetch is synchronous. When this method returns, you can use the array of objects in the fetched results controller’s fetchedObjects property right away. Here’s an example of using this method to fetch data. The request searches for surnames matching a text field’s string and lists the matching data in a text view:

Click here to view code image

- (void) list
{
if (!textField.text.length) return;

[dataHelper fetchItemsMatching:textField.text
forAttribute:@"surname" sortingBy:@"surname"];
NSMutableString *string = [NSMutableString string];
for (Person *person in dataHelper.fetchedResultsController.fetchedObjects)
{
NSString *entry = [NSString stringWithFormat: @"%@, %@ %@: %@\n",
person.surname, person.givenname,
person.middleinitial, person.occupation];
[string appendString:entry];
}
textView.text = string;
}

Removing Objects

Removing objects in a flat database is straightforward: Just tell the context to delete the object and save the results. Here are two methods that delete either one object or all objects from your database:

Click here to view code image

// Delete one object
- (BOOL) deleteObject: (NSManagedObject *) object
{
[self fetchData];
if (!_fetchedResultsController.fetchedObjects.count) return NO;
[_context deleteObject:object];
return [self save];
}

// Delete all objects
- (BOOL) clearData
{
[self fetchData];
if (!_fetchedResultsController.fetchedObjects.count) return YES;
for (NSManagedObject *entry in
_fetchedResultsController.fetchedObjects)
[_context deleteObject:entry];
return [self save];
}

Working with relationships may prove slightly harder. Core Data ensures internal consistency before writing data out, throwing an error if it cannot. Some models that use cross-references get complicated. In some data models, you must clear lingering references before the object can safely be removed from the persistent store. If not, objects may point to deleted items, which is a situation that can lead to bad references.

To avoid this problem, set Core Data delete rules in the data model inspector. Delete rules control how an object responds to an attempted delete. You can Deny delete requests, ensuring that a relationship has no connection before allowing object deletion. Nullify resets inverse relationships before deleting an object. Cascade deletes an object plus all its relationships; for example, you could delete an entire department (including its members) all at once with a cascade. No Action provides that the objects pointed to by a relationship remain unaffected, even if those objects point back to the item about to be deleted.

Xcode issues warnings when it detects nonreciprocal relationships. Avoid unbalanced relationships to simplify your code and provide better internal consistency. If you cannot avoid nonreciprocal items, you need to take them into account when you create your delete methods.

Recipe: Using Core Data for a Table Data Source

Core Data on iOS works closely with table views. The NSFetchedResultsController class includes features that simplify the integration of Core Data objects with table data sources. As you can see in the following subsections, many of the fetched results class’s properties and methods are designed from the ground up for table support.

Index Path Access

The fetched results class offers object-index path integration in two directions. You can recover objects from a fetched object array using index paths by calling objectAtIndexPath:. You can query for the index path associated with a fetched object by calling indexPathForObject:. These two methods work with both sectioned tables and those tables that are flat—that is, that only use a single section for all their data.

Section Key Path

The sectionNameKeyPath property links a managed object attribute to section names. This property helps determine which section each managed object belongs to. You can set this property directly at any time, or you can initialize it when you set up your fetched results controller.

Recipe 12-1 uses an attribute named section to distinguish sections, although you can use any attribute name for this key path. For this example, this attribute is set to the first character of each object name to assign a managed object to a section. Set the key path to nil to produce a flat table without sections.

Section Groups

Recover section subgroups with the controller’s sections property. This property returns a collection of sections, each of which stores the managed objects whose section attribute maps to the same letter.

Each returned section implements the NSFetchedResultsSectionInfo protocol. This protocol ensures that sections can report their objects and numberOfObjects, their name, and an indexTitle—that is, the title that appears on the quick reference index optionally shown above and at the right of the table.

Index Titles

The fetched results controller’s sectionIndexTitles property generates a list of section titles from the sections within the fetched data. For Recipe 12-1, that array includes single-letter titles. The default implementation uses the value of each section key to return a list of all known sections.

Two further instance methods, sectionIndexTitleForSectionName: and sectionForSectionIndexTitle:atIndex:, provide section title lookup features. The first returns a title for a section name. The second looks up a section via its title. Override these to use section titles that do not match the data stored in the section name key.

Table Readiness

As these properties and methods reveal, fetched results instances are table ready. Recipe 12-1 presents all the standard table methods, adapted to Core Data fetched results. As you can see, each method used for creating and managing sections is tiny. The built-in Core Data access features reduce these methods to one or two lines each.

That’s because all the work in creating and accessing the sections is handed over directly to Core Data. The call that initializes each fetched data request specifies what data attribute to use for the sections. Core Data then takes over and performs the rest of the work.

Figure 12-2 shows the interface built by Recipe 12-1. It offers a full-feature table, complete with section headers and a floating index.

Figure 12-2. Recipe 12-1 creates a full-feature table with an absolute minimum of programming. Core Data powers all these features, from cell contents to section headers to an index.

Note

You’ll want to reset the simulator or delete the Hello World app off your devices between recipes in this chapter, because they all use the same database file (Person.sqlite), which will persist in the Documents folder.

Recipe 12-1. Building a Sectioned Table with Core Data

Click here to view code image

#pragma mark Data Source
// Number of sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return dataHelper.fetchedResultsController.sections.count;
}

// Rows per section
- (NSInteger)tableView:(UITableView *)tableView
numberOfRowsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo =
dataHelper.fetchedResultsController.sections[section];
return sectionInfo.numberOfObjects;
}

// Return the title for a given section
- (NSString *)tableView:(UITableView *)aTableView
titleForHeaderInSection:(NSInteger)section
{
NSArray *titles = [dataHelper.fetchedResultsController
sectionIndexTitles];
if (titles.count <= section)
return @"Error";
return titles[section];
}

// Section index titles
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
return [dataHelper.fetchedResultsController sectionIndexTitles];
}

// Populate a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:
@"cell" forIndexPath:indexPath];
Person *person = (Person *)[dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
cell.textLabel.text = person.fullname;

return cell;
}

#pragma mark Delegate
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// When a row is selected, update title accordingly
Person *person = (Person *)[dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
self.title = person.fullname;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 12.

Recipe: Search Tables and Core Data

Core Data stores are designed to work efficiently with NSPredicates. Predicates allow you to create fetch requests that select only those managed objects that match the predicate’s rule or rules. Adding a predicate to a fetch request limits the fetched results to matching objects. Recipe 12-2 takes advantage of the predicates introduced earlier in this chapter to add searching to a table view.

Users may search for entries whose last names match the search string they type. As the text in the search bar at the top of the table changes, the search bar’s delegate receives a searchBar:textDidChange: callback. In turn, that callback method performs a new fetch, using that string as the basis for searching.

The changes to the recipe are as follows:

• The loadView method adds a search controller; the viewDidAppear: method scrolls the search field out of sight.

• The section index expands to include a search icon and the “section for index” method respects that icon by scrolling the search controller frame into view.

• The search bar delegate methods fetch new results whenever the search field contents change. They submit a new Core Data fetch request and use those results to populate the table view.

Together, these few changes create a search field-powered table that responds to user-driven queries. As both Recipes 12-1 and 12-2 show, it takes surprisingly little work to make table views work with Core Data.

Recipe 12-2. Using Fetch Requests with Predicates

Click here to view code image

// Section index titles plus search
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)aTableView
{
if (aTableView == searchController.searchResultsTableView) return nil;
return [[NSArray arrayWithObject:UITableViewIndexSearch]
arrayByAddingObjectsFromArray:
[dataHelper.fetchedResultsController sectionIndexTitles]];
}

// Allow scrolling to search bar
- (NSInteger)tableView:(UITableView *)tableView
sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
if (title == UITableViewIndexSearch)
{
[self.tableView scrollRectToVisible:
searchController.searchBar.frame animated:NO];
return -1;
}
return [dataHelper.fetchedResultsController.sectionIndexTitles
indexOfObject:title];
}

// Return a cell specific to the table being shown
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
[aTableView registerClass:[UITableViewCell class]
forCellReuseIdentifier:@"cell"];
UITableViewCell *cell =
[aTableView dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];
Person *person = [dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
cell.textLabel.text = person.fullname;
return cell;
}
// Handle cancel by fetching all data
- (void)searchBarCancelButtonClicked:(UISearchBar *)aSearchBar
{
aSearchBar.text = @"";
[dataHelper fetchData];
}

// Handle search field update by fetching matching entries
- (void)searchBar:(UISearchBar *)aSearchBar
textDidChange:(NSString *)searchText
{
[dataHelper fetchItemsMatching:aSearchBar.text
forAttribute:@"surname" sortingBy:nil];
}

// Set up search and Core Data
- (void) loadView
{
[super loadView];

// Create a search bar
UISearchBar *searchBar = [[UISearchBar alloc]
initWithFrame:CGRectMake(0.0f, 0.0f, 0.0f, 44.0f)];
searchBar.autocorrectionType = UITextAutocorrectionTypeNo;
searchBar.autocapitalizationType = UITextAutocapitalizationTypeNone;
searchBar.keyboardType = UIKeyboardTypeAlphabet;
searchBar.delegate = self;
self.tableView.tableHeaderView = searchBar;

// Create the search display controller
searchController = [[UISearchDisplayController alloc]
initWithSearchBar:searchBar contentsController:self];
searchController.searchResultsDataSource = self;
searchController.searchResultsDelegate = self;

// Establish Core Data
dataHelper = [[CoreDataHelper alloc] init];
dataHelper.entityName = @"Person";
dataHelper.defaultSortAttribute = @"surname";
[dataHelper setupCoreData];
[dataHelper fetchData];
[self.tableView reloadData];
}

// Hide the search bar
- (void) viewDidAppear:(BOOL)animated
{
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:path
atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 12.

Recipe: Adding Edits to Core Data Table Views

You’ve seen how table views integrate well with static data. Now it’s time to bring that technology to the next level. Recipe 12-3 demonstrates how to add edits to both the table presentation and the core data that’s backing that table.

Much of this recipe should look familiar. Its code is based on the basic edits you read about in the table view chapter. Users can add new rows by tapping + and delete them by swiping or entering edit mode. All the remaining features, including the search table and the section index, remain in place.

In this recipe, the new data is loaded from a collection of fake contacts, courtesy of fakenamegenerator.com. When users tap +, the app loads a random name into the database from its collection.

You should make a number of adaptations to bring table edits into the Core Data world. Topics you should consider when building your table implementation include undo/redo support, user control limits, and using controller delegation for data updates.

Adding Undo/Redo Support

Core Data simplifies table undo/redo support to an astonishing degree. It provides automatic support for these operations with little programming effort. Add this support by assigning an undo manager when you create a Core Data context:

Click here to view code image

_context = [[NSManagedObjectContext alloc] init];
_context.persistentStoreCoordinator = persistentStoreCoordinator;
_context.undoManager = [[NSUndoManager alloc] init];
_context.undoManager.levelsOfUndo = 999;

As with all undo/redo support, your primary controller must become first responder while it is onscreen. The standard suite of first responder methods includes canBecomeFirstResponder (respond YES), viewDidAppear: (the controller view should become responder as soon as it appears), and viewWillDisappear: (the controller view resigns responder as it leaves the screen):

Click here to view code image

- (BOOL)canBecomeFirstResponder
{
return YES;
}

- (void) viewDidAppear:(BOOL)animated
{
[self becomeFirstResponder];

if (dataHelper.numberOfEntities == 0) return;

// Hide the search bar
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];
[self.tableView scrollToRowAtIndexPath:path
atScrollPosition:UITableViewScrollPositionTop animated:NO];
}

- (void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self resignFirstResponder];
}

Notice that this search bar is scrolled offscreen only if the table contains at least one entry. That workaround was not needed in Recipe 12-2. On a table where users have direct control over the contents by adding and removing entries, it is entirely possible that the table is presented without any data at all.

Creating Undo Transactions

Build your Core Data updates into undo transactions by bracketing them into undo groupings. The beginUndoGrouping and endUndoGrouping calls appear before and after context updates. Specify an action name that describes the operation that just took place. This action name is primarily used for shake-to-undo support (for example, “Undo delete?”). It also helps document the action you’re expressing.

The braces used in the following undo-grouping sample are purely stylistic. You do not need to include them in your code. I added them to highlight the transaction-nature that underlies undo groupings:

Click here to view code image

// Delete request
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSManagedObject *object = [dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
NSUndoManager *manager = dataHelper.context.undoManager;
[manager beginUndoGrouping];
[manager setActionName:@"Delete"];
{
[dataHelper.context deleteObject:object];
}
[manager endUndoGrouping];
[dataHelper save];
}

These three calls (begin, end, and setting the action name) ensure that Core Data can reverse its operations. For this minimal effort, your application gains a fully realized undo management system, courtesy of Core Data. Be aware that any undo/redo history will not survive quitting your application. The stack resets each time the app launches.

Rethinking Edits

When working with Core Data-powered tables, this recipe doesn’t let users reorder rows. That’s because its fetch requests sort the data, not users. Recipe 12-3’s tableView:canMoveRowAtIndexPath: method hard-codes its result to NO. Yes, you can work around this by introducing a custom row position attribute. Much of the time you won’t want to. Recipe 12-3 represents a common use-case.

In a similar vein, make sure you coordinate any database edits to your data sources. With Core Data-driven tables, these changes may come from user requests (swiping, pressing +, and so forth) and also from the undo manager. By subscribing to the fetched results controller as its delegate, you’ll know whenever data has updated from undo actions. Use the fetch result delegate callbacks to reload your data whenever data changes occur.

Recipe 12-3. Adapting Table Edits to Core Data

Click here to view code image

// Update items in the navigation bar
- (void) setBarButtonItems
{
// Expire any ongoing operations
if (dataHelper.context.undoManager.isUndoing ||
dataHelper.context.undoManager.isRedoing)
{
[self performSelector:@selector(setBarButtonItems)
withObject:nil afterDelay:0.1f];
return;
}

UIBarButtonItem *undo = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemUndo,
dataHelper.context.undoManager, @selector(undo));
undo.enabled = dataHelper.context.undoManager.canUndo;
UIBarButtonItem *redo = SYSBARBUTTON_TARGET(
UIBarButtonSystemItemRedo,
dataHelper.context.undoManager, @selector(redo));
redo.enabled = dataHelper.context.undoManager.canRedo;
UIBarButtonItem *add = SYSBARBUTTON(
UIBarButtonSystemItemAdd, @selector(addItem));

self.navigationItem.leftBarButtonItems = @[add, undo, redo];
}

// Refetch data
- (void) refresh
{
// If searching, fetch search results, otherwise all data
if (searchController.searchBar.text)
[dataHelper fetchItemsMatching:
searchController.searchBar.text
forAttribute:@"surname" sortingBy:nil];
else
[dataHelper fetchData];
dataHelper.fetchedResultsController.delegate = self;

// Reload tables
[self.tableView reloadData];
[searchController.searchResultsTableView reloadData];

// Update bar button items
[self setBarButtonItems];
}

// Respond to section changes
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
if (type == NSFetchedResultsChangeDelete)
[self.tableView deleteSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationAutomatic];

if (type == NSFetchedResultsChangeInsert)
[self.tableView insertSections:
[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationAutomatic];

sectionHeadersAffected = YES;
}

// Respond to item changes
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
UITableView *tableView = self.tableView;

if (type == NSFetchedResultsChangeInsert)
[tableView insertRowsAtIndexPaths:@[newIndexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];

if (type == NSFetchedResultsChangeDelete)
[tableView deleteRowsAtIndexPaths:@[indexPath]
withRowAnimation:UITableViewRowAnimationAutomatic];
}

// Prepare for updates
- (void)controllerWillChangeContent:
(NSFetchedResultsController *)controller
{
sectionHeadersAffected = NO;
[self.tableView beginUpdates];
}

// Apply updates
- (void)controllerDidChangeContent:
(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];

// Update section headers if needed
if (sectionHeadersAffected)
[self.tableView reloadSections:
[NSIndexSet indexSetWithIndexesInRange:
NSMakeRange(0, self.tableView.numberOfSections)]
withRowAnimation:UITableViewRowAnimationNone];

[self setBarButtonItems];
}

// Only allow editing on the main table
- (BOOL)tableView:(UITableView *)aTableView
canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
if (aTableView == searchController.searchResultsTableView) return NO;
return YES;
}

// No reordering allowed
- (BOOL)tableView:(UITableView *)tableView
canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO;
}

- (void) addItem
{
// Surround the "add" functionality with undo grouping
NSUndoManager *manager = dataHelper.context.undoManager;
[manager beginUndoGrouping];
{
Person *person = (Person *)[dataHelper newObject];
[self setupNewPerson:person];
}
[manager endUndoGrouping];
[manager setActionName:@"Add"];
[dataHelper save];
}

// Handle deletions
- (void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// delete request
if (editingStyle == UITableViewCellEditingStyleDelete)
{
NSManagedObject *object = [dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
NSUndoManager *manager = dataHelper.context.undoManager;
[manager beginUndoGrouping];
{
[dataHelper.context deleteObject:object];
}
[manager endUndoGrouping];
[manager setActionName:@"Delete"];
[dataHelper save];
}
}

// Limit edits
- (BOOL)tableView:(UITableView *)aTableView
canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Editing only on the main table
if (aTableView == searchController.searchResultsTableView) return NO;
return YES;
}

// Toggle editing mode
- (void) setEditing: (BOOL) isEditing animated: (BOOL) animated
{
[super setEditing:isEditing animated:animated];
[self.tableView setEditing:isEditing animated:animated];

NSIndexPath *path = [self.tableView indexPathForSelectedRow];
if (path)
[self.tableView deselectRowAtIndexPath:path animated:YES];

[self setBarButtonItems];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 12.

Recipe: A Core Data-Powered Collection View

It takes work to convert Recipe 12-3 from a table to a collection view, but not an overwhelming amount. Ditch the search view controller, get rid of the index view, update the edits a little, and switch out the controller class from table to collection. Figure 12-3 shows the results. This collection view displays the same data as the table did, offering selectable cells, edits, and undo/redo support.

Figure 12-3. Recipe 12-4 builds a collection view powered by Core Data.

The refactoring story begins with the data model. This recipe adds a new attribute, a binary data item called imageData. The image is built out of each person’s first and last name and saved in binary format. This extra attribute allows the collection view to present each data entry as a reusable image, sized to fit each name.

The data source methods all update from table view to collection view versions. Some need little work. The section count and items-per-section methods switch to their collection view counterparts while their internals essentially stay the same.

Others experience a bigger makeover. The cell-for-path method gets a complete refresh as cells are built to present images rather than populate a title with text. I completely deleted the search view controller and the index view and header callbacks. Finally, Recipe 12-4 adds a custom cell size layout method to match each view size to its embedded image size. That layout is an important component in collection views but not needed in table views.

Edits are affected as well and no longer center around cell animation. Instead of providing deletion support through a table-based “commit edits” method, Recipe 12-4 adds a standalone deleteItem method that corresponds to the addItem method used in Recipe 12-3.

The bar button on the right that used to switch into and out from edit mode on the table now becomes a Delete button, activated whenever any item is selected in the collection view. The remaining items in the navigation bar that provide undo and redo support, and the methods that power them, make the jump from tables to collection views unchanged.

Nothing much else changes, which is what you’d expect with Model-View-Controller development. The Core Data model methods are the same ones used in Recipe 12-3. The UIKit-provided views are stock items. Only the controller part needs or receives updates, simplifying this refactoring exercise.

Recipe 12-4. Core Data Collection View

Click here to view code image

#pragma mark Data Source
// Return the number of sections
- (NSInteger)numberOfSectionsInCollectionView:
(UICollectionView *)collectionView
{
if (dataHelper.numberOfEntities == 0) return 0;
return dataHelper.fetchedResultsController.sections.count;
}

// Return the number of items per section
- (NSInteger)collectionView:(UICollectionView *)collectionView
numberOfItemsInSection:(NSInteger)section
{
id <NSFetchedResultsSectionInfo> sectionInfo =
dataHelper.fetchedResultsController.sections[section];
return sectionInfo.numberOfObjects;
}

// This method builds images into collection view cells
- (UICollectionViewCell *)collectionView:
(UICollectionView *) aCollectionView
cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [self.collectionView
dequeueReusableCellWithReuseIdentifier:@"cell"
forIndexPath:indexPath];
Person *person = [dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
UIImage *image = [UIImage imageWithData:person.imageData];

cell.backgroundColor = [UIColor clearColor];
if (![cell.contentView viewWithTag:IMAGEVIEWTAG])
{
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.tag = IMAGEVIEWTAG;
[cell.contentView addSubview:imageView];
}

UIImageView *imageView =
(UIImageView *)[cell.contentView viewWithTag:IMAGEVIEWTAG];
imageView.frame = CGRectMake(0.0f, 10.0f, image.size.width, image.size.height);
imageView.image = image;

cell.selectedBackgroundView = [[UIView alloc] init];
cell.selectedBackgroundView.backgroundColor = [UIColor redColor];

return cell;
}

// Return the size for layout
- (CGSize)collectionView:(UICollectionView *)collectionView
layout:(UICollectionViewLayout*)collectionViewLayout
sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
Person *person = [dataHelper.fetchedResultsController
objectAtIndexPath:indexPath];
UIImage *image = [UIImage imageWithData:person.imageData];
return CGSizeMake(image.size.width, image.size.height + 20.0f);
}

#pragma mark Delegate methods

- (void)collectionView:(UICollectionView *)aCollectionView
didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
[self setBarButtonItems];
}

#pragma mark Editing and Undo

- (void) setBarButtonItems
{
// Delete requires a selected item
self.navigationItem.rightBarButtonItem.enabled =
(self.collectionView.indexPathsForSelectedItems.count != 0);

// Set up undo/redo items via the context's undo manager
NSMutableArray *undoRedoItems = [NSMutableArray array];
NSUndoManager *manager = dataHelper.context.undoManager;
if (manager.canUndo)
[undoRedoItems addObject:SYSBARBUTTON_TARGET(
UIBarButtonSystemItemUndo, manager, @selector(undo))];
[undoRedoItems addObject:SYSBARBUTTON(
UIBarButtonSystemItemFlexibleSpace, nil)];
if (manager.canRedo)
[undoRedoItems addObject:SYSBARBUTTON_TARGET(
UIBarButtonSystemItemRedo, manager, @selector(redo))];

UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:
CGRectMake(0.0f, 0.0f, 100.0f, 30.0f)];
toolbar.items = undoRedoItems;
self.navigationItem.titleView = toolbar;
}

// Refresh the data, update the view
- (void) refresh
{
[dataHelper fetchData];
dataHelper.fetchedResultsController.delegate = self;
[self.collectionView reloadData];
[self performSelector:@selector(setBarButtonItems)
withObject:nil afterDelay:0.1f];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
// Respond to data change from undo controller
[self refresh];
}

// Add a new item
- (void) addItem
{
NSUndoManager *manager = dataHelper.context.undoManager;
[manager beginUndoGrouping];
{
Person *person = (Person *)[dataHelper newObject];
[self setupNewPerson:person];
}
[manager endUndoGrouping];
[manager setActionName:@"Add"];
[dataHelper save];
[self refresh];
}

// Delete the selected item
- (void) deleteItem
{
if (!self.collectionView.indexPathsForSelectedItems.count) return;

NSIndexPath *indexPath =
self.collectionView.indexPathsForSelectedItems[0];
NSManagedObject *object =
[dataHelper.fetchedResultsController objectAtIndexPath:indexPath];
NSUndoManager *manager = dataHelper.context.undoManager;
[manager beginUndoGrouping];
[manager setActionName:@"Delete"];
{
[dataHelper.context deleteObject:object];
}
[manager endUndoGrouping];
[dataHelper save];
[self refresh];
}

#pragma mark Setup
- (void) viewDidLoad
{
[self.collectionView registerClass:[UICollectionViewCell class]
forCellWithReuseIdentifier:@"cell"];

self.collectionView.backgroundColor = [UIColor lightGrayColor];
self.collectionView.allowsMultipleSelection = NO;
self.collectionView.allowsSelection = YES;

self.navigationItem.leftBarButtonItem =
SYSBARBUTTON(UIBarButtonSystemItemAdd, @selector(addItem));
self.navigationItem.rightBarButtonItem =
BARBUTTON(@"Delete", @selector(deleteItem));
self.navigationItem.rightBarButtonItem.enabled = NO;
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 12.

Summary

When you are working with table views and collection views, Core Data provides the perfect backing technology. It offers easy-to-use model support that easily integrates into UIKit data sources. This chapter offered just a taste of Core Data’s capabilities. These recipes showed you how to design and implement basic Core Data support for managed object models. You read about defining a model and implementing fetch requests. You saw how to add objects, modify them, delete them, and save them. You learned about predicates and undo operations. After reading through this chapter, here are a few final thoughts to take away with you:

• If you’re not using Core Data with tables and collection views, you’re missing out on some of the most elegant ways to populate and control your data.

• When working with Core Data, you’re not limited to scrolling views of content. Use Core Data to save any kind of tabular information. It offers a relational database solution that goes well beyond the demands of most applications.

• Always design for undo/redo support. Even if you don’t think you’ll need it right away, having the work done in advance lets you add features in later. I’m not a big fan of shake-to-undo, but it offers a button-free way to integrate this functionality into otherwise overdesigned interfaces.

• Predicates are one of my favorite SDK features. Spend some time learning how to construct them and use them with all kinds of objects such as arrays and sets, not just with Core Data.

• iCloud provides the perfect match between Core Data and ubiquitous data, extending iOS data to the user’s desktop, to each of his or her devices, and to the cloud as a whole. Look up UIManagedDocument to learn more about iCloud and Core Data integration.

• Core Data’s capabilities go way beyond the basic recipes you’ve seen in this chapter. Check out Tim Isted and Tom Harrington’s Core Data for iOS: Developing Data-Driven Applications for the iPad, iPhone, and iPod touch, available from Pearson Education/InformIT/Addison-Wesley for an in-depth exploration of Core Data and its features.

13. Networking Basics

As an Internet-connected device, the iPhone and its other iOS family members are particularly well suited to retrieving remote data and accessing Web-based services. Apple has lavished the platform with a solid grounding in all kinds of network computing and its supporting technologies. This chapter surveys basic techniques for network computing, offering recipes that simplify day-to-day tasks.

Recipe: Checking Your Network Status

Networked applications need a live connection to communicate with the Internet or other nearby devices. Applications should know whether that connection exists before reaching out to send or retrieve data. Checking the network status lets the application communicate with users and explain why certain functions might be disabled.

Apple has and will reject applications that do not check the network status before providing download options to the user. Apple reviewers are trained to check whether you properly notify the user, especially in the case of network errors. Always verify your network status and alert the user accordingly.

Apple also may reject applications based on “excessive data usage.” If you plan to stream large quantities of data in your application, such as voice or data, you want to test for the current connection type. Provide lower-quality data streams for users on a cell network connection and higher-quality data for users with a Wi-Fi connection. Apple has had little tolerance for applications that place high demands on cell network data. Keep in mind that unlimited data has given way to metered accounts in the United States. You can alienate your users as well as Apple.

iOS tests for the following configuration states: some (that is, any kind of) network connection available, Wi-Fi available, and cell service available. No App Store-safe application programming interfaces (APIs) allow the iPhone to test for Bluetooth connectivity at this time (although you can limit your application to run only on Bluetooth-enabled devices), nor can you check to see whether a user is roaming before offering data access.

The System Configuration framework offers network-checking functions. Among these, SCNetworkReachabilityCreateWithAddress tests whether an IP address is reachable. Recipe 13-1 shows a simple example of this test in action.

This call determines whether your device has outgoing connectivity, which it defines as having both access and a live connection. This method, based on Apple sample code, returns YES when the network is available and NO otherwise. The flags used here indicate both that the network is reachable (kSCNetworkFlagsReachable) and that no further connection is required (kSCNetworkFlagsConnectionRequired). Other flags you may use are as follows:

• kSCNetworkReachabilityFlagsIsWWAN—Tests whether your user is using the carrier’s network or local Wi-Fi. When available, the network can be reached via EDGE, GPRS, or another cell connection. That means you might want to use lightweight versions of your resources (for example, smaller versions of images) because of the connection’s constricted bandwidth.

• kSCNetworkReachabilityFlagsConnectionOnTraffic—Specifies that addresses can be reached with the current network configuration but that a connection must first be established. Any actual traffic will initiate the connection.

• kSCNetworkReachabilityFlagsIsDirect—Tells you whether the network traffic goes through a gateway or arrives directly.

To confirm that connectivity code works, it is best evaluated on a variety of devices. The iPhone and 3G iPad offer the most options. These devices provide both cell and Wi-Fi support, enabling you to confirm that the network remains reachable when using a cellular (wireless wide area network [WWAN]) connection.

Test out this code by toggling Wi-Fi and cell data off and on in the iPhone’s Setting app. Be aware that a slight delay sometimes occurs when checking for network reachability and design your applications accordingly. Let the user know what your code is up to during the check.

SCNetworkReachabilityGetFlags is a synchronous call that can block for a long period of time particularly on Domain Name System (DNS) lookup if there is no connection. Because this can sometimes be enough to get your app booted by the iOS watchdog, use caution with this approach. Indicate whether your application is using the network by setting the networkActivityIndicatorVisible property for the shared application instance. A spinning indicator in the status bar shows that network activity is in progress.

Recipe 13-1. Testing a Network Connection

Click here to view code image

- (void) pingReachabilityInternal
{
if (!reachability)
{
BOOL ignoresAdHocWiFi = NO;
struct sockaddr_in ipAddress;
bzero(&ipAddress, sizeof(ipAddress));
ipAddress.sin_len = sizeof(ipAddress);
ipAddress.sin_family = AF_INET;
ipAddress.sin_addr.s_addr =
htonl(ignoresAdHocWiFi ? INADDR_ANY : IN_LINKLOCALNETNUM);

/* Can also create zero address if needed
struct sockaddr_in zeroAddress;
bzero(&zeroAddress, sizeof(zeroAddress));
zeroAddress.sin_len = sizeof(zeroAddress);
zeroAddress.sin_family = AF_INET; */

reachability = SCNetworkReachabilityCreateWithAddress(
kCFAllocatorDefault, (struct sockaddr *)&ipAddress);
CFRetain(reachability);
}

// Recover reachability flags
BOOL didRetrieveFlags = SCNetworkReachabilityGetFlags(
reachability, &connectionFlags);
if (!didRetrieveFlags)
NSLog(@"Error. Could not recover network reachability flags");
}

- (BOOL) networkAvailable
{
[[UIApplication sharedApplication]
setNetworkActivityIndicatorVisible:YES];
[self pingReachabilityInternal];
BOOL isReachable =
(connectionFlags & kSCNetworkFlagsReachable) != 0;
BOOL needsConnection = ((connectionFlags &
kSCNetworkFlagsConnectionRequired) != 0);
[[UIApplication sharedApplication]
setNetworkActivityIndicatorVisible:NO];
return (isReachable && !needsConnection) ? YES : NO;
}}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 13.

Scanning for Connectivity Changes

Connectivity state may change while an application is running. Checking once at application launch usually isn’t enough for an application that depends on data connections throughout its lifetime. Alert the user when a network connection is lost—or when it can finally be established.

Listing 13-1 addresses this challenge by extending the UIDevice reachability category to monitor network changes. It provides a pair of methods that allow you to schedule and unschedule reachability watchers, observers to notify when the connectivity state changes. It builds a callback that messages a watcher object when that state changes. The monitor is scheduled on the current run loop and runs asynchronously. Upon detecting a change, the callback function triggers.

Listing 13-1’s callback function redirects itself to a custom delegate method, reachabilityChanged, which must be implemented by its watcher. That watcher object can then query for current network state.

The method that schedules the watcher assigns the delegate as its parameter. Here’s a trivial case of how that might be implemented skeletally using Listing 13-1’s implementation. In real-world deployment, you’ll want to update the functionality presented in your GUI to match the availability (or lack thereof) of network-only features. Inform your user when connectivity changes and update your interface to mirror the current state. You might want to disable buttons or menu items that depend on network access when that access disappears. Providing an alert of some kind lets the user know why the GUI has updated.

Be prepared for multiple callbacks. Your application will generally receive one callback at a time for each kind of state change (that is, when the cellular data connection is established or released) or when Wi-Fi is established or lost. Your user’s connectivity settings (especially remembering and logging in to known Wi-Fi networks) will affect the kind and number of callbacks you may have to handle.

Listing 13-1. Monitoring Connectivity Changes

Click here to view code image

@protocol ReachabilityWatcher <NSObject>
- (void) reachabilityChanged;
@end

// For each callback, ping the watcher
static void ReachabilityCallback(
SCNetworkReachabilityRef target,
SCNetworkConnectionFlags flags, void* info)
{
@autoreleasepool {
id watcher = (__bridge id) info;
if ([watcher respondsToSelector: @selector(reachabilityChanged)])
[watcher performSelector: @selector(reachabilityChanged)];
}
}

// Schedule watcher into the run loop
- (BOOL) scheduleReachabilityWatcher: (id <ReachabilityWatcher>) watcher
{
[self pingReachabilityInternal];

SCNetworkReachabilityContext context =
{0, (__bridge void *)watcher, NULL, NULL, NULL};
if(SCNetworkReachabilitySetCallback(reachability,
ReachabilityCallback, &context))
{
if(!SCNetworkReachabilityScheduleWithRunLoop(
reachability, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes))
{
NSLog(@"Error: Could not schedule reachability");
SCNetworkReachabilitySetCallback(reachability, NULL, NULL);
return NO;
}
}
else
{
NSLog(@"Error: Could not set reachability callback");
return NO;
}
return YES;
}

// Remove the watcher
- (void) unscheduleReachabilityWatcher
{
SCNetworkReachabilitySetCallback(reachability, NULL, NULL);
if (SCNetworkReachabilityUnscheduleFromRunLoop(
reachability, CFRunLoopGetCurrent(),
kCFRunLoopCommonModes))
NSLog(@"Success. Unscheduled reachability");
else
NSLog(@"Error: Could not unschedule reachability");

CFRelease(reachability);
reachability = nil;
}

Recipe: Synchronous Downloads

Synchronous downloads allow you to request data from the Internet, wait until that data is received, and then move on to the next step in your application. The following snippet is both synchronous and blocking. You will not return from this method until all the data is received. If the connection hangs, so will your app. The iOS system watchdog will summarily execute your app if it blocks the main thread for too long—it won’t just hang forever:

Click here to view code image

- (UIImage *) imageFromURLString: (NSString *) urlstring
{
// This is a blocking call
return [UIImage imageWithData:[NSData
dataWithContentsOfURL:[NSURL URLWithString:urlstring]]];
}

The NSURLConnection class provides a more general download approach than class-specific URL initialization. It provides both synchronous and asynchronous downloads, the latter provided by a series of delegate callbacks. Recipe 13-2 focuses on the simpler, synchronous approach. It begins by creating an NSMutableURLRequest with the URL of choice. That request is sent synchronously using the NSURLConnection class:

Click here to view code image

NSMutableURLRequest *theRequest =
[NSMutableURLRequest requestWithURL:url];
NSData* result = [NSURLConnection sendSynchronousRequest:
theRequest returningResponse:&response error:&error];

This call blocks until the request fails (returning nil, and an error is produced) or the data finishes downloading. Recipe 13-2 performs the synchronous request using an operation queue. This allows the main thread to continue execution without blocking. When the download finishes, it performs a callback on the main thread to pick up execution there.

This example allows testing with three predefined URLs. There’s one that downloads a short (3MB) movie, another using a larger (35MB) movie, and a final fake URL to test errors. The movies are sourced from the Internet Archive (http://archive.org), which provides a wealth of public domain data.

Some Internet providers produce a valid Web page, even when given a completely bogus URL. The data returned in the response parameter helps you determine when this happens. This parameter points to an NSURLResponse object. It stores information about the data returned by the URL connection. These parameters include expected content length and a suggested filename. Should the expected content length be less than zero, that’s a good clue that the provider has returned data that does not match up to your expected request:

NSLog(@"Response expects %d bytes",
response.expectedContentLength);

As you can see in Recipe 13-2, integrating large downloads into the main application GUI is messy, even with a secondary thread. It also provides no interdownload feedback. Recipe 13-3 addresses both these issues by using asynchronous downloads with delegate callbacks.

Recipe 13-2. Synchronous Downloads

Click here to view code image

// Large Movie (35MB)
#define LARGE_MOVIE @"http://www.archive.org/download/\
BettyBoopCartoons/Betty_Boop_More_Pep_1936_512kb.mp4"

// Small movie (3MB)
#define SMALL_MOVIE @"http://www.archive.org/download/\
Drive-inSaveFreeTv/Drive-in--SaveFreeTv_512kb.mp4"

// Fake address
#define FAKE_MOVIE \
@"http://www.thisisnotavalidurlforthisexample.com"

// Current URL to test
#define MOVIE_URL   [NSURL URLWithString:LARGE_MOVIE]

// Location to store downloaded item
#define DEST_PATH    [NSHomeDirectory() \
stringByAppendingString:@"/Documents/Movie.mp4"]
#define DEST_URL    [NSURL fileURLWithPath:DEST_PATH]

@implementation TestBedViewController
{
BOOL success;
}

- (void) playMovie
{
// Create a player
MPMoviePlayerViewController *player =
[[MPMoviePlayerViewController alloc]
initWithContentURL:DEST_URL];
player.moviePlayer.allowsAirPlay = YES;
[player.moviePlayer prepareToPlay];

// Listen for finish state
[[NSNotificationCenter defaultCenter] addObserverForName:
MPMoviePlayerPlaybackDidFinishNotification
object:player.moviePlayer queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification *notification){
[[NSNotificationCenter defaultCenter] removeObserver:self];
}];

[self presentMoviePlayerViewControllerAnimated:player];
}

// Handle the end of the download
- (void) downloadFinished
{
// Restore GUI
self.navigationItem.rightBarButtonItem.enabled = YES;
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

if (!success)
{
NSLog(@"Failed download");
return;
}

// Play the movie
[self playMovie];
}

// Perform a synchronous download
- (void) getData: (NSURL *) url
{
// Determine the duration of the request
NSDate *startDate = [NSDate date];

NSURLResponse *response;
NSError *error;
success = NO;

// Send the request
NSMutableURLRequest *theRequest =
[NSMutableURLRequest requestWithURL:url];
NSData* result = [NSURLConnection
sendSynchronousRequest:theRequest
returningResponse:&response error:&error];

if (!result)
{
NSLog(@"Download failed: %@", error.localizedFailureReason);
return;
}

if ((response.expectedContentLength ==
NSURLResponseUnknownLength) ||
(response.expectedContentLength < 0))
{
NSLog(@"Unexpected content length");
return;
}

if (![response.suggestedFilename
isEqualToString:url.path.lastPathComponent])
{
NSLog(@"Name mismatch. Probably carrier error page");
return;
}

if (response.expectedContentLength != result.length)
{
NSLog(@"Got %d bytes, expected %lld",
result.length, response.expectedContentLength);
return;
}

success = YES;
[result writeToFile:DEST_PATH atomically:YES];
NSLog(@"Download %d bytes", result.length);
NSLog(@"Suggested file name: %@", response.suggestedFilename);
NSLog(@"Elapsed time: %0.2f seconds.",
[[NSDate date] timeIntervalSinceDate:startDate]);
}

// Initiate the download
- (void) go
{
self.navigationItem.rightBarButtonItem.enabled = NO;

// Remove any existing data
if ([[NSFileManager defaultManager]
fileExistsAtPath:DEST_PATH])
{
NSError *error;
if (![[NSFileManager defaultManager]
removeItemAtPath:DEST_PATH error:&error])
NSLog(@"Error removing existing data: %@",
error.localizedFailureReason);
}

// Fetch the data
[[UIApplication sharedApplication]
setNetworkActivityIndicatorVisible:YES];
[[[NSOperationQueue alloc] init] addOperationWithBlock:
^{
[self getData:MOVIE_URL];
[[NSOperationQueue mainQueue] addOperationWithBlock:^
{
// Finish up on main thread
[self downloadFinished];
}];
}];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 13.

Recipe: Asynchronous Downloads

Asynchronous downloads allow your application to download data in the background without explicit operation queues. They keep your code from blocking while waiting for a download to finish. Recipe 13-3 uses NSURLConnections asynchronously. It implements a helper class called DownloadHelper that hides the details involved in downloading data. It works in the following fashion. Instead of sending a synchronous request, it initializes the connection and assigns a delegate:

Click here to view code image

NSURLConnection *theConnection = [[NSURLConnection alloc]
initWithRequest:theRequest delegate:delegate];

A download may finish either by retrieving all the requested data or failing with an error. Delegate methods help you track the download life cycle. You receive updates when new data is available, when the data has finished downloading, or if the download fails. To support these callbacks, the DownloadHelper class defines its variables and properties as follows:

• Two strong string properties hold the requested resource (urlString) and where its data is to be saved (targetPath). The urlString initializes the URL request that begins the download process (requestWithURL:).

• This recipe creates an outputStream that writes the incoming data to the targetPath as it arrives.

• A weak delegate property points to the client object. The delegate, which implements the custom DownloadHelperDelegate protocol, updates with optional callbacks as the download progresses. This external delegate is distinct from the internal delegate used with the NSURLConnection object. External callbacks occur when the download succeeds internally (connection:didFinishLoading:), fails (connection:didFailWithError:), when the filename and expected size become known (connection:didReceive-Response:), and as each chunk of data arrives (connection:didReceiveData:).

• The data consumer can update a progress view to show the user how far a download has progressed by querying the read-only bytesRead and expectedLength properties. Dividing the former by the latter yields the percent the download has progressed. The Boolean isDownloading property indicates whether the helper object is actively downloading.

• The urlconnection property stores the current NSURLConnection object. It is kept on hand to allow the DownloadHelper class’s cancel method to halt an ongoing download:

Click here to view code image

@protocol DownloadHelperDelegate <NSObject>
@optional
- (void) downloadFinished;
- (void) downloadReceivedData;
- (void) dataDownloadFailed: (NSString *) reason;
@end

@interface DownloadHelper : NSObject
@property (strong) NSString *urlString;
@property (strong) NSString *targetPath;
@property (weak) id <DownloadHelperDelegate> delegate;

@property (readonly) BOOL isDownloading;
@property (readonly) int bytesRead;
@property (readonly) int expectedLength;

+ (id) download:(NSString *) aURLString
withTargetPath: (NSString *) aPath
withDelegate: (id <DownloadHelperDelegate>) aDelegate;
- (void) cancel;
@end

The client starts the download by calling the class download:withTargetPath:withDelegate: convenience method. This starts a new download and returns the new DownloadHelper object, which should be retained by the client until the download finishes or the client specifically asks it to cancel.

Note

Recipe 13-3 assumes that you are assured an expected content length from the data provider. When the server side returns a response using chunked data (that is, Transfer-Encoding:chunked), the content length is not specified in the response. Recipe 13-3 does not work with chunked data because it tests for content length and fails if the expected length is unknown (NSURLResponseUnknownLength).

Recipe 13-3. Download Helper

Click here to view code image

#define SAFE_PERFORM_WITH_ARG(THE_OBJECT, THE_SELECTOR, THE_ARG) \
(([THE_OBJECT respondsToSelector:THE_SELECTOR]) ? \
[THE_OBJECT performSelector:THE_SELECTOR withObject:THE_ARG] : nil)

@implementation DownloadHelper
{
NSOutputStream *outputStream;
NSURLConnection *urlconnection;
}

- (void) start
{
_isDownloading = NO;
if (!_urlString)
{
NSLog(@"URL string required but not set");
return;
}

// Build a URL
NSURL *url = [NSURL URLWithString:_urlString];
if (!url)
{
NSString *reason = [NSString stringWithFormat:
@"Could not create URL from string %@", _urlString];
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), reason);
return;
}

// Build the request
NSMutableURLRequest *theRequest =
[NSMutableURLRequest requestWithURL:url];
if (!theRequest)
{
NSString *reason = [NSString stringWithFormat:
@"Could not create URL request from string %@", _urlString];
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), reason);
return;
}

// Create a new connection
urlconnection = [[NSURLConnection alloc]
initWithRequest:theRequest delegate:self];
if (!urlconnection)
{
NSString *reason = [NSString stringWithFormat:
@"URL connection failed for string %@", _urlString];
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), reason);
return;
}

// Establish an output stream
outputStream = [[NSOutputStream alloc]
initToFileAtPath:_targetPath append:YES];
if (!outputStream)
{
NSString *reason = [NSString stringWithFormat:
@"Could not create output stream at path %@", _targetPath];
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), reason);
return;
}
[outputStream open];

_isDownloading = YES;
_bytesRead = 0;

// Schedule the download
NSLog(@"Beginning download");
[urlconnection scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
}

// Clean up the helper
- (void) cleanup
{
_isDownloading = NO;
if (urlconnection)
{ [urlconnection cancel]; urlconnection = nil; }

if (outputStream)
{ [outputStream close]; outputStream = nil; }

self.urlString = nil;
self.targetPath = nil;
}

// Handle cancel request
- (void) cancel
{
[self cleanup];
}

- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)aResponse
{
// Check for bad connection
_expectedLength = [aResponse expectedContentLength];
if (_expectedLength == NSURLResponseUnknownLength)
{
NSString *reason = [NSString stringWithFormat:
@"Invalid URL [%@]", _urlString];
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), reason);
[connection cancel];
[self cleanup];
return;
}
}

// Handle the new data
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)theData
{
// Write out the data
_bytesRead += theData.length;
NSUInteger bytesLeft = theData.length;
NSUInteger bytesWritten = 0;
do {
bytesWritten =
[outputStream write:theData.bytes maxLength:bytesLeft];
if (-1 == bytesWritten) break;
bytesLeft -= bytesWritten;
} while (bytesLeft > 0);
if (bytesLeft) {
NSLog(@"stream error: %@", [outputStream streamError]);
}

// After writing, update the delegate
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(downloadReceivedData), nil);
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// finished downloading the data, cleaning up
[outputStream close];
[urlconnection unscheduleFromRunLoop:
[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[self cleanup];

SAFE_PERFORM_WITH_ARG(_delegate,
@selector(downloadFinished), nil);
}

- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
_isDownloading = NO;
NSLog(@"Error: Failed connection, %@", error.localizedFailureReason);
SAFE_PERFORM_WITH_ARG(_delegate,
@selector(dataDownloadFailed:), @"Failed Connection");
[self cleanup];
}

+ (id) download:(NSString *) aURLString
withTargetPath: (NSString *) aPath
withDelegate: (id <DownloadHelperDelegate>) aDelegate
{
if (!aURLString)
{
NSLog(@"Error. No URL string");
return nil;
}

if (!aPath)
{
NSLog(@"Error: No target path");
return nil;
}

DownloadHelper *helper = [[self alloc] init];
helper.urlString = aURLString;
helper.targetPath = aPath;
helper.delegate = aDelegate;
[helper start];

return helper;
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 13.

One-Call No-Feedback Asynchronous Downloads

Introduced in iOS 5, an NSURLConnection class method offers another way to perform asynchronous downloads, executing a completion block upon completion. This enables you to use asynchronous downloads with a single call instead of implementing several delegate methods.

The downside is that you lose any state-specific updates. The following snippet manually animates an activity indicator, which offers no sense of how far the download has progressed. This trade-off means that this approach is best used with operations that are either very short or not triggered directly by the user:

Click here to view code image

// Items to perform on main thread
- (void) readyToLaunch
{
self.navigationItem.titleView = nil;
self.navigationItem.rightBarButtonItem.enabled = YES;
[self playMovie];
}

//
- (void) go
{
// Disable the "go" button
self.navigationItem.rightBarButtonItem.enabled = NO;

// Show an activity indicator during the download
UIActivityIndicatorView *aiv = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[aiv startAnimating];
self.navigationItem.titleView = aiv;

// Remove any existing data
if ([[NSFileManager defaultManager] fileExistsAtPath:DEST_PATH])
{
NSError *error;
if (![[NSFileManager defaultManager] removeItemAtPath:DEST_PATH error:&error])
NSLog(@"Error removing existing data: %@", error.localizedFailureReason);
}

// Perform the download on a new queue
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL
URLWithString:MOVIE_PATH]];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:
^(NSURLResponse *response, NSData *data, NSError *error){
if (!data)
{
NSLog(@"Error downloading data: %@", error.localizedFailureReason);
return;
}

// Store the data and perform the finish on the main thread
[data writeToFile:DEST_PATH atomically:YES];
[self performSelectorOnMainThread:@selector(readyToLaunch)
withObject:nil waitUntilDone:NO];
}];
}

Recipe: Using JSON Serialization

The NSJSONSerialization class (introduced in iOS 5) proves tremendously handy when you’re working with Web services. All you need is a valid JSON container (namely an array or dictionary) whose components are also valid JSON objects, including strings, numbers, arrays, dictionaries, and NSNull. Test an object’s validity with isValidJSONObject, which returns YES if the object can be safely converted to JSON format:

Click here to view code image

// Build a basic JSON object
NSArray *array = @[@"Val1", @"Val2", @"Val3"];
NSDictionary *dict = @{@"Key 1":array,
@"Key 2":array, @"Key 3":array};

// Convert it to JSON
if ([NSJSONSerialization isValidJSONObject:dict])
{
NSData *data = [NSJSONSerialization
dataWithJSONObject:dict options:0 error:nil];
NSString *result = [[NSString alloc]
initWithData:data encoding:NSUTF8StringEncoding];
NSLog(@"Result: %@", result);
}

The code from this method produces the following JSON. Notice that dictionary output is not guaranteed to be in alphabetic order:

Click here to view code image

Result: {"Key 2":["Val1","Val2","Val3"],"Key 3":
["Val1","Val2","Val3"],"Key 1":["Val1","Val2","Val3"]}

Moving from JSON to a conforming object is just as easy. Recipe 13-4 uses JSONObjectWithData:options:error: to convert NSData representing a JSON object into an Objective-C representation. This recipe downloads a JSON search from Twitter, retrieves the results array from the returned dictionary, and uses it to power a standard table view.

Recipe 13-4. JSON Data

Click here to view code image

// Return a cell for the index path
- (UITableViewCell *)tableView:(UITableView *)aTableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView
dequeueReusableCellWithIdentifier:@"cell"
forIndexPath:indexPath];

// Set the cell style
cell = [cell initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:@"cell"];
cell.textLabel.numberOfLines = 3;
cell.textLabel.lineBreakMode = NSLineBreakByWordWrapping;

// Set the text based on the Twitter JSON data structure
cell.textLabel.text = [items objectAtIndex:indexPath.row][@"text"];
cell.detailTextLabel.text =
[items objectAtIndex:indexPath.row][@"from_user_name"];

return cell;
}

- (void) downloadFinished
{
[self.refreshControl endRefreshing];

// Retrieve JSON data and convert it to an array of entries
NSData *jsonData = [NSData dataWithContentsOfFile:DEST_PATH];
NSDictionary *json = [NSJSONSerialization
JSONObjectWithData:jsonData options:0 error:nil];
items = json[@"results"];
[self.tableView reloadData];
}

#define TWITTERSEARCH @"http://search.twitter.com/search.json?\
q=ericasadun%20OR%20sadun&rpp=25&include_entities=true\
&result_type=mixed"

// Download the Twitter JSON
- (void) reloadData
{
self.title = nil;
[self.refreshControl beginRefreshing];
helper = [DownloadHelper download:TWITTERSEARCH
withTargetPath:DEST_PATH withDelegate:self];
}

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 13.

Recipe: Converting XML into Trees
iOS’s NSXMLParser class scans through XML, creating callbacks as new elements are processed and finished (that is, using the typical logic of a SAX parser). The class is terrific for when you’re downloading simple data feeds and want to scrape just a bit or two of relevant information. It might not be so great when you’re doing production-type work that relies on error checking, status information, and back-and-forth handshaking.

Recipe 13-5 retrieves the same search data from Twitter as Recipe 13-4, but in XML format. It requests the “atom” rather than the “json” feed and uses an XML parser to populate its table:

Click here to view code image

- (void) reloadData
{
[self.refreshControl beginRefreshing];
XMLParser *parser = [[XMLParser alloc] init];
root = [parser parseXMLFromURL:
[NSURL URLWithString:TWITTERSEARCH]];
items = [root nodesForKey:@"entry"];
[self.refreshControl endRefreshing];
[self.tableView reloadData];
}

Trees

Tree data structures offer an excellent way to represent XML data. They allow you to create search paths through the data so that you can find just the data you’re looking for. You can retrieve all “entries,” search for a success value, and so forth. Trees convert text-based XML back into a multidimensional structure.

To bridge the gap between NSXMLParser and tree-based parse results, you can use an NSXMLParser-based helper class to return more standard tree-based data. This requires a simple tree node like the kind shown here. This node uses double linking to access its parent and its children, allowing two-way traversal in a tree. Only parent-to-child values are retained, allowing the tree to deallocate without being explicitly torn down:

Click here to view code image

@interface TreeNode : NSObject
@property (nonatomic, assign)     TreeNode        *parent;
@property (nonatomic, strong)     NSMutableArray  *children;
@property (nonatomic, strong)     NSString        *key;
@property (nonatomic, strong)     NSString        *leafvalue;
@end

If you are building for iOS 5 and later, you’ll probably want to change the parent from assign to weak.

Building a Parse Tree

Recipe 13-5 introduces the XMLParser class. Its job is to build a parse tree as the NSXMLParser class works its way through the XML source. The three standard NSXML routines (start element, finish element, and found characters) read the XML stream and perform a recursive depth-first descent through the tree.

The class adds new nodes when reaching new elements (parser:didStartElement: qualifiedName:attributes:) and adds leaf values when encountering text (parser:foundCharacters:). Because XML allows siblings at the same tree depth, this code uses a stack to keep track of the current path to the tree root. Siblings always pop back to the same parent in parser:didEndElement:, so they are added at the proper level.

After finishing the XML scan, the parseXMLFile: method returns the root node.

Recipe 13-5. The XMLParser Helper Class

Click here to view code image

@implementation XMLParser
// Parser returns the tree root. Go down
// one node to the real results
- (TreeNode *) parse: (NSXMLParser *) parser
{
stack = [NSMutableArray array];
TreeNode *root = [TreeNode treeNode];
[stack addObject:root];

[parser setDelegate:self];
[parser parse];

// Pop down to real root
TreeNode *realroot = [[root children] lastObject];

// Remove any connections
root.children = nil;
root.leafvalue = nil;
root.key = nil;
realroot.parent = nil;

// Return the true root
return realroot;
}

- (TreeNode *)parseXMLFromURL: (NSURL *) url
{
TreeNode *results = nil;
@autoreleasepool {
NSXMLParser *parser =
[[NSXMLParser alloc] initWithContentsOfURL:url];
results = [self parse:parser];
}
return results;
}

- (TreeNode *)parseXMLFromData: (NSData *) data
{
TreeNode *results = nil;
@autoreleasepool {
NSXMLParser *parser =
[[NSXMLParser alloc] initWithData:data];
results = [self parse:parser];
}
return results;
}

// Descend to a new element
- (void)parser:(NSXMLParser *)parser
didStartElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
attributes:(NSDictionary *)attributeDict
{
if (qName) elementName = qName;

TreeNode *leaf = [TreeNode treeNode];
leaf.parent = [stack lastObject];
[(NSMutableArray *)[[stack lastObject] children] addObject:leaf];

leaf.key = [NSString stringWithString:elementName];
leaf.leafvalue = nil;
leaf.children = [NSMutableArray array];
[stack addObject:leaf];
}

// Pop after finishing element
- (void)parser:(NSXMLParser *)parser
didEndElement:(NSString *)elementName
namespaceURI:(NSString *)namespaceURI
qualifiedName:(NSString *)qName
{
[stack removeLastObject];
}

// Reached a leaf
- (void)parser:(NSXMLParser *)parser
foundCharacters:(NSString *)string
{
if (![[stack lastObject] leafvalue])
{
[[stack lastObject]
setLeafvalue:[NSString stringWithString:string]];
return;
}
[[stack lastObject] setLeafvalue:
[NSString stringWithFormat:@"%@%
[[stack lastObject] leafvalue], string]];
}
@end

Get This Recipe’s Code

To find this recipe’s full sample project, point your browser to https://github.com/erica/iOS-6-Cookbook and go to the folder for Chapter 13.

Summary

This chapter introduced basic network-supporting technologies. You saw how to check for network connectivity, download data, and convert to and from JSON. Here are a few thoughts to take away with you from this chapter:

• Most of Apple’s networking support is provided through very low-level C-based routines. If you can find a friendly Objective-C wrapper to simplify your programming work, consider using it. The only drawback occurs when you specifically need tight networking control at the most basic level of your application, which is rare. There are superb resources out there. Just Google for them.

• The most important lesson about connecting from a device to the network is this: It can fail. Design your apps accordingly. Check for network connectivity, test for aborted downloads, assume data may arrive corrupted. Everything else follows from the basic fact that you cannot rely on data to arrive when you want, how you expect it to, and as you requested.

• When working with networking, always think “threaded.” Blocks and queues are your new best friends when it comes to creating positive user experiences in networked applications.

• This chapter provided a taste of iOS Networking tools. The Advanced iOS 6 Developer’s Cookbook offers more in-depth coverage on this topic.

Appendix. Objective-C Literals

Think about how often you type cookie-cutter templates like [NSNumber numberWithInteger:5] to produce number objects in your code. Perhaps you’ve defined macros to simplify your coding. Objective-C literals introduce features that transform awkward constructs like NSNumber and NSArray creation instances into easy-to-read parsimonious expressions.

Speaking as someone who has long created/used macro definitions for the NSNumber declarations, I love the way these literals provide more readable, succinct code. They save an enormous amount of typing and provide a natural, coherent presentation.

Now, instead of establishing endless series of those declarations, you can use a simple literal like @5. This number literal is just like the strings literals you’ve used for years. With strings, the at sign is followed by a string constant (for example, @"hello"); with numbers, an at sign followed by a number value. Similar literals simplify the creation and indexing of NSDictionarys and NSArrays.

This new advance squeezes together previously wordy constructs to create simpler, more succinct representations.

Numbers

Through the magic of its LLVM Clang compiler, Xcode’s new number literals allow you to wrap scalar values like integers and floating-point numbers into object containers. Just add an @ prefix to a scalar. For example, you can transform 3.1415926535 to a conforming NSNumber object as follows:

NSNumber *eDouble = @2.7182818;

This number literal is functionally equivalent to the following.

NSNumber *eDouble = [NSNumber numberWithDouble: 2.7182818];

The difference is that the compiler takes care of the heavy lifting for you. You don’t have to use a class call, and you don’t have to write out a full method, brackets and all. Instead, you prefix the number with @ and let Clang do the rest of the work.

Standard suffixes allow you to specify whether a number is a float (F), long (L), longlong (LL), or unsigned (U). Here are some examples of how you would do that. Notice how simple each declaration is, without having to use numerous specialized method calls:

Click here to view code image

NSNumber *two = @2;                 // [NSNumber numberWithInt:2];
NSNumber *twoUnsigned = @2U;        // [NSNumber numberWithUnsignedInt:2U];
NSNumber *twoLong = @2L;            // [NSNumber numberWithLong:2L];
NSNumber *twoLongLong = @2LL;       // [NSNumber numberWithLongLong:2LL];
NSNumber *eDouble = @2.7182818;     // [NSNumber numberWithDouble: 2.7182818];
NSNumber *eFloat = @2.7182818F;     // [NSNumber numberWithFloat: 2.7182818F];

Unfortunately you cannot wrap long double numbers according to the Clang specification. Be aware that the follow statement will cause the compiler to complain. (Apple’s runtime doesn’t support long doubles, either.)

NSNumber *eLongDouble = @2.7182818L // Will not compile

The Boolean constants @YES and @NO produce number objects equivalent to [NSNumber numberWithBool:YES] and [NSNumber numberWithBool:NO].

Finally, do note that @-5 works in Xcode 4.5 and later. You don’t have to enclose the value in parentheses.

Boxing

In early 2012, Xcode 4.4 supported only literal scalar constants after the @. If you wanted to interpret a value and then convert it to a number object, you had to use the traditional method call:

NSNumber *two = [NSNumber numberWithInt:(1+1)];

Xcode 4.5 introduced boxed expression support, avoiding this awkward approach. Boxed expressions are values that are interpreted and then converted to number objects. A boxed expression is enclosed in parentheses, telling the compiler to evaluate and then convert to an object. For example:

NSNumber *two = @(1+1);
and
int foo = ...; // some value
NSNumber *another = @(foo);

Enums

When working with boxing, you need to think of other considerations as well. Take enums, for example. Although you would think you should be able to define an enum and then use it directly, allowing user-defined sequences that start with an @ and continue with text could cause issues. Observe the following poorly chosen enum:

enum {interface, implementation, protocol};

You might imagine you could create an NSNumber with the value 2 by defining the following:

NSNumber *which = @protocol;

That would, quite obviously, be bad. Boxing prevents any conflict with current and future @-delimited literals:

NSNumber *which = @(protocol); // [NSNumber numberWithInt:2];

Boxed expressions are not limited to numbers. They work for strings as well. The following assignment evaluates the results of strstr() and forms an NSString from the results (that is, @"World!").

NSString *results = @(strstr("Hello World!", "W"));

Container Literals

Container literals add another great language feature to the LLVM Clang compiler. Until now, you’ve had to create dictionaries and arrays along the following lines. This snippet creates a three-item array and a three-key dictionary:

Click here to view code image

NSArray *array = [NSArray arrayWithObjects: @"one", @"two", @"three", nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
@"value 1", @"key 1",
@"value 2", @"key 2",
@"value 3", @"key 3",
nil];

These forms are wordy and require a nil terminator. That’s not always a bad thing, but certainly a thing that’s easy to forget and one that’s bitten nearly every developer at some point along the line. What’s more, the dictionary declaration requires value followed by key. That’s the opposite of how most people conceptualize and express dictionary entries, even though the method name indicates the proper order.

Container literals address both concerns by introducing a new, simpler syntax. These examples declare an array and a dictionary with the same contents as the manual example you saw detailed earlier:

NSArray *array = @[@"one", @"two", @"three"];
NSDictionary *dict = @{
@"key 1":@"value 1",
@"key 2":@"value 2",
@"key 3":@"value 3"
};

Array literals consist of square brackets with a comma-delimited list of items. Dictionaries are formed with a curly braced list, where comma-delimited key-value pairs are associated by colons. In neither case do you need to add a nil terminator. Notice that the key-value ordering has switched to what I consider a far more sensible key-then-value definition versus the older object-then-key layout.

When evaluated, these expressions produce the same results as the previous two assignments declared with the traditional approach. The standard container rules still apply:

• Don’t add keys or values that evaluate to nil.

• Make sure that each item is typed as an object pointer.

• Conform to <NSCopying>.

Subscripting

Clang introduces container access using standard subscripting, via square brackets. In other words, you can now access an NSArray just like you would a C array. You can do the same with an NSDictionary, although indexed by key rather than number. Here are a couple of examples of doing so, using the array and dictionary declared earlier:

Click here to view code image

NSLog(@"%@", array[1]); // @"two"
NSLog(@"%@", dictionary[@"key 2"]); // @"value 2"

But you’re not just limited to value look-ups. Using this new syntax, you can also perform assignments for mutable instances. Here’s how you might use the new subscripting features in simple assignments:

Click here to view code image
mutableArray[0] = @"first!";
mutableDictionary[@"some key"] = @"new value";

You still have to watch out for the index. Reading and writing an index outside the array range raises an exception.

Best of all, you can extend this subscripted behavior to custom classes by implementing support for a few core methods. So, if you want an element of a custom class to offer indexed access, you can provide that support by implementing one or more of the following methods:

• -(id) objectAtIndexedSubscript: anIndex

• -(void) setObject: newValue atIndexedSubscript: anIndex

• -(id) objectForKeyedSubscript: aKey

• -(void) setObject: newValue forKeyedSubscript: aKey

You choose whether you want access by order (array-style index) or keyword (dictionary-style key) and whether that access can update values (mutable style). Simply implement the methods you want to support and let the compiler handle the rest for you.

Feature Tests

As a final note, be aware that you can create feature-dependent coding. Just use Clang’s __has_feature test to see whether literals are available in the current compiler. Feature tests include array literals (objc_array_literals), dictionary literals (objc_dictionary_literals), object subscripting (objc_subscripting), numeric literals (objc_bool), and boxed expressions (objc_boxed_expressions):

#if __has_feature(objc_array_literals)
// ...
#else
// ...
#endif

Index

Symbols

, (commas), 183

- (hyphens), 180

@ (at sign), 184

| (pipe) character, 179

~ (tilde), 382

¨ (umlaut), 382

A
above method, 135
accessibility, 341
broadcasting, 348
enabling, 343
hints, 346

IB, 342
index paths, 451
labels, 345-346
overview of, 341-342
simulators, testing, 347
testing, 349-350
traits, 344-345
updating, 347

Accessibility Inspector, 347
accessibilityFrame property, 341-342
accessibilityHint property, 346
accessibilityLabel property, 342, 345
accessibilityTraits property, 342, 344
accessibilityValue property, 342
accessories, disclosure, 365-367
accessory views, 364. See also views
customizing, dismissing text views, 213-216
text, modifying around, 220-222
accumulating user tracings, 26
actions
action sheets, 101
alerts, 114-117
displaying text in, 117
objects, 103
buttons, connecting, 55
interaction, adding, 44
ongoing, displaying, 52
rows, adding, 391-394

UIProgressView class, 107
activities
customizing, adding, 333
data types, 335-336
services, items, 335-336

ActivityAlert class, 107
activityImage method, 333
activityTitle method, 332
activityType method, 332
activityViewController method, 333
activity view controllers, 328-336

Add Attribute button, 443
addConstraints: method, 186
adding
accessories, 216
action rows, 391-394
animation buttons, 58-59
attachments, 321

Auto-Hide sliders, 285
backsplashes, 16
buttons, 102, 104, 215, 254-255
cells, 370
compound predicates, 183
constraints, 186, 205
custom activities, 333
custom input views to nontext views, 230-233
data, 445-448
delay, 13
direct manipulation interfaces, 5-7
editing features to tables, 367
edits to Core Data table views, 457-463
efficiency, 63
entities, 443
flexible spaces between views, 181
frameworks, 301-302
gestures
layouts, 429-430
recognizers, 13
images, 301, 415
input clicks, 231
interactive actions, 44
menus, views, 44-46
modal controllers, 259

Multi-Touch interaction, 25
nametag properties, 137
pages, 94
pan gesture recognizers, 7-9
persistence, 234
pull-to-refresh option, 388-391
services, 331-332
state to buttons, 59
subviews, 134-135
tags, 136
traces, 26
transforms, scaling, 151
undo support, 458
universal support for split views, 267
views
controllers, 287
types to labels, 345

AFC (Apple File Connection), 301
affine matrices, 151

Ahmad, Bilal Sayed, 391

AIFF files, 124

AirPlay, enabling, 314
albums, images, 299. See also images

Saved Photos, 299
saving to, 309
alerts, 101
action sheets, 114-117
audio, 124-128
building, 102
customizing, 107
delegates, 103-104
displaying, 104
indicators, 123-124
interaction, restricting, 118
local notifications, 121-123
modal, creating with run loops, 110-113
modifying, 107
no-button, 107-110
overlays, customizing, 117-119
overview of, 101-105

Please Wait, displaying progress, 105-107
popovers, 119-121
sounds, 126
types, 104-105

UIActivityIndicatorView class, 106
views, applying variadic arguments, 113-114
alertViewStyle property, 104
algorithms, smoothing, 22
aligning
alignment property, 431
alignment rectangles, 170-171
indicators, 106
views, constraints, 199-200
alignmentRectForFrame: method, 171
allApplicationSubviews() function, 133
allowsEditing property, 302, 307, 316
alpha levels, testing bitmap touches against, 17

Alpha property, 160
alphanumeric characters, 239

AND conditions, 67
animationImage property, 167
animations, 129
blocks, combining multiple view changes, 161
buttons, responses, 59-61
creation/deletion, 424
elements, adding to buttons, 58-59
star sliders, 77
transitions, 164

UIView class, 158-159
views
blocks, 159
bouncing, 165-133

Core Animation transitions, 163-165
fading in and out, 159-160
flipping, 162-163
image animations, 166-167
swapping, 161

APIs (application programming interfaces), 253

Bluetooth connectivity, 471
appearance proxies, 66-68

Apple

Accessibility Programming Guide for iPhone, 343
gestures, detecting, 5

Apple File Connection. See AFC
application programming interfaces. See APIs

Application Support folder, 301
application:didFinishLaunchingWithOptions: method, 122
applicationIconBadgeNumber property, 124
applications, 5
activities, 333
badging, 124
constraints, executing, 174
context, creating, 445
gestures, troubleshooting, 347
local notifications, 121-123
networks, checking status, 471

Reflection, 314
universal split view/navigation, 266-268
views, 129. See also views

VoiceOver gestures, 350. See also VoiceOver
windows, 133
applicationSupportsShakeToEdit property, 369
applying
built-in detectors, 244
disclosure accessories, 365-367
fetch requests, 449-450

JSON serialization, 487-489
multiple gesture recognizers simultaneously, 9-13

UIActivityIndicatorView class, 106

UIDatePicker class, 399-400

UIProgressView class, 107
variadic arguments with alert views, 113-114
views, pickers, 397-398

ARC (automatic reference counting), 98
arrays 497
sections, 375-376
windows, returning, 133
arrows, formatting, 120
aspect ratios, formatting, 197-199
assets, libraries, 301-302
assigning
colors to backgrounds, 157
data sources to tables, 356
delegates, 357
images to buttons, 56
ranges to controls, 73
titles, 255

User Defined Runtime Attributes, 137
associating
center properties, 196
objects, naming views, 137-139
asynchronous downloads
networks, 480-486
one-call no-feedback, 486-487
asynchronous feedback, 105
at sign (@), 184
attachments, 321. See also e-mailing images
attributed text, enabling, 236
attributes, 443
baseline, 172
bottom, 171
centerX, 172
centerY, 172
constraints, 171-173
controls, 70-72
customizing, 443
height, 172
leading, 172
left, 171
right, 171
text, managing, 236
top, 171
trailing, 172

User Defined Runtime Attributes, assigning, 137
width, 172
audio
alerts, 124-128
delays, 126
disposing of, 126-127
interfaces, 341. See also accessibility; VoiceOver
system sounds, building, 124-125
vibration, 125-126

Audio Queue, 124

AudioServicesAddSystemSoundCompletion() method, 125

AudioServicesPlaySystemSound class, 124

Auto-Hide sliders, adding, 285
autocapitalizationType property, 211
autocorrectionType property, 211
autolayout, 139
constraints, 173
automatic reference counting. See ARC
autorepeats, steppers, 73
autoresizesSubviews property, 144
autoresizingMask property, 144
autosizing constraints, 192-193
availability, Wi-Fi, 471

AVAudioPlayer class, 124, 126

AVFoundation, 316-318
axes, 176
constraints, 184

B

Back button, 250, 254
titles, 256
backgrounds
buttons, 56
colors, 15, 157
backsplashes, adding, 16
badging applications, 124
bar buttons, 254-255
macros, 256
baseline attribute, 172
basic flows, collection views, 411-414
beginTrackingWithTouch:withEvent: method, 74
beginUpdates method, 392
below method, 135
best practices, local notifications, 122-123

Bezier paths, 19
smoothing, 22
bindings, variables, 177
bitmap images, testing against, 17-19. See also images
blocks
animations, combining multiple view changes, 161
code, 105
interaction, 109
views, animations, 159
blue disclosures, 367

Bluetooth connections, 471
book properties, 276-277

Boolean values, 310
borders, styles, 212
bottom attribute, 171
bouncing views, 165-133
boundaries, defining, 140
bounds
constraints, 174, 193
movement, 14
properties, 140, 144
rectangles, 29
views, 15
boxing Objective-C literals, 496-497

BrightnessController class, 269
broadcasting, accessibility updates, 348
building
alerts, 102
indicators, 123-124
overlays, 117-119
buttons, 55-59
controls, subclassing, 73-77
dynamic slider thumbs, 63
entities, 442
floating progress monitors, 109-110
model files, 442-443
multiwheel tables, 396-399
objects, classes, 444
page indicator controls, 90-92
paged scrollers, customizing, 93
presentation indexes, 279
pull controls, 83-87
pull-to-refresh into tables, 389
sections, 375-376, 453
sounds, 124-125
split view controllers, 261-266
star slider, 77-80
tables, 359
text editors, 233-239
toolbars, 99
touch wheels, 80-83
transforms, 33
tree parsers, 490
views, 134-136

XIB, 132
built-in cameras, iPhones, 300
built-in detectors, applying, 244
buttons, 49, 53-54. See also controls
actions, connecting, 55

Add Attribute, 443
adding, 102, 104, 254-255
animation, adding to, 58-59
appearance proxies, 66-68

Back, 250, 254, 256
bar, 254-255, 256
building, 55-59

Camera, 314

Cancel, 102
alerts, 110
avoiding use on iPads, 116
video, 319
clear, 212

Contact Add, 53
custom lock controls, 87-90
deleting, 106, 108

Detail Disclosure, 53

Done, 120, 213

Info Dark, 53

Info Light, 53

Interface Builder, 54-55
keyboards, adding, 215
no-button alerts, 107-110
page indicator controls, 90-92
paged scrollers, customizing, 93

Play, 314
pull controls, 83-87

Redo, 233
removing, 255
responses, animating, 59-61

Rounded Rectangle, 53, 54
rounding, 56
sliders, adding with custom thumbs, 61-66
star sliders, 77-80
state, adding to, 59
switches/steppers, 72-73
text, multiline, 58
toolbars, 99
touch wheels, 80-83
twice-tappable segmented controls, 69-72

UIControl class, subclassing, 73-77

Undo, 233

C
caches, executing fetch requests, 449

CAF files, 124
calculating distance, 7. See also mathematics

Calendar, 365
callbacks, 3
alerts, 103
image picker controllers, 303-304
video, 319
views, 135-136
calling view controllers, 250

Camera button, 314
cameraCaptureMode property, 308
cameraDevice property, 307
cameraFlashMode property, 308

Camera Roll, 299, 302
activity view controllers, 330
cameras
images, snapping, 306-310
iPhones, 300
video, recording, 310-313

Cancel button, 102
alerts, 110
iPads, avoiding, 116
video, 319
cancelButtonIndex property, 103, 115
cancelTrackingWithEvent: method, 74
canEditVideoAtPath: method, 318
canPerformAction: withSender: method, 45, 239
canPerformWithActivityItems: method, 333
capitalizing labels, 345
capturing video, 300

Catmull-Rom smoothing, 22
cellForRowAtIndexPath: method, 364
cells
adding, 370
classes, registering, 356-357
colors, selecting, 362
customizing, 415-416
dequeueing, 357, 384
reordering, 370
returning, 377-378
search display controllers, registering, 384
swipes, 370
tables
creating checked, 363-365
views, 343, 361-363
center property, 140
centering, 145

CGRect structures, 146
indicators, 106
views, 6, 196-197
centerX attribute, 172
centerY attribute, 172

CFRunLoopRun() method, 112

CGAffineTransformMakeRotation() function, 151

CGAffineTransformScale() function, 151

CGPointApplyAffineTransform() method, 17

CGRect structures, 146

CGRectContainsRect() function, 146

CGRectDivide() function, 141

CGRectEqualToRect() function, 141

CGRectFromString() function, 140

CGRectGetMidX() function, 141

CGRectInset() function, 141

CGRectIntersectsRect() function, 141, 152

CGRectMake() function, 140

CGRectOffset() function, 141

CGRectZero function, 141

Chagalls, Marc, 395
chains, responders, 1
characters
alphanumeric, 239
commas (,), 183
hyphens (-), 180
pipe (|), 179
at sign (@), 184
spelling out, 350
tilde (~), 382
umlaut (¨), 382
checking network status, 471-473

Choi, Charles, 85
circles
detecting, 28-33
layouts, 423-429
circular hit tests, 16. See also hit tests
classes

ActivityAlert, 107

AudioServicesPlaySystemSound, 124

AVAudioPlayer, 124, 126

BrightnessController, 269
cells, registering, 356-357
customizing, subscripting 498

DataManager, 389

DetailViewController, 263

DownloadHelper, 480

InsetCollectionView, 417

ModalAlertDelegate, 112

NSCompoundPredicate, 449

NSEntityDescription, 445

NSIndexPath, 404

NSJSONSerialization, 487

NSLayoutConstraint, 175

NSManagedObject, 444

NSManagedObjectContext, 444

NSPredicate, 239

NSRegularExpression, 243

NSTextCheckingResult, 244

NSURLConnection, 476, 486

NSXMLParser, 489
objects, building, 444
support, appearance proxies, 67

TouchTrackView, 19

UIActionSheet, 101, 114, 117

UIActivityIndicatorView, 105-106

UIActivityIndicatorViewStyleGray, 106

UIActivityIndicatorViewStyleWhite, 106

UIActivityViewController, 329

UIAlertView, 101, 102

UIButton, 53-54. See also buttons

UICollectionView, 403

UICollectionViewCell, 415

UICollectionViewController, 405

UICollectionViewFlowLayout, 406-407, 420

UIColor, 158

UIControl, 49-53. See also controls
events, 51-53
subclassing, 73-77

UIDatePicker, 399-400

UIDevice, 231, 474

UIImage, 55

UIImagePickerController, 299-300, 308, 318

UIImageView, 167

UIKeyboardFrameEndUserInfoKey, 218

UILayoutContainerView, 130

UIMenuController, 44

UINavigationBar, 252

UINavigationController, 254

UINavigationItem, 255-257

UIPageControl, 90

UIPickerViewController, 396

UIProgressView, 105, 107

UIResponder, 3, 213

UIScreen, 143

UISegmentedControl, 69

UISlider, 62-63, 77

UISplitViewController, 250-252

UIStepper, 68, 73

UISwitch, 72. See also switches

UITabBarController, 251, 268

UITableView, 353

UITableViewController, 388

UITextAttributeFont, 70

UITextAttributeShadowColor, 71

UITextAttributeTextColor, 70

UITextAttributeTextShadowOffset, 71

UITextChecker, 246

UITextField, 209-213, 239

UITextView, 246-247

UIToolbar, 98. See also toolbars

UIVideoEditorController, 318

UIView, 2-3, 129
adding animation blocks, 60
animations, 158-159
frame geometry categories, 147

UIViewController, 3, 250

UIWindow, 130

XMLParser, 490
clear buttons, 212
clicks, adding input, 231
clipsToBounds flag, 145

CMTimeMakeWithSeconds() function, 317

Cocoa Touch, 105
cells, 361

Core Animation transitions, 164

Core Data, 441. See also Core Data
layout rules, 181
menus, viewing, 114
multiple-views, 251
network activity, 123
code
blocking, 105
custom group tables, 394-395

Objective-C, regular expressions, 243
toolbars, creating, 99
collections
constraints, 186. See also constraints
views, 403
adding gestures to layouts, 429-430
basic flows, 411-414

Core Data, 464-468
creating circle layouts, 423-429
custom cells, 415-416
customizing item menus, 437-439
establishing, 405-407
flow layouts, 407-410
formatting true grid layouts, 431-437
interactive layout effects, 420-422
overview of, 403-405
scrolling horizontal lists, 416-420
scroll snapping, 422-423
colors
backgrounds, 15, 157
cells, selecting, 362
controls, customizing, 75
comma-separated value. See CSV
commands, copy, 45
commas (,), 183
common controllers, 299. See also controllers
communication, alerts, 101-105. See also alerts
comparing
collection views/tables, 403-405
constraints, 187-189
view-to-view, 184
components, format strings, 184-185
composite drawings, 26
compound predicates, 183
compound transforms, 33
conditions, AND, 67
configuring
fetch requests, 449
image pickers, 307-308
objects, delegates, 355

System Configuration framework, 472
conflicts, gestures, 12-13
connecting. See also networks

AFC, 301
buttons to actions, 55
networks
scanning for connectivity changes, 474-475
testing, 472
store coordinators, 445
views, 179-182, 183
consistency, constraints, 202
constants
properties, 187

UITableViewIndexSearch, 386
constraint:matches: method, 188
constraintMatchingConstraint: method, 188
constraintPosition: method, 194
constraints, 139
adding, 186
aspect ratios, formatting, 197-199
attributes, 171-173
autosizing, 192-193
comparing, 187-189
debugging, 205-207
declaring, 175
defining, 194
describing, 189-191
dumps, 207
format strings, 177-182, 184-185
formatting, 175-177
hierarchies, 174
laws, 173-174
macros, 202-205
managing, 187
mathematics, 172-173
movement, 13-14
orientation, 177-179, 200-202
overview of, 169-170
predicates, 183-184
priorities, 173
rectangles, aligning, 170-171
sizes, 193-196
storing, 186-187
updating, 186-187
views, 169
aligning, 199-200
centering, 196-197
connecting, 179-182
creating fixed-size, 192-196
updating, 201
visual format, 176
constraintsAffectingLayoutForAxis: method, 179
constraintSize: method, 184-185
constraintWithinSuperviewBounds method, 193

Contact Add button, 53

Contacts Add button style, 93

Contacts list, 365
containers
accessibility, 343
customizing, 286-292

Objective-C literals, 497-498
content
messages, creating, 321-322
modes, viewing images, 308
sizing, 120
tables, 354
target offsets, customizing, 422
contentSizeForViewInPopover property, 120
contentViewController property, 252
contexts, creating, 444
continueTrackingWithTouch:withEvent: method, 74
controllers, 299
collection views, 405-406
images
e-mailing images, 321-323
pickers, 299-300
selecting images, 300-306
snapping, 306-310
menus, 45
message compose view, 323-325
modal, customizing, 258-259
navigation, 250, 252-255
page view, 251
popovers, 251-252

Quick Look preview, 336-339
search display, 383
social updates, posting, 325-328
split view, 251-255, 365
tab bar, 251
video
editing, 316-318
playing, 313-316
recording, 310-313
selecting, 318-320
views, 249
activity, 328-336
building split view controllers, 261-266
custom containers, 286-292
developing navigation controllers/split views, 252-255
message view, 323-325
modal presentation, 257-261
overview of, 249-252
page view controllers, 275-284
pushing/popping, 254
scrubbing pages in page view controllers, 285-286
sequoias, 292-297
tab bars, 268-272
tab state memory, 272-275
transitions, 288-289

UINavigationItem class, 255-257
universal split view/navigation apps, 266-268
controls, 49
attributes, 70-72
buttons
adding sliders with custom thumbs, 61-66
animating responses, 59-61
appearance proxies, 66-68
building, 55-59
custom lock controls, 87-90
customizing paged scrollers, 93

Interface Builder, 54-55
overview of, 53-54
page indicator controls, 90-92
pull controls, 83-87
star sliders, 77-80
switches/steppers, 72-73
toolbars, 99
touch wheels, 80-83
twice-tappable segmented controls, 69-72
events, 51-53
ranges, assigning, 73
remove, displaying, 369
ribbon, dragging, 85
types of, 50

UIControl class, 49-53, 73-77
converting
swipes to drags, 36

XML into trees, 489-492
coordinate systems, 142-143
coordinators, connecting store, 445
copy command, 45
copy: method, 237

Core Animation transitions, 163-165

Core Data, 441
collection views, 464-468
contexts, creating, 444
databases, querying, 448-450
entities, 442-444
models, 442-444
objects, removing, 450-451
overview of, 441
tables
data sources, 451-454
editing, 457-463
searching, 455-457

Core Graphics

Quartz calls, 62
transforms, 142

Core Media, 316-318
countDownDuration property, 401
counting rows/sections, 376
createLabel: method, 197
creation/deletion animations, 424
cross-references, 451

CSV (comma-separated value), 337

Curl style, 258
currentMode property, 145
currentResponder method, 225
customizableViewControllers property, 251
customizing
accessory views, dismissing text views, 213-216
action sheets, 114-117
activities, adding, 333
alerts, 107, 117-119
attributes, 443
buttons, adding to keyboards, 215
cells, 415-416
classes, subscripting 498
containers, 286-292
controls, building, 73-77
default appearance of members, 66
gestures, recognizers, 33-35
group tables, coding, 394-395
input views, 223-227, 230-233
items, menus, 437-439
lock controls, 87-90
modal controllers, 258-259
navigation controllers, 254
objects, 446
paged scrollers, 93
relationships, 443
segues, 292-297
star sliders, 77
tab bars, 269
target content offsets, 422
thumbs, adding sliders, 61-66
traits, 363

UISlider class, 62-63
cut: method, 237
cycles, constraints, 174

D
data
adding, 445-448
viewing, 446-448

Data Core, 445-448
data detectors, 243
data sources
collection views, 406
headers/footers, 411
methods, 359, 384-385
multiwheel tables, 356
tables, 354
assigning, 356

Core Data as, 451-454
data types, activities, 335-336
databases, 443
objects, removing, 450-451
querying, 448-450

SQLite, 447

DataManager class, 389
datePickerMode property, 400
dates
pickers, creating, 399-400
properties, 401
dealloc method, 126
debugging constraints, 189, 205-207
decimal points, entering numbers with, 239
declaring
alignment rectangles, 171
constraints, 175
decoration views, 404
defaults
appearance of members, customizing, 66
tab state, storing, 273
defining
boundaries, 140
constraints, 139, 194
delays
adding, 13
sounds, 126
delegate property, 210, 302
video, editing, 318
delegates
alerts, 103-104
assigning, 357
collection views, 406
gestures, 36
methods, 385
multiwheel tables, 356
objects, configuring, 355
sections, 380
tables, 354
views, 354-355
delete: method, 237
deleteBackwards method, 228
deleting
badges, 124
buttons, 106, 108, 255
creation/deletion animations, 424
gestures, recognizers, 13
objects, 450-451
pages, 94
requests, 369-370
text patterns, 242-246
view controllers, 287
deploying iPhones, view controllers, 266
dequeueing cells, 357, 384
describing constraints, 189-191
design
constraints, 170. See also constraints
target-action patterns, 50, 74

Detail Disclosure button, 53

DetailViewController class, 263
detecting
circles, 28-33
gestures, 5
nonreciprocal relationships, 451
second-tap feedback, 70
swipes, 36
touches, mathematics, 16
detectors
built-in, applying, 244
data, 243
developing constraints, 189
devices, 63
diacritics, 382, 449
dictionaries, 497
info, 316
metrics, 176
did-save method, 319
didAddSubview: method, 135
didMoveToSuperview: method, 135
didMoveToWindow: method, 135
direct manipulation interfaces
adding, 5-7
touches, testing, 15
directions, scrolling, 407, 431
disabling
constraints, autosizing, 192-193
gestures, recognizers, 13
pattern matching, 244
properties, transitions, 158

VoiceOver, 349
disclosure accessories, 365-367
discoverability, pull controls, 83-85
discrete valued star sliders, building, 78
dismissing
modal controllers, 259
no-button alerts, 108
text
keyboards, 209-213
views, 213-216
dismissViewControllerAnimated:completion: method, 257
dispatching events, 74-75
displaying
alerts, 104

HUD (heads-up display), 107
images, 308
ongoing actions, 52
progress, 105-107
remove controls, 369
search display controllers, 383
text in action sheets, 117
views, 129, 157-158, 257-261. See also views
disposing of system sounds, 126-127
distance, calculating, 7
distribution, views, 199-200

DNS (Domain Name System), 472

Done button, 120, 213

DownloadHelper class, 480
downloads
asynchronous
networks, 480-486
one-call no-feedback, 486-487
helpers, 482
synchronous, 476-480
downward gestures, 36
dragging
ribbon controls, 85
scroll views, 35-39
swipes, converting, 36
views
applying pan gesture recognizers, 8
formatting, 6

DragView, 6
drawing, 2
composite, 26
smooth, 21-24
touches, 19-21
drawRect: method, 2, 19, 26

Drobnik, Oliver, 110
dumps, constraints, 207
dynamic slider thumbs, building, 63

E
e-mailing images, 321-323
edges, insets, 411
editing
images, 302
tables, 371

Core Data, 457-463
views, 367-375
text views, 246
video, 316-320

XIB, 132
editors, building text, 233-239
effects, interactive layouts, 420-422
efficiency, adding, 63
ejecting keyboards, 220
elements
animation, adding to buttons, 58-59
geometry, 147
embedding
collection views, 418
views, 406

Enabled check box, 343
enableInputClickWhenVisible method, 231
enablesReturnKeyAutomatically property, 211
enabling
accessibility, 343

AirPlay, 314
attributed text, 236
background colors, 16
gestures, recognizers, 13
touch feedback, 39-40
endRefreshing method, 389
endTrackingWithTouch:withEvent: method, 74
endUpdates method, 392
entering
numbers, 239
text, 209. See also text
entities, Core Data, 442-444
enumerating regular expressions, 243
enums, boxing, 496-497
equality, 172
equations, constraints, 172-173. See also mathematics
erasing old items, 26. See also deleting
errors
networks, 471
runtime, constraints, 192

Eschers, M. C., 395
establishing collection views, 405-407
events
controls, 51-53
dispatching, 74-75
exceptions, constraints, 170
executing
applications, constraints, 174
fetch requests, 449
existing popovers, 119. See also popovers
exporting video, 317
expressions
boxing, 497
regular, 242-243
enumerating, 243
websites, 244
extra state to buttons, adding, 59
extracting view hierarchy trees, 131

F

F (float), 496

Facebook, 299
activity view controllers, 328-336
social updates, posting, 325-328

Fade style, 258
fading views, 159-160
failures, runtime, 174
feature tests, Objective-C literals, 499
feedback, 62
asynchronous, 105
nonblocking, 109
one-call no-feedback asynchronous downloads, 486-487
second-tap, 70
touches, 39-44
fetch requests, 448
applying, 449-450
configuring, 449
predicates, 455
fields
location, 248
text, 49, 212-213. See also controls
files

AFC, 301

CSV, 337
data, viewing, 446-448
headers, UIKit, 67
models, building, 442-443

SQLite, 447
store coordinators, connecting, 445
types, 124, 337
xcdatamodel, 442

XIB, 132
fileURLWithPath: method, 314
filtering text-entry, 239-241
finding views, 136-137
fingers, support, 25
firstAttribute property, 187
firstItem property, 187
firstResponder method, 225
fitting, 145
fixed size views, creating constraints, 192-196
flags
autosizing, 192
clipsToBounds, 145
kSCNetworkReachabilityFlagsConnectionOnTraffic, 472
kSCNetworkReachabilityFlagsIsDirect, 472
kSCNetworkReachabilityFlagsIsWWAN, 472

SCNetworkReachabilityGetFlags, 472
traits, 344
flexible sizes, 199-200
flipping views, 162-163

Flip style, 258
float (F), 496
floating-point numbers, 242
floating progress monitors, building, 109-110
flow layouts, collection views, 407-414
folders

Application Support, 301

Media, 301
footerReferenceSize property, 409
footers
data sources, 411
headers, formatting, 378
sizing, 409-410

Form Sheet style, 258
format strings, constraints, 177-182, 184-185
formatting
arrows, 120
aspect ratios, 197-199

Autolayout, 139
checked table cells, 363-365
constraints, 175-177
contexts, 444
controls, 74
headers/footers, 378
messages, content, 321-322
progress views, 107
repeating, 398
sections, indexes, 379-380
table views, 355-358
text, 237
text-input-aware views, 227-230
true grid layouts, 431-437
undo transactions, 459
video recording pickers, 311
views, dragging, 6
forwarding touch events, 40-41
frameForAlignmentRect: method, 171
frames, 140
alignment rectangles, 171
taps, sensing, 15
views, 140, 143-150
frameworks
adding, 301-302

AVFoundation, 316-318

Core Data, 441. See also Core Data

Media Player, playing video, 313-316

Message UI, 321

Quick Look, 336
social updates, posting, 325-328

System Configuration, 472

Full Screen style, 258
functionality, responders, 237
functions
allApplicationSubviews(), 133

CGAffineTransformMakeRotation(), 151

CGAffineTransformScale(), 151

CGRectContainsRect(), 146

CGRectDivide(), 141

CGRectEqualToRect(), 141

CGRectFromString(), 140

CGRectGetMidX(), 141

CGRectInset(), 141

CGRectIntersectsRect(), 141, 152

CGRectMake(), 140

CGRectOffset(), 141

CGRectZero, 141

CMTimeMakeWithSeconds(), 317

NSDictionaryOfVariableBindings(), 177

NSStringFromCGRect(), 140
subview utility, 133

UIImageWriteToSavedPhotosAlbum(), 309
utilities, rectangles, 140-141

G
gaps, view-to-view, 181
generating layouts, 409
geometry
constraints, 171
orientation, modifying, 200-202
rules, 181
testing, 17
views, 139-143, 147
gestureRecognizers property, 9
gestures, 1
conflicts, resolving, 12-13
delegates, 36
downward, 36
layouts, adding, 429-430
multiple recognizers, applying simultaneously, 9-13
pan recognizers, adding, 7-9
reassigning, 36
recognizers, 5, 33-35

VoiceOver, 347, 350
gestureWasHandled, 36
goesForward property, 294
graphical user interfaces. See GUIs
gray disclosures, 367
greater-than inequality, 172
groups
sections, 452
tables, coding custom, 394-395
undo, 459
guidelines
buttons, 53

HIGs, 54

GUIs (graphical user interfaces), 4
navigation controllers, 252
subview hierarchies, 129

H

H.264 Baseline Profile Level 3.0 video, 313
handlePan: method, 7-8
handling touches in views, 2
hardware, keyboards, 220. See also keyboards
hasText method, 228
headerReferenceSize property, 409
headers
data sources, 411
files, UIKit, 67
footers, formatting, 378
sizing, 409-410
heads-up display. See HUD
height attribute, 172
helpers
classes, XMLParser, 490
downloads, 482
hidden property, 157
hiding
badges, 124
custom alert overlays, 118
hierarchies
constraints, 174

Core Data, 442
views, 129-131
controllers, 259
recovering, 131-132
highlightedTextColor property, 363

HIGs (Human Interface Guidelines), 54
buttons, 53
hints, accessibility, 346
hit tests, 4, 16. See also tests

Hollemans, Matthijs, 84
horizontal axis, 176
horizontal flow, 407
horizontal lists, scrolling, 416-420

Hosgrove, Alex, 35

HUD (heads-up display), 107

Human Interface Guidelines. See HIGs
hyphens (-), 180

I

IB (Interface Builder). See also XIB
accessibility, 342
constraints, 170
layout rules, 181
modal controllers, 259
views, naming, 137-139
iCloud, image picker controllers, 299

Identity Inspector, opening, 137

IMA audio, 124
image:didFinishSavingWithError:contextInfo: method, 309
images
adding, 415
albums, saving to, 309
animations, 166-167
bitmaps, testing against, 17-19
buttons, 56
displaying, 308
editing, 302
pickers
configuring, 307-308
controllers, 299-300
e-mailing images, 321-323
iPads/iPhones, 300
selecting images, 300-306
snapping images, 306-310
retaking, 307

Saved Photos album, 299
selecting, 300
shadows, 171
simulators, adding, 301
snapping, 309
sources, 299-300
synchronization, 299
imageView property, 415
implementing
collection views, 405

Quick Look, 337-339
tables, 358-361

TOUCHkit overlays, 41
wrapping, 277-278
indexes 498
paths, access, 451
presentations, 279
searching, 386
sections, 379-380, 455
titles, 452
indexPathForObject: method, 451
indicators
alerts, 123-124
centering, 106
progress, 117

Info Dark button, 53
info dictionary, 316

Info Light button, 53
initWithFormat: arguments: method, 113
input
accessories, dismissing text views, 214
clicks, adding, 231
matching, 242
text-input-aware views, formatting, 227-230
inputAccessoryView property, 223
inputView property, 223
inserting text, modifying, 350
insertText method, 228

InsetCollectionView class, 417
insets, collection views, 410
instances
appearance, customizing, 68
self.view, 186

UIViewController class, 250
instantiating camera versions, image pickers, 307
integrating object-index paths, 451
interaction
actions, adding, 44
alerts, 103, 118. See also alerts
blocking, 109
labels, 346
layout effects, 420-422

Multi-Touch, 2, 4, 24-28
views, 157-158
intercepting touch events, 40-41

Interface Builder. See IB
interfaces
activity view controllers, 328-336

APIs, 253
audio, 341. See also accessibility; VoiceOver

AVFoundation, 316

Bluetooth connectivity, 471

Core Data, 441. See also Core Data
direct manipulation
adding, 5-7
testing touches, 15

GUIs, subview hierarchies, 129

HIGs, 53-54
modal controllers, 258
multiple-button, 69. See also buttons

Multi-Touch interaction, 4

XIB, 132
intersections, testing views, 152
intersectView: method, 152
inverse relationships, 451
iOS

BOCHS emulators, 228
tables, 353-354
iPads
action sheets, 114-117
buttons, customizing sliders, 63
image pickers, 300
keyboards, dismissing, 209
modal controllers, 258
popovers, 120
split view controllers, 253, 261, 365
video, recording, 310
view controllers, 266
iPhones
accessibility, 341, 349-350. See also accessibility

Accessibility Inspector, 347
action sheets, 114-117

Apple Accessibility Programming Guide for iPhone, 343

Bluetooth connections, 471
buttons, customizing sliders, 63
cameras, 300
coordinate systems, 143
images, 299-300
keyboards, dismissing, 209
modal controllers, 258
navigation controllers, 253
popovers, 120
tab bar controllers, 251
unlocking, 350
video, recording, 310
view controllers, 266
iPods
action sheets, 114-117
buttons, customizing sliders, 63
coordinate systems, 143
keyboards, dismissing, 209
modal controllers, 258
touch wheels, building, 80-83
video, recording, 310
view controllers, 266
isAccessibilityElement property, 343
isCameraDeviceAvailable: method, 308
isDescendantOfView: method, 133
isFlashAvailableForCameraDevice: method, 308
items
menus, customizing, 437-439
old, erasing, 26
selecting, 350
services, activities, 335-336
sizing, 407-408
zooming, 420
itemSize property, 407

J

JavaScript

Object Notation. See JSON
regular expressions, testing, 244

JSON (JavaScript Object Notation) serialization, 487-489

K
keyboardAppearance property, 211
keyboards
buttons, adding, 215
input, adding to nontext views, 229
replacing, 223
tests, 221-222
text
dismissing, 209-213
modifying around, 216-220
keyboardType property, 211
keys, paths, 451
keywords, 498
kSCNetworkReachabilityFlagsConnectionOnTraffic flag, 472
kSCNetworkReachabilityFlagsIsDirect flag, 472
kSCNetworkReachabilityFlagsIsWWAN flag, 472

L

L (long), 496
labels
accessibility, 345-346
retrieving, 137
landscape layouts, 200
laws, constraints, 173-174
layers, views, 129
layouts

Autolayout, 139
circles, 423-429
constraints, 169. See also constraints
effects, interactive, 420-422
flow, collection views, 407-414
generating, 409
gestures, adding, 429-430
nil, 405
presentations, 429
rules, 181
table views, 355
true grid, formatting, 431-437
leading attribute, 172
left attribute, 171
less-than inequality, 172
libraries
assets, frameworks, 301-302
images, 299

Interface Builder, buttons, 54-55

Regular Expression Library, 244
save-to-library feature, 319
lifetimes
alerts, 102

UIViewController class, 250
limiting
movement, 13
touches, 25
lines, spacing, 407-408
lists, scrolling horizontal, 416-420
literals, Objective-C, 495
boxing, 496-497
containers, 497-498
feature tests, 499
numbers, 495-496
subscripting, 498
live feedback, touches, 39-44

LL (longlong) 496

LLVM Clang compilers, 495, 497
loadView method, 392, 405, 455
localization, accessibility, 346
local notifications, alerts, 121-123
location fields, 248
lock controls, customizing, 87-90
long (L), 496
long presses, 5
longlong (LL), 496
look-ups, 498
loops, creating modal alerts with run, 110-113

M
macros, 495
constraints, 202-205

NSDictionaryOfVariableBindings() function, 177
view controllers, 256
managing
constraints, 187
controllers, 249. See also controllers
subviews, 134-136
text, attributes, 236
views, 134-136
manual retain-release. See MRR
master view controllers, 263. See also view controllers

MATCH operator, 239
matching
input, 242
patterns, disabling, 244
mathematics
constraints, 172-173
touches, detecting, 16
matrices, affine, 151
maximumDate property, 401
media. See also images; video

QuickTime, 316
selecting, 303

Media folder, 301

Media Player, 313-316
members, customizing appearance, 66
memory
tab state, 272-275
view controllers, 249
menus
items, customizing, 437-439
scrolling, 117
showFromTabBar, 115
showFromToolBar, 115
showInView, 115
spoken text, accessing, 350
viewing, 114
views, adding, 44-46

Message UI framework, 321
messages. See also e-mailing images
compose view controllers, 323-325
content, creating, 321-322
text, sending, 323-325
metadata, images, 303
methods
above, 135
activityImage, 333
activityTitle, 332
activityType, 332
activityViewController, 333
addConstraints:, 186
alignmentRectForFrame:, 171
application:didFinishLaunchingWithOptions:, 122

AudioServicesAddSystemSoundCompletion(), 125
beginTrackingWithTouch:withEvent:, 74
beginUpdates, 392
below, 135
callback
image picker controllers, 303-304
video, 319
callbacks, 3, 135-136
cancelTrackingWithEvent:, 74
canEditVideoAtPath:, 318
canPerformAction: withSender:, 45, 239
canPerformWithActivityItems:, 333
cellForRowAtIndexPath:, 364

CFRunLoopRun(), 112

CGPointApplyAffineTransform(), 17
constraint:matches:, 188
constraintMatchingConstraint:, 188
constraintPosition:, 194
constraintsAffectingLayoutForAxis:, 179
constraintSize:, 184-185
constrainWithinSuperviewBounds, 193
continueTrackingWithTouch:withEvent:, 74
copy:, 237
createLabel:, 197
currentResponder, 225
cut:, 237
data sources, 359, 384-385
dealloc, 126
delegates, 103, 385. See also delegates
delete:, 237
deleteBackwards, 228
did-save, 319
didAddSubview:, 135
didMoveToSuperview:, 135
didMoveToWindow:, 135
dismissViewControllerAnimated: completion:, 257
drawRect:, 2, 19, 26
enableInputClickWhenVisible, 231
endRefreshing, 389
endTrackingWithTouch:withEvent:, 74
endUpdates, 392
fileURLWithPath:, 314
firstResponder, 225
frameForAlignmentRect:, 171
handlePan:, 7-8
hasText, 228
image:didFinishSavingWithError:contextInfo:, 309
indexPathForObject:, 451
initWithFormat: arguments:, 113
insertText, 228
intersectsView:, 152
isCameraDeviceAvailable:, 308
isDescendantOfView:, 133
isFlashAvailableForCameraDevice:, 308
loadView, 392, 405, 455
multiwheel tables, 356
numberOfSectionsInTableView, 359, 376
objectAtIndexPath:, 451
paste:, 237, 239
pathToView(), 133
performArchive, 234
playInputClick, 231
pointInside: withEvent:, 16
prepareLayout, 423
prepareWithActivityItems:, 333
presentViewController:animated:completion:, 257
pushViewController:animated:, 254
reachabilityChanged, 474
reloadData, 356
removeMatchingConstraint:, 188
responders, touches, 3-4
say:, 113
scrollRangeToVisible:, 248
sectionForSectionIndexTitle:atIndex, 452
sectionIndexTitleForSectionName:, 452
select:, 237
selectAll:, 237
sendActionsForControlEvents:, 75
setBarButtonItems, 369
setMessageBody:, 321
setOn: animated:, 72
setSubject:, 321
setTitleTextAttributes: forState:, 70
setTranslation: inView:, 7
showFromBarbuttonItem: animated, 115
showFromRect:inView:animated:, 115
startRefreshing, 389
stopAnimating, 167
swap:, 161
tableView:canMoveRowAtIndexPath:, 459
tableView:cellForRowAtIndexPath:, 356, 359
tableView:didSelectRowAtIndexPath:, 354, 392
tableView:numberOfRowsInSection:, 359, 377
tableView:willSelectRowAtIndexPath:, 392
targetContentOffsetForProposedContentOffset:, 422
textFieldAtIndex:, 105
textFieldShouldReturn:, 210
toggleBoldFace:, 237
toggleItalics:, 237
toggleUnderline:, 237
touchesBegan: withEvent:, 6, 8, 25
touchesEnded: withEvent:, 3
touchesMoved: withEvent:, 19
updateItemAtIndexPath:withObject:, 369
updateTransformWithOffset:, 10
updateViewConstraints, 200

URLWithString:, 314
viewDidAppear:, 193, 455
viewDidLoad, 405, 411
views:, 176
viewWillAppear:, 250, 386
viewWillDisappear:, 250
viewWithTag:, 136
will-hide/will-show method pairs, 263
willMoveToSuperview:, 135
willRemoveSubview:, 136
metrics
constraints, 194
dictionaries, 176
predicates, 183-184
midpoints, x-axis/y-axis, 171

MIME types, 321
minimumDate property, 401
minuteInterval property, 401
mismatches, sections, 380
modal alerts, creating run loops with, 110-113
modal presentation, 257-261

ModalAlertDelegate class, 112
modalTransitionStyle property, 258
models

Core Data, 442-444
files, building, 442-443

Model-View-Controller. See MVC
modes
content, viewing images, 308
editing, 300

UIViewContentModeScaleAspectFill, 308

UIViewContentModeScaleAspectFit, 308
modifying
alerts, 107
orientation, 200-202
text
accessory views, 220-222
inserting, 350
keyboards, 216-220
transparencies, 160
views
frames, 143
sizes, 144-145
monitoring connectivity changes, 474
monitors, building floating progress, 109-110
movement, constraining, 13-14
movies, 314. See also video
moving
frameworks, 301
indicators, 106
sliders, 350
subviews, 135
views, 143

MPEG-4 Part 2 video (Simple Profile), 313

MRR (manual retain-release), 98

Multi-Touch interaction, 2, 4, 24-28
multiline button text, 58
multiple-button interfaces, 69. See also buttons
multiple gesture recognizers, applying simultaneously, 9-13
multipleTouchEnabled property, 25
multiplier property, 187
multiwheel tables, building, 396-399

MVC (Model-View-Controller), 354

N
naming

DNS, 472
entities, 442
model files, 442
views, 137-139, 179
navigating
gestures, 1. See also gestures; touches
horizontal lists, 416-420
menus, scrolling, 117
touches, 1-5. See also gestures; touches
views, hierarchies, 129-131
navigation controllers, 250, 252-255
navigationController property, 254
networkActivityIndicatorVisible property, 472
networks, 471
activity (Cocoa Touch), 123
asynchronous downloads, 480-486
connecting
scanning for connectivity changes, 474-475
testing, 472

JSON serialization, 487-489
one-call no-feedback, 486-487
status, checking, 471-473
synchronous downloads, 476-480

WWANs, 472

XML, converting into trees, 489-492
nil layouts, 405
no-button alerts, 107-110
nonblocking feedback, 109
nonreciprocal relationships, detecting, 451
notation, JSON serialization, 487-489
notifications
alerts, 121-123
keyboards, 216
video, playing, 314

NSCompoundPredicate class, 449

NSDictionaryOfVariableBindings() function, 177

NSEntityDescription class, 445

NSIndexPath class, 404

NSJSONSerialization class, 487

NSLayoutConstraint class, 175

NSManagedObject class, 444

NSManagedObjectContext class, 444

NSPredicate class, 239

NSRegularExpression class, 243

NSStringFromCGRect() function, 140

NSTextCheckingResult class, 244

NSURLConnection class, 476, 486

NSXMLParser class, 489
numberOfPages property, 91
numberOfSectionsInTableView method, 359, 376
numbers
entering, 239
floating-point, 242

Objective-C literals, 495-496

O
objectAtIndexPath: method, 451

Objective-C

AVFoundation, 316
literals, 495
boxing, 496-497
containers, 497-498
feature tests, 499
numbers, 495-496
subscripting, 498
regular expressions, 243
objects
action sheets, 103
associating, naming views, 137-139
classes, building, 444
customizing, 446
delegates, configuring, 355
fetch requests, 448

JSON serialization, 487-489
removing, 450-451

UIActionSheet class, 117

UIAlertView class, 102

UICollectionViewController class, 405

UIEvent, 2

UIPickerView, 396

UITouch, 2, 10
offsets, customizing target content, 422
old items, erasing, 26
on-the-fly, adding/deleting pages, 94
one-call no-feedback asynchronous downloads, 486-487
ongoing actions, displaying, 52
onscreen views, 4. See also views
opaque factors, 157

Open GL, 164
opening Identity Inspector, 137
operators, MATCH, 239
options
action sheets, 114-117
pull-to-refresh, adding, 388-391

Undo, adding support, 368-369
orientation
constraints, 177-179, 200-202
split view controllers, 251
overlays
alerts, customizing, 117-119
taps, 119

TOUCHkit, implementing, 41

P

Page Sheet style, 258
paged scrollers, customizing, 93
pages
adding, 94
deleting, 94
indicator controls, 90-92
view controllers, 251, 275-284
painting, 19. See also drawing
panning, 5
gesture recognizers, adding, 7-9
parallel gestures, recognizing, 10
parameters, views: method, 176
parsing

SAX parsers, 489
trees, building, 490
paste: method, 237, 239
pasting activity view controllers, 328-336
paths

Bezier, 19, 22
index access, 451
key sections, 451
pathToView() method, 133
patterns
matching, disabling, 244
presentation, image pickers, 302
target-action, 50, 74
text, deleting, 242-246

PCM audio, 124
performArchive method, 234
persistence, 272
adding, 234
phases, touches, 2-3

PhoneView (Ecamm), 301
pickers
images
configuring, 307-308
controllers, 299-300
e-mailing, 321-323
selecting, 300-306
snapping, 306-310

UIDatePicker class, 399-400
video recording, creating, 311
views, applying, 397-398
pinches, 5
pipe (|) character, 179
placeholders, text, 212

Play button, 314
playing video, 313-316
playInputClick method, 231
playSound: selector, 50

Please Wait, displaying progress, 105-107
pointInside: withEvent: method, 16
points
sizes, 141
touch, 2. See also touches
pop-up menus, 44. See also menus
popovers
alerts, 119-121
controllers, 251-252
popping view controllers, 254
populating image collections, 301
portrait layouts, 200
positioning indicators, 106
posting social updates, 325-328
predefined callback methods, 3
predicates
constraints, 183-184
fetch requests, 455
metrics, 183-184
priorities, 184
sections, 376
view-to-view, 184
preferences, grouping tables, 395
prepareLayout method, 423
prepareWithActivityItems: method, 333
presentations
activity view controllers, 328-330
image pickers, 302-303
indexes, 279
layouts, adding gestures, 429
modal, 257-261
patterns, image pickers, 302
presentViewController:animated:completion: method, 257
preventing keyboard dismissal, 210
previewing, Quick Look preview controller, 336-339
priorities
constraints, 173
predicates, 184
priority property, 187
processing touches, 1. See also touches
programming
accessibility, updating, 342

Apple Accessibility Programming Guide for iPhone, 343
progress
displaying, 105-107
floating monitors, building, 109-110
indicators, 117
views, formatting, 107

Project Navigator, moving frameworks, 301
properties
accessibilityHint, 341-342, 346
accessibilityFrame, 342
accessibilityLabel, 342, 345
accessibilityTraits, 342, 344
accessibilityValue, 342
alertViewStyle, 104
alignment, 431
allowsEditing, 302, 307, 316

Alpha, 160
animationImage, 167
applicationIconBadgeNumber, 124
applicationSupportsShakeToEdit, 369
autocapitalizationType, 211
autocorrectionType, 211
autoresizesSubviews, 144
autoresizingMask, 144
book, 276-277
bounds, 140, 144
cameraCaptureMode, 308
cameraDevice, 307
cameraFlashMode, 308
cancelButtonIndex, 103, 115
center, 140
constant, 187
contentSizeForViewInPopover, 120
contentViewController, 252
countDownDuration, 401
currentMode, 145
customizableViewControllers, 251
date, 401
datePickerMode, 400
delegate, 210, 302, 318
enablesReturnKeyAutomatically, 211
firstAttribute, 187
firstItem, 187
footerReferenceSize, 409
geometry, 139
gestureRecognizers, 9
goesForward, 294
headerReferenceSize, 409
hidden, 157
highlightedTextColor, 363
imageView, 415
inputAccessoryView, 223
inputView, 223
isAccessibilityElement, 343
itemSize, 407
keyboardAppearance, 211
keyboardType, 211
maximumDate, 401
minimumDate, 401
minuteInterval, 401
modalTransitionStyle, 258
multipleTouchEnabled, 25
multiplier, 187
navigationController, 254
networkActivityIndicatorVisible, 472
numberOfPages, 91
priority, 187
relation, 187
returnKeyType, 211
row, 380
scale, 143
scrollDirection, 407
secondAttribute, 187
secondItem, 187
section, 446
sectionIndexTitles, 452
sectionInset, 410
sectionNameKeyPath, 451
sections, 380
secureTextEntry, 211
selectedBackgroundView, 363
spellCheckingType, 211
splitViewController, 263
subviews, 133
tableFooterView, 379
text fields, 212-213
title, 263
titleLabel, 58
transform, 140, 151-152
transitions, disabling, 158
translatesAutoresizingMaskIntoConstraints, 192

UIApplication, 123
undoManager, 368
urlconnection, 481
userInteractionEnabled, 118, 158
videoPath, 318
protocols
appearance, 68

UIAccessibility, 341

UIActivityItemSource, 328

UIAppearance, 269

UIInputViewAudioFeedback, 231

UIKeyInput, 228

UIResponderStandardEditActions, 236

UITextFieldDelegate, 210

UITextInputTraits, 211
proxies, appearance, 66-68
pull controls, 83-87
pull-to-refresh option, adding, 388-391
pushbuttons, 55. See also buttons
pushing view controllers, 254
pushViewController:animated: method, 254

Q

Quartz 2D calls, 19
querying
databases, 448-450
subviews, 133-134

Quick Look, 336
preview controller, 336-339

QuickTime, 316

R
ranges, assigning controls, 73
ratios, aspect, 197-199
reachabilityChanged method, 474
readiness, tables, 452
reassigning gestures, 36
recipes
activity view controllers, 328-336
alerts
audio, 124-128
creating modal alerts with run loops, 110-113
customizing overlays, 117-119
local notifications, 121-123
popovers, 119-121
variadic arguments with alert views, 113-114
buttons
adding sliders with custom thumbs, 61-66
animating responses, 59-61
appearance proxies, 66-68
building, 55-59
custom lock controls, 87-90
customizing paged scrollers, 93
page indicator controls, 90-92
pull controls, 83-87
star sliders, 77-80
switches/steppers, 72-73
toolbars, 99
touch wheels, 80-83
twice-tappable segmented controls, 69-72
collection views
adding gestures to layouts, 429-430
basic flows, 411-414
creating circle layouts, 423-429
custom cells, 415-416
customizing item menus, 437-439
formatting true grid layouts, 431-437
interactive layout effects, 420-422
scrolling horizontal lists, 416-420
scroll snapping, 422-423
constraints
centering views, 196-197
comparing, 187-189
creating fixed-size views, 192-196
describing, 189-191
formatting aspect ratios, 197-199
responding to orientation changes, 200-202

Core Data
collection views, 464-468
editing tables, 457-463
searching tables, 455-457
table data sources, 451-454
direct manipulation interfaces, adding, 5-7
image picker controllers
e-mailing images, 321-323
selecting images, 300-306
snapping images, 306-310
message compose view controller, 323-325
movement, constraining, 13-14
multiple gesture recognizers, applying simultaneously, 9-13
networks
asynchronous downloads, 480-486
checking status, 471-473
converting XML into trees, 489-492
synchronous downloads, 476-480
no-button alerts, 107-110
pan gesture recognizers, adding, 7-9

Quick Look preview controller, 336-339
social updates, posting, 325-328
table views
action rows, 391-394
adding pull-to-refresh option, 388-391
creating checked, 363-365
editing, 367-375
multiwheel tables, 396-399
searching, 382-388
sections, 375-382
tables, implementing, 358-361
tests, against bitmap images, 17-19
text
adding custom input views to nontext views, 230-233
building text editors, 233-239
customizing input views, 223-227
deleting patterns, 242-246
detecting misspelling in UITextView, 246-247
dismissing UITextField keyboards, 209-213
dismissing views, 213-216
formatting text-input-aware views, 227-230
modifying views around accessory views, 220-222
modifying views around keyboards, 216-220
text-entry filtering, 239-241
touches
adding menus to views, 44-46
customizing gesture recognizers, 33-35
detecting circles, 28-33
dragging scroll views from, 35-39
drawing, 19-21
live feedback, 39-44

Multi-Touch interaction, 24-28
smooth drawings, 21-24
testing, 15-17

UIControl class, subclassing, 73-77
video
editing, 316-318
playing, 313-316
recording, 310-313
selecting, 318-320
view controllers
building split view controllers, 261-266
custom containers, 286-292
modal presentation, 257-261
page view controllers, 275-284
scrubbing pages in page view controllers, 285-286
segues, 292-297
tab bars, 268-272

UINavigationItem class, 255-257
universal split view/navigation apps, 266-268
views
bouncing, 165-133

Core Animation transitions, 163-165
fading in and out, 159-160
flipping, 162-163
frames, 143-150
image animations, 166-167
naming by object association, 137-139
retrieving transform information, 151-157
swapping, 161
recognizers, 5
gestures, 5, 33-35
multiple gesture, applying simultaneously, 9-13
pan gesture
adding, 7-9
recording video, 310-313
recovering views, hierarchies, 131-132
rectangles
alignment, 170-171
bounding, 29
utility functions, 140-141

Redo buttons, 233

Reflection, 314
refreshing, 388-391

Regex Pal, 244
registering cells
classes, 356-357
for search display controllers, 384

Regular Expression Library, 244
regular expressions, 242-243
enumerating, 243
websites, 244
relation property, 187
relationships, 443
constraints, 173. See also constraints
customizing, 443
inverse, 451
nonreciprocal, detecting, 451
reliability, 5
reloadData method, 356
remove controls, displaying, 369
removeMatchingConstraint: method, 188
removing
badges, 124
buttons, 106, 108, 255
gestures, recognizers, 13
objects, 450-451
subviews, 135
view controllers, 287
reordering
cells, 370
subviews, 135
repeating
formatting, 398
steppers, 73
replacing keyboards, 223
requests
delete, 369-370
fetch, 448
applying, 449-450
configuring, 449
predicates, 455
synchronous downloads, 476-480
resizing views, 183, 216
resolving conflicts, gestures, 12-13
resources, regular expressions, 244
responders
chains, 1
functionality, 237
methods, touches, 3-4
responding to touches, tables, 359
responses, animating buttons, 59-61
restricting alerts, interaction, 118
retaking images, 307
retapping, 69. See also taps
retrieving
keyboard bounds, 218
labels, 137
transforms
properties, 151-152
views, 151-157
views, 136-137
returning
cells, 377-378
values, action sheets, 115
returnKeyType property, 211
ribbon controls, dragging, 85
right attribute, 171
rolling regular expressions, 243
rotate recognizers, 429
rotations, 5

Rounded Rectangle button, 53-54
rounding buttons, 56
rows, 354. See also tables
actions, adding, 391-394
counting, 376
properties, 380
rules, 169. See also constraints
geometry, 181
layouts, 181
predicates. See predicates
separating, 183
views, centering, 196
run loops
creating modal alerts with, 110-113
modal alerts, creating with, 110-113
runtime
constraints, 174
errors, 192

User Defined Runtime Attributes, assigning, 137

S
save-to-library feature, 319

Saved Photos album, 299
saving
did-save method, 319
images to albums, 309
video, 311

SAX parsers, 489
say: method, 113
scale property, 143
scaling, 145, 151, 173
scanning for connectivity changes, 474-475
scheduling local notifications, 122

SCNetworkReachabilityGetFlags flag, 472

ScreenCurtain, 350
screens, sensing taps, 15
scroll views, dragging from touches, 35-39
scrollDirection property, 407
scrollers, customizing paged, 93
scrolling
horizontal lists, 416-420
menus, 117
snapping, 422-423
text, 350
scrollRangeToVisible: method, 248
scrubbing pages in page view controllers, 285-286

SDKs (software development kits), 5
buttons, 53
collection views, 406
constraints, 170
controllers, 299. See also controllers
gesture conflicts, resolving, 12
remove controls, displaying, 369

UIKit header files, 67

UIWindow class, 130
views
controllers, 250
moving, 143
searching

Core Data, 455-457
data source methods, 384-385
indexes, 386
tables, 382-388
text strings, 247-248
views, 136-137
second-tap feedback, 70
secondAttribute property, 187
secondItem property, 187
section property, 446
sectionForSectionIndexTitle:atIndex method, 452
sectionIndexTitleForSectionName: method, 452
sectionIndexTitles property, 452
sectionInset property, 410
sectionNameKeyPath property, 451
sections
counting, 376
delegates, 380
groups, 452
indexes, 379-380, 455
insets, 410
keys, paths, 451
mismatches, 380
properties, 380
tables, 375-382, 453
secureTextEntry property, 211
segments, twice-tappable segmented controls, 69-72
segues, 292-297
select: method, 237
selectAll: method, 237
selectedBackgroundView property, 363
selecting
colors, cells, 362
images, 300-306
items, 350
media, 303
text, 350
video, 318-320
selectors, 50
playSound:, 50
self.view instance, 186
sendActionsForControlEvents: method, 75
sending
images, e-mailing, 321-323
text messages, 323-325
sensing taps, 15
separating rules, 183
services
adding, 331-332
items, activities, 335-336
setBarButtonItems method, 369
setMessageBody: method, 321
setOn: animated: method, 72
setSubject: method, 321
setTitleTextAttributes: forState: method, 70
setTranslation: inView: method, 7
shadows, images, 171
sharing menu controllers, 45
showFromBarbuttonItem: animated method, 115
showFromRect:inView:animated: method, 115
showFromTabBar menu, 115
showFromToolBar menu, 115
showInView menu, 115
simulators
images, adding, 301
testing, 347

Sina Weibo
activity view controllers, 328-336
social updates, posting, 325-328

Siri Assistant, 341
sizing
alerts, 102
constraints, 193-196
content, 120
fixed size views, creating constraints, 192-196
flexible, 199-200
footers, 409-410
frames, 143
headers, 409-410
items, 407-408
points, 141
views, modifying, 144-145
skinnable input elements, creating, 224

Slide style, 258
sliders, 49. See also controls

Auto-Hide, adding, 285
buttons, adding with custom thumbs, 61-66
moving, 350
star, 77-80
thumbs, building dynamic, 63
smooth drawings, 21-24
snapping
images, 306-310
scrolling, 422-423
social updates, posting, 325-328
software, 5
software development kits. See SDKs
sorting
constraints, 173
fetch requests, 449

Sound Effects, 124
sounds
alerts, 126
building, 124-125
delays, 126
disposing of, 126-127
vibration, 125-126
sources
data source. See data sources
images, 299-300
spacing lines, 407-408
spell checking, 246-247
spellCheckingType property, 211
spelling out characters, 350
split view controllers, 251-255
building, 261-266
iPads, 365
universal split view/navigation apps, 266-268
splitViewController property, 263
spoken text menus, accessing, 350

SQL tables, 448

SQLite files, 447
stacks, navigation controllers, 253-254
star sliders, 77-80
startRefreshing method, 389
state
buttons, adding to, 59
storing, 364
tabs, memory, 272-275
status, checking network, 471-473
steppers, 72-73
stopAnimating method, 167
store coordinators, connecting, 445
storing
constraints, 186-187
state, 364
tab state, 273
storyboards, 259
strings
format, 177-182, 184-185
text, searching, 247-248
structures, centering CGRect, 146
styles
borders, 212

Contacts Add button, 93
modal presentations, 258
tables, 355

UIAlertViewStyleDefault, 104

UIButtonTypeCustom, 55

UITableViewCellStyleDefault, 362

UITableViewCellStyleSubtitle, 362

UITableViewCellStyleValue1, 362

UITableViewCellStyleValue2, 362
subclassing, UIControl class, 73-77
subscripting Objective-C literals 498
subviews, 129. See also views
adding, 134-135
managing, 134-136
querying, 133-134
removing, 135
reordering, 135
tagging, 136
sufficient constraints, 203
superviews, 183
support

Application Support folder, 301
classes, appearance proxies, 67
fingers, 25
section-based tables, 380
undo, adding, 234, 368-369, 458
swap: method, 161
swapping views, 161
swipes, 5
cells, 370
detecting, 36
dragging, converting, 36
switches, 49, 72-73. See also controls
synchronizing images, 299
synchronous downloads, 476-480

System Audio, 124

System Configuration framework, 472
system sounds
building, 124-125
disposing of, 126-127

T
tableFooterView property, 379
tables
building, 359
collection views, comparing, 403-405

Core Data
as data sources, 451-454
editing, 457-463
searching, 455-457
editing, 371
implementing, 358-361
iOS, 353-354
readiness, 452
sections, building, 453

SQL, 448
styles, 355
touches, responding to, 359
views, 353
action rows, 391-394
adding pull-to-refresh option, 388-391
cells, 343, 361-363
coding custom group tables, 394-395
creating checked, 363-365
delegates, 354-355
disclosure accessories, 365-367
editing, 367-375
formatting, 355-358
multiwheel tables, 396-399
searching, 382-388
sections, 375-382

UIDatePicker class, 399-400
tableView:canMoveRowAtIndexPath: method, 459
tableView:cellForRowAtIndexPath: method, 356, 359
tableView:didSelectRowAtIndexPath: method, 354, 392
tableView:numberOfRowsInSection: method, 359, 377
tableView:willSelectRowAtIndexPath: method, 392
tabs
bars, 251, 268-272
state, 272-275
tags

UI_APPEARANCE_SELECTOR, 67
views, 136-137
taps, 5
overlays, 119
second-tap feedback, 70
sensing, 15
twice-tappable segmented controls, 69-72
target-action design patterns, 50, 74
target content offsets, customizing, 422
targetContentOffsetForProposedContentOffset: method, 422
templates, Core Data, 442
testing
accessibility, 349-350
against bitmap images, 17-19

Bluetooth connections, 471
features, Objective-C literals, 499
hit, 4
keyboards, 221-222
networks
checking status, 471
connecting, 472
pull controls, 85
regular expressions, 244
simulators, 347
touches, 15-17
views, intersections, 152
text
accessibility, 345
action sheets, displaying, 117
alerts, 113. See also alerts
attributed, enabling, 236
attributes, managing, 236
buttons, multiline, 58
editors, building, 233-239
entry, 209
fields, 49, 212-213. See also controls
file types, 337
formatting, 237
inserting, modifying, 350
keyboards, dismissing, 209-213
labels, 345-346
messages, sending, 323-325
patterns, deleting, 242-246
scrolling, 350
selecting, 350
spoken text menus, accessing, 350
strings, searching, 247-248
text-entry filtering, 239-241
titles, assigning, 255
traits, 211

UITextView class, detecting misspelling in, 246-247
views
adding custom input views to nontext views, 230-233
customizing input, 223-227
dismissing, 213-216
editing, 246
formatting text-input-aware, 227-230
modifying around accessory views, 220-222
modifying around keyboards, 216-220
textFieldAtIndex: method, 105
textFieldShouldReturn: method, 210
thumbs. See also images
customizing, adding sliders, 61-66
sliders, building dynamic, 63
tilde (~), 382
titleLabel property, 58
titles
assigning, 255

Back button, 256
indexes, 452
properties, 263
toggle-style buttons, 55. See also buttons
toggleBoldFace: method, 237
toggleItalics: method, 237
toggles, VoiceOver, 349-350. See also VoiceOver
toggleUnderline: method, 237
toolbars, building, 99
tools, painting, 19. See also drawing
top attribute, 171
touches, 1-5
bitmap alpha levels, testing against, 17
circles, detecting, 28-33
direct manipulation interfaces, adding, 5-7
drawing, 19-21
gestures. See also gestures
customizing recognizers, 33-35
recognizers, 5
limiting, 25
live feedback, 39-44
mathematics, detecting, 16
movement, constraining, 13-14

Multi-Touch interaction, 2, 4, 24-28
phases, 2-3
pull controls, 85
responder methods, 3-4
scroll views, dragging from, 35-39
smooth drawings, 21-24
tables, responding to, 359
testing, 15-17
touch wheels, 80-83
tracking, 3, 74
views, 4
adding menus, 44-46
handling, 2
touchesBegan: withEvent: method, 6, 8, 25
touchesEnded: withEvent: method, 3
touchesMoved: withEvent: method, 19

TOUCHkit, 39
overlays, implementing, 41

TouchTrackView class, 19
traces, adding, 26
tracking touches, 3, 74
trailing attribute, 172
traits
accessibility, 344-345
customizing, 363
text, 211
transactions, creating undo, 459
transforms
building, 33
properties, 140
retrieving, 151-152
views, 142, 151-157
transitions
animations, 164

Core Animation, 164
core animation, 163-165
modal presentations, 258
properties, disabling, 158

UIView class animations, 162
view controllers, 288-289
translatesAutoresizingMaskIntoConstraints property, 192
transparencies, modifying, 160
tree-based hierarchies, 129-131
recovering, 131-132
trees
parsing, building, 490

XML, converting into, 489-492
trimming video, 317-318
troubleshooting. See also errors; testing
gestures
applications, 347
conflicts, 12-13
network connection loss, 474
true grid layouts, formatting, 431-437

Tweetbot, 391
twice-tappable segmented controls, 69-72

Twitter, 299
activity view controllers, 328-336
social updates, posting, 325-328
two-dimensional arrays, 376
two-state buttons, 59. See also buttons
txt2re generator, 244
types
of alerts, 104-105
of controls, 50
data, 335-336
of files, 124, 337
of layouts, 200

MIME, 321
typing text (VoiceOver), 350

U

U (unsigned), 496

UIAccessibility protocol, 341

UIActionSheet class, 101, 114, 117

UIActivityIndicatorView class, 105-106

UIActivityIndicatorViewStyleGray class, 106

UIActivityIndicatorViewStyleWhite class, 106

UIActivityItemSource protocol, 328

UIActivityViewController class, 329

UIAlertView class, 101-102

UIAlertViewStyleDefault style, 104

UIAppearance protocol, 269

UI_APPEARANCE_SELECTOR tag, 67

UIApplication property, 123

UIButton class, 53-54. See also buttons

UIButtonTypeCustom style, 55

UICollectionViewCell class, 415

UICollectionView class, 403

UICollectionViewController class, 405

UICollectionViewFlowLayout class, 406-407, 420

UIColor class, 158

UIControl class, 49-53. See also controls
events, 51-53
subclassing, 73-77

UIDatePicker class, 399-400

UIDevice class, 231, 474

UIEvent objects, 2

UIGestureRecognizer, 7, 33

UIGestureRecognizerDelegate, 36

UIImage class, 55

UIImagePickerController class, 299-300, 308, 318

UIImageView class, 167

UIImageWriteToSavedPhotosAlbum() function, 309

UIInputViewAudioFeedback protocol, 231

UIKeyboardFrameEndUserInfoKey class, 218

UIKeyInput protocol, 228

UIKit class
calls, 19
item behaviors, 344

UIKit header files, 67

UILayoutContainerView class, 130

UIMenuController class, 44

UINavigationBar class, 252

UINavigationController class, 254

UINavigationItem class, 255-257

UIPageControl class, 90

UIPickerViewController class, 396

UIPickerView objects, 396

UIProgressView class, 105, 107

UIResponder class, 3, 213

UIResponderStandardEditActions protocol, 236

UIScreen class, 143

UISegmentedControl class, 69

UISlider class, 62-63, 77. See also sliders

UISplitViewController class, 250-252

UIStepper class, 68, 73

UISwitch class, 72. See also switches

UITabBarController class, 251, 268

UITableView class, 353

UITableViewCellStyleDefault style, 362

UITableViewCellStyleSubtitle style, 362

UITableViewCellStyleValue1 style, 362

UITableViewCellStyleValue2 style, 362

UITableViewController class, 388

UITableViewIndexSearch constant, 386

UITappGestureRecognizer, 10

UITextAttributeFont class, 70

UITextAttributeShadowColor class, 71

UITextAttributeTextColor class, 70

UITextAttributeTextShadowOffset class, 71

UITextChecker class, 246

UITextField class, 209-213, 239

UITextFieldDelegate protocol, 210

UITextInputTraits protocol, 211

UITextView class, 246-247

UIToolbar class, 98. See also toolbars

UITouch objects, 2, 10

UITouchPhaseBegan, 2

UITouchPhaseCancelled, 3

UITouchPhaseEnded, 3

UITouchPhaseMoved, 2

UITouchPhaseStationary, 3

UIVideoEditorController class, 318

UIView class, 2, 3, 129
animation blocks, adding, 60
animations, 158-159
frame geometry categories, 147

UIViewContentModeScaleAspectFill mode, 308

UIViewContentModeScaleAspectFit mode, 308

UIViewController class, 3, 250

UIWindow class, 130
umlaut (¨), 382
undo
support (Core Data), adding, 458
transactions, creating, 459

Undo buttons, 233
support, adding, 368-369
undoManager property, 368
unlocking iPhones, 350
unsigned (U), 496
updateItemAtIndexPath:withObject: method, 369
updateTransformWithOffset: method, 10
updateViewConstraints method, 200
updating
accessibility, 342, 347
broadcasting, 348
constraints, 186-187
responding to, 272
size components, 144
social updates, posting, 325-328
views
centers, 6
constraints, 201

URLs (uniform resource locators)

asset libraries, moving, 301
video, editing, 316
urlconnection property, 481

URLWithString: method, 314

User Defined Runtime Attributes, assigning, 137
userInteractionEnabled property, 118, 158
users
alerts, 101. See also alerts
defaults, storing tab state, 273
interaction, blocking, 109
utilities
functions, rectangles, 140-141
subview functions, 133

V
values
action sheets, returning, 115

Boolean, 310
transforms, retrieving, 153
variables, bindings, 177
versions
constraints, 170
macros, 256
vertical axis, 176
vertical flow, 407
vibration, 125-126
video
capturing, 300
editing, 316-320
exporting, 317
playing, 313-316
recording, 310-313
saving, 311
selecting, 318-320
trimming, 317-318
videoPath property, 318
viewDidAppear: method, 193, 455
viewDidLoad method, 405, 411
viewing
alerts, 104
data, 446-448

HUD, 107
images, 308
menus, 114
text in action sheets, 117
views, 129
accessory, 364
alerts, applying variadic arguments, 113-114
animations, 158-159
bouncing, 165-133
bounds, 15, 193
callbacks, 135-136
centers, updating, 6
collections, 403
adding gestures to layouts, 429-430
basic flows, 411-414

Core Data, 464-468
creating circle layouts, 423-429
custom cells, 415-416
customizing item menus, 437-439
establishing, 405-407
flow layouts, 407-410
formatting true grid layouts, 431-437
interactive layout effects, 420-422
overview of, 403-405
scroll snapping, 422-423
scrolling horizontal lists, 416-420
connecting, 179-182, 183
constraints, 169. See also constraints
aligning, 199-200
centering, 196-197
creating fixed-size, 192-196
updating, 201
controllers, 249
activity, 328-336
building split view controllers, 261-266
custom containers, 286-292
developing navigation controllers/split views, 252-255
message view, 323-325
modal presentation, 257-261
overview of, 249-252
page view controllers, 275-284
pushing/popping, 254
scrubbing pages in page view controllers, 285-286
segues, 292-297
tab bars, 268-272
tab state memory, 272-275
transitions, 288-289

UINavigationItem class, 255-257
universal split view/navigation apps, 266-268

Core Animation transitions, 163-165
decoration, 404
displaying, 157-158
distribution, 199-200
dragging, formatting, 6
embedding, 406
fading in and out, 159-160
finding, 136-137
flipping, 162-163
frames, 140, 143-150
geometry, 139-143, 147
hierarchies, 129-132
image animations, 166-167
interaction, 157-158
intersections, testing, 152
menus, adding, 44-46
moving, 143
naming, 137-139, 179
pickers, applying, 397-398
progress, formatting, 107
resizing, 183
retrieving, 136-137
rounding, 56
scroll, dragging, 35-39
shadows, 171
sizes, modifying, 144-145
subviews
adding, 134-135
managing, 134-136
querying, 133-134
removing, 135
reordering, 135
swapping, 161
tables, 353
action rows, 391-394
adding pull-to-refresh option, 388-391
cells, 343, 361-363
coding custom group tables, 394-395

Core Data, 457-463
creating checked, 363-365
delegates, 354-355
disclosure accessories, 365-367
editing, 367-375
formatting, 355-358
implementing, 358-361
iOS, 353-354
multiwheel tables, 396-399
searching, 382-388
sections, 375-382

UIDatePicker class, 399-400
tagging, 136-137
text
adding custom input views to nontext views, 230-233
customizing input, 223-227
dismissing, 213-216
editing, 246
formatting text-input-aware, 227-230
modifying around accessory views, 220-222
modifying around keyboards, 216-220
scrolling, 350
touches, 2, 4

TOUCHkit overlays, implementing, 41
transforms, 142, 151-157
zooming, 161
views: method, 176
view-to-view gaps, 181
view-to-view predicates, 184
viewWillAppear: method, 250, 386
viewWillDisappear: method, 250
viewWithTag: method, 136
visual format constraints, 176, 185

Voice Control, 341

VoiceOver, 341. See also accessibility
gestures, 350
hints, 346
labels, 345-346
simulators, 347
testing, 349-350
traits, 344-345

W
warnings, Xcode, 451

WAV files, 124
websites, regular expressions, 244
wheels, touch, 80-83
width attribute, 172

Wi-Fi. See also networks
availability, 471
indicators, 123
wiggles, 84
will-hide/will-show method pairs, 263
willMoveToSuperview: method, 135
willRemoveSubview: method, 136
windows, 133. See also views
wireless wide area networks. See WWANs
wrapping implementations, 277-278

WWANs (wireless wide area networks), 472

X
x-axis, midpoints, 171
xcdatamodel file, 442

Xcode
constraints, debugging, 205-207
model files, building, 442-443
number literals, 495
warnings, 451

XIB (Xcode Interface Builder), 132

XML (Extensible Markup Language), converting trees into, 489-492

XMLParser class, 490

Y
y-axis, midpoints, 171

Z
zooming
items, 420
views, 161