i phone developer cookbook

380

Upload: huy-le-quoc

Post on 08-May-2015

5.363 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: I Phone Developer Cookbook
Page 2: I Phone Developer Cookbook

Praise for The iPhone Developer’s Cookbook

“This book would be a bargain at ten times its price! If you are writing iPhone software,it will save you weeks of development time. Erica has included dozens of crisp and clearexamples illustrating essential iPhone development techniques and many others thatshow special effects going way beyond Apple’s official documentation.”

—Tim Burks, iPhone Software Developer,TootSweet Software

“Erica Sadun’s technical expertise lives up to the Addison-Wesley name. The iPhoneDeveloper’s Cookbook is a comprehensive walkthrough of iPhone development that willhelp anyone out, from beginners to more experienced developers. Code samples andscreenshots help punctuate the numerous tips and tricks in this book.”

—Jacqui Cheng,Associate Editor, Ars Technica

“We make our living writing this stuff and yet I am humbled by Erica’s command of hersubject matter and the way she presents the material: pleasantly informal, then veryappropriately detailed technically.This is a going to be the Petzold book for iPhonedevelopers.”

—Daniel Pasco, Lead Developer and CEO, Black Pixel Luminance

“The iPhone Developer’s Cookbook: Building Applications with the iPhone SDK should bethe first resource for the beginning iPhone programmer, and is the best supplementalmaterial to Apple’s own documentation.”

—Alex C. Schaefer, Lead Programmer, ApolloIM, iPhoneApplication Development Specialist, MeLLmo, Inc

“Erica’s book is a truly great resource for Cocoa Touch developers.This book goes farbeyond the documentation on Apple’s Web site, and she includes methods that give thedeveloper a deeper understanding of the iPhone OS, by letting them glimpse at what’sgoing on behind the scenes on this incredible mobile platform.”

—John Zorko, Sr. Software Engineer, Mobile Devices

Page 3: I Phone Developer Cookbook
Page 4: I Phone Developer Cookbook

The iPhone™

Developer’sCookbook

Page 5: I Phone Developer Cookbook
Page 6: I Phone Developer Cookbook

The iPhone™

Developer’sCookbook

Building Applicationswith the iPhone SDK

Erica Sadun

Upper Saddle River, NJ • Boston • Indianapolis • San FranciscoNew York • Toronto • Montreal • London • Munich • Paris • Madrid

Cape Town • Sydney • Tokyo • Singapore • Mexico City

Page 7: I Phone Developer Cookbook

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

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

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

U.S. Corporate and Government Sales(800) [email protected]

For sales outside the United States please contact:

International [email protected]

AirPort, Apple, the Apple logo, Aqua, Bonjour, Cocoa, Cover Flow, Dashcode, Finder,FireWire, iMac, iPhone, iPod, iTunes, the iTunes logo, Mac, Mac logo, Macintosh,Multi-Touch, Objective-C, QuickTime, QuickTime logo, Safari, Spotlight, and Xcode aretrademarks of Apple, Inc., registered in the U.S. and other countries.

Visit us on the Web: informit.com/aw

Library of Congress Cataloging-in-Publication Data:

Sadun, Erica.The iPhone developer’s cookbook : building mobile applications with the iPhone SDK

/ Erica Sadun.p. cm.

ISBN-10: 0-321-55545-7 (pbk. : alk. paper)ISBN-13: 978-0-321-55545-8 (pbk. : alk. paper) 1. iPhone (Smartphone)–

Programming. 2. Computer software–Development. 3. Mobile computing. I. Title.QA76.8.I64S33 2009005.26—dc22

2008030294

Copyright © 2009 Pearson Education, Inc.

All rights reserved. Printed in the United States of America. This publication isprotected by copyright, and permission must be obtained from the publisher prior toany prohibited reproduction, storage in a retrieval system, or transmission in any formor by any means, electronic, mechanical, photocopying, recording, or likewise. Forinformation regarding permissions, write to:

Pearson Education, IncRights and Contracts Department501 Boylston Street, Suite 900Boston, MA 02116Fax (617) 671 3447

ISBN-13: 978-0-321-55545-8ISBN-10: 0-321-55545-7

Text printed in the United States on recycled paper at RR Donnelley inCrawfordsville, Indiana.First printing October 2008

Editor-in-ChiefKaren Gettman

Senior AcquisitionsEditorChuck Toporek

SeniorDevelopmentEditorChris Zahn

Managing EditorKristy Hart

Project EditorChelsey Marti

Copy EditorKeith Cline

IndexersCheryl Lenser,Erika Millen

ProofreaderSan Dee Phillips

TechnicalReviewersTim Burks, DanielPasco, Alex C.Schaefer

PublishingCoordinatorRomny French

Cover DesignerGary Adair

CompositionNonie Ratcliff

Page 8: I Phone Developer Cookbook

I dedicate this book with love to my husband,Alberto, whohas put up with too many gadgets and too many SDKsover the years while remaining both kind and patient at

the end of the day.

Page 9: I Phone Developer Cookbook
Page 10: I Phone Developer Cookbook

Contents

Preface xvii

Acknowledgments xxi

About the Author xxii

1 Introducing the iPhone SDK 1Apple’s iPhone SDK 1

Assembling iPhone Projects 2

iPhone Application Components 4

Application Folder Hierarchy 4

The Executable 4

The Info.plist File 4

The Icon and Default Images 6

XIB (NIB) files 6

Files Not Found in the Application Bundle 7

Sandboxes 7

Platform Limitations 8

Storage Limits 8

Data Access Limits 8

Memory Limits 8

Interaction Limits 9

Energy Limits 9

Application Limits 9

User Behavior Limits 10

SDK Limitations 10

Programming Paradigms 11

Object-Oriented Programming 11

Model-View-Controller 11

Building an iPhone Application Skeleton 18

The Hello World Application 19

The Classes 19

The Code 20

A Note About Sample Code and MemoryManagement 20

Page 11: I Phone Developer Cookbook

x Contents

Building Hello World 23

Create an iPhone Project 23

Running the Skeleton 24

Customize the iPhone Project 24

Editing Identification Information 25

Using the Debugger 26

Apple’s iPhone Developer Program 28

Development Phones 28

Application Identifiers 29

From Xcode to Your iPhone: The Organizer Interface 30

Projects and Sources List 30

Devices List 31

Summary Tab 31

Console Tab 31

Crash Logs Tab 31

Screenshot Tab 32

About Tethering 32

Testing Applications on Your iPhone 32

Compiling for Distribution 33

Using Undocumented API Calls 34

Ad Hoc Distribution 35

Summary 36

2 Views 37UIView and UIWindow 37

Hierarchy 37

Geometry and Traits 39

Gestures 42

Recipe: Adding Stepwise Subviews 42

Reorienting 44

Recipe: Dragging Views 45

UITouch 46

Adding Persistence 48

Recipe: Clipped Views 51

Balancing Touches with Clipping 53

Accessing Pixel-by-Pixel Values 54

Page 12: I Phone Developer Cookbook

xiContents

Recipe: Detecting Multitouch 56

UIView Animations 59

Building UIView Animation Blocks 59

Recipe: Fading a View In and Out 60

Recipe: Swapping Views 62

Recipe: Flipping Views 64

Recipe: Applying CATransitions to Layers 66

Undocumented Animation Types 67

General Core Animation Calls 68

Recipe: Swiping Views 69

Recipe: Transforming Views 72

Centering Landscape Views 74

Summary 74

3 View Controllers 77View Management 77

Core Classes 77

Specialized Classes 78

Creating a UIViewController 79

Working with Interface Builder to Build Views forUIViewControllers 81

Temperature Conversion Example 81

Loading XIB Files Directly 90

Navigation Controllers 91

Setting Up a Navigation Controller 91

Pushing and Popping View Controllers 92

The Navigation Item Class 92

Recipe: Building a Simple Two-Item Menu 93

Recipe: Adding a Segmented Control 95

Recipe: Adding a UIToolbar to a Navigation Bar 97

Recipe: Navigating Between View Controllers 100

Popping Back to the Root 102

Loading a View Controller Array 102

Tab Bars 103

Summary 106

Page 13: I Phone Developer Cookbook

xii Contents

4 Alerting Users 107Talking Directly to Your User Through Alerts 107

Logging Your Results 108

Building Alerts 109

Displaying the Alert 110

Recipe: Creating Multiline Button Displays 110

Recipe: Autotimed No-Button Alerts 112

Recipe: Soliciting Text Input from the User 113

Recipe: Presenting Simple Menus 115

“Please Wait”: Showing Progress to Your User 117

Recipe: Invoking the Basic UndocumentedUIProgressHUD 117

Recipe: Using UIActivityIndicatorView 119

Recipe: Building a UIProgressView 121

Recipe: Adding Custom, Tappable Overlays 123

Recipe: Building a Scroll-Down Alert 127

Recipe: Adding Status Bar Images 131

Adding Application Badges 132

Recipe: Simple Audio Alerts 134

Vibration 136

Summary 136

5 Basic Tables 139Introducing UITableView and UITableViewController 139

Creating the Table 140

What the UITableViewController Does 141

Recipe: Creating a Simple List Table 142

Data Source Functions 142

Reusing Cells 143

Font Table Sample 143

Recipe: Creating a Table-Based Selection Sheet 145

Recipe: Loading Images into Table Cells 149

Recipe: Setting a Cell’s Text Traits 151

Removing Cell Selections 152

Recipe: Creating Complex Cells 153

Recipe: Creating Checked Selections 155

Page 14: I Phone Developer Cookbook

xiiiContents

Recipe: Deleting Cells 157

Creating and Displaying Remove Controls 157

Dismissing Remove Controls 158

Handling Delete Requests 158

Swiping Cells 158

Adding Cells 159

Recipe: Reordering Cells 161

Recipe: Working with Disclosures 162

Summary 164

6 Advanced Tables 165Recipe: Grouping Table Selections 165

Building a Section-Based Data Source 166

Adding Section Headers 170

Recipe: Building a Section Table with an Index 171

Recipe: Custom Cell Backgrounds 172

Customizing the Table View 176

Recipe: Creating Alternate Blue and White Cells 177

Recipe: Framing Tables 179

Recipe: Adding Coupled Cell Controls 180

Recipe: Building a Multiwheel Table 182

Creating the UIPickerView 183

Recipe: Using the UIDatePicker 186

Creating the Date Picker 186

Recipe: Creating Fully Customized Group Tables 189

Creating Grouped Preferences Tables 189

Summary 195

7 Media 197Recipe: Browsing the Documents Folderby File Type 197

Locating Documents 198

Loading and Viewing Images 200

Recipe: Displaying Small Images 201

Recipe: Using a UIWebView to Display Images 203

Displaying Web Pages with UIWebView 205

Recipe: Browsing Your Image Library 206

Page 15: I Phone Developer Cookbook

xiv Contents

Recipe: Selecting and Customizing Images fromthe Camera Roll 209

Recipe: Snapping Pictures with the iPhone Camera 212

Working with iPhone Audio 214

Recipe: Playing Audio with Celestial 215

Recipe: Using the Media Player for Audio and VideoPlayback 217

Recipe: Recording Audio 219

Reading in Text Data 227

Displaying Property Lists 227

Recovering Media from Backup Files 228

Summary 229

8 Controls 231Recipe: Building Simple Buttons 231

The UIButton class 232

Building Custom Buttons 233

Glass Buttons 236

Recipe: Adding Animated Elements to Buttons 236

Recipe: Animating Button Responses 238

Recipe: Customizing Switches 239

Customizing UIAlertView Buttons 241

Recipe: Adding Custom Slider Thumbs 242

Adding Text to the Slider 246

Recipe: Dismissing a UITextField Keyboard 246

Recipe: Dismissing UITextView Keyboards 248

Recipe: Adding an Undo Button to Text Views 250

Recipe: Creating a Text View–Based HTML Editor 253

Recipe: Building an Interactive Search Bar 255

Recipe: Adding Callout Views 258

Adding a Page Indicator Control 260

Recipe: Customizing Toolbars 263

Toolbar Tips 266

Summary 267

9 People, Places, and Things 269Address Book Frameworks 269

Address Book UI 269

Address Book 270

Page 16: I Phone Developer Cookbook

xvContents

Recipe: Accessing Address Book Image Data 271

Recipe: Displaying Address Book Information 273

Recipe: Browsing the Address Book 274

Browsing for (Only) E-Mail Addresses 277

Adding New Contacts 277

Core Location 278

How Core Location Works 278

Recipe: Core Location in a Nutshell 280

Recipe: Reverse Geocoding to an Address 283

Recipe: Accessing Maps Using Core Location Data 286

Recipe: Accessing Core Device Information 288

Recipe: Enabling and Disabling the ProximitySensor 289

Recipe: Using Acceleration to Locate “Up” 290

Recipe: Using Acceleration to Move OnscreenObjects 292

Summary 295

10 Connecting to Services 297Recipe: Adding Custom Settings Bundles 297

Declaring Application Settings 297

Recipe: Subscribing Applications to Custom URLSchemes 302

Recipe: Checking Your Network Status 304

Testing the Network Status 304

Recovering a Local IP Address 305

Querying Site IP Addresses 306

Checking Site Availability 307

Recipe: Interacting with iPhone Databases 308

Recipe: Converting XML into Trees 311

Recipe: Storing and Retrieving Keychain Items 313

Storing Multiple Keychain Values 318

Keychain Persistence 319

Sending and Receiving Files 320

Recipe: Building a Simple Web-Based Server 321

Push Notifications 325

Summary 326

Page 17: I Phone Developer Cookbook

xvi Contents

11 One More Thing: Programming Cover Flow 327The UICoverFlowLayer Class 327

Building a Cover Flow View 329

Building a Cover Flow View Controller 331

Cover Flow Data Source Methods 332

Cover Flow Delegate Methods 333

Summary 336

Index 357

Page 18: I Phone Developer Cookbook

Preface

Few platforms match the iPhone’s unique developer technologies. It combinesOS X-based mobile computing with an innovative multitouch screen, location aware-ness, an onboard accelerometer, and more.When Apple introduced the iPhone CocoaTouch SDK beta in early March 2008, developers responded in numbers that broughtApple’s servers to its knees.Apple delivered more than one hundred thousand SDKdownloads in less than one week.The iPhone Developer’s Cookbook was written to addressthis demand, providing an accessible resource for those new to iPhone programming.

Who This Book Is ForThis book is written for new iPhone developers with projects to get done and a newunfamiliar SDK in their hands.Although each programmer brings different goals andexperiences to the table, most developers end up solving similar tasks in their develop-ment work:“How do I build a table?”;“How do I create a secure keychain entry?”;“How do I search the Address Book?”;“How do I move between views?”; and “How doI use Core Location?”

The iPhone Developer’s Cookbook is aimed squarely at anyone just getting started withiPhone programming.With its clear, fully documented examples, it will get you up tospeed and working productively. It presents already tested ready-to-use solutions, lettingprogrammers focus on the specifics of their application rather than on boilerplate tasks.

How This Book Is StructuredThis book offers single-task recipes for the most common issues new iPhone developersface: laying out interface elements, responding to users, accessing local data sources, andconnecting to the Internet.The cookbook approach delivers cut-and-paste convenience.Programmers can add source recipes into their projects and then customize them to theirneeds. Each chapter groups related tasks together. Readers can jump directly to the kindof solution they’re looking for without having to decide which class or framework bestmatches that problem.

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

n Chapter 1: Getting Started with the iPhone SDK

Chapter 1 introduces the iPhone SDK and explores the iPhone as a delivery plat-form, limitations and all. It explains the breakdown of the standard iPhone applica-tion and enables you to build your first Hello World style samples.

Page 19: I Phone Developer Cookbook

xviii Preface

n Chapter 2: Views

Chapter 2 introduces iPhone views, objects that live on your screen.You see howto lay out, create, and order your views to create backbones for your iPhone appli-cations.You read about view hierarchies, geometries, and animations as well as howusers can interact with views through touch.

n Chapter 3: View Controllers

The iPhone paradigm in a nutshell is this: small screen, big virtual worlds. InChapter 3, you discover the various UIViewController classes that enable you toenlarge and order the virtual spaces your users interact with.You learn how to letthese powerful objects perform all the heavy lifting when navigating betweeniPhone application screens.

n Chapter 4: Alerting Users

The iPhone offers many ways to provide users with a heads up, from pop-updialogs and progress bars to audio pings and status bar updates. Chapter 4 showshow to build these indications into your applications and expand your user-alertvocabulary.

n Chapter 5: Basic Tables

Tables provide an interaction class that works particularly well on a small, crampeddevice. Many, if not most, apps that ship with the iPhone and iPod touch centeron tables, including Settings,YouTube, Stocks, and Weather. Chapter 5 shows howiPhone tables work, what kinds of tables are available to you as a developer, andhow you can use table features in your own programs.

n Chapter 6: Advanced Tables

iPhone tables do not begin and end with simple scrolling lists.You can build tableswith titled sections, with multiple scrolling columns, and more.You can add con-trols such as switches, create translucent cell backgrounds, and include customfonts. Chapter 6 starts from where “Basic Tables” left off. It introduces advancedtable recipes for you to use in your iPhone programs.

n Chapter 7: Media

As you’d expect, the iPhone can load and display media from a wide variety offormats. It does music; it does movies. It handles images and Web pages.You canpresent PDF documents and photo albums and more. Chapter 7 shows way afterway that you can import or download data into your program and display that datausing the iPhone’s multitouch interface.

n Chapter 8: Control

The UIControl class provides the basis for many iPhones interactive elements,including buttons, text fields, sliders, and switches. Chapter 8 introduces controlsand their use, both through well-documented SDK calls and through less-documented ones.

Page 20: I Phone Developer Cookbook

xixPreface

n Chapter 9: People, Places, and Things

In addition to standard user interface controls and media components that you’dsee on any computer, the iPhone SDK provides a number of tightly focused devel-oper solutions specific to iPhone and iPod touch delivery. Chapter 9 introducesthe most useful of these, including Address Book access (“people”), core location(“places”), and sensors (“things”).

n Chapter 10: Connecting to Services

As an Internet-connected device, the iPhone is particularly suited to subscribing toWeb-based services.Apple has lavished the platform with a solid grounding in allkinds of network computing services and their supporting technologies.TheiPhone SDK handles sockets, password keychains, SQL access, XML processing,and more. Chapter 10 surveys common techniques for network computing andoffering recipes that simplify day-to-day tasks.

n Chapter 11: One More Thing: Programming Cover Flow

Although Cover Flow is not officially included in the iPhone SDK, it offers one ofthe nicest and most beautiful features of the iPhone experience.With Cover Flow,you can offer your users a gorgeously intense visual selection experience that putsstandard scrolling lists to shame. Chapter 11 introduces Cover Flow and showshow you can use it in your applications.

PrerequisitesHere are basics you need on hand to begin programming for the iPhone or iPod touch:

n A copy of Apple’s iPhone SDK. Download your copy of the iPhone SDKfrom Apple’s iPhone Dev Center (http://developer.apple.com/iphone/).You mustjoin Apple’s (free) developer program before you download.

n An iPhone or iPod touch. Although Apple supplies a simulator as part of itsSDK, you really do need to have an actual unit to test on if you’re going to developany serious software.You’ll be able to use the cable that shipped with your iPhoneor iPod touch to tether your unit to the computer and install the software you’vebuilt.

n An Apple iPhone Developer License. You will not be able to test your soft-ware on an actual iPhone or iPod touch until you join Apple’s iPhone Developerprogram (http://developer.apple.com/iphone/program). Members receive a certificate that allows them to sign their applications and download them to theplatforms in question for testing and debugging.The program costs $99/year forindividuals and companies, $299/year for in-house enterprise development.

n An Intel-based Macintosh running Leopard. The SDK requires a Macintoshrunning Leopard OS X 10.5.3 or later.Apple requires an Intel-based computer in32-bit mode. Many features do not work properly on PPC-based Macs or IntelMacs in 64-bit mode. Reserve plenty of disk space and at least 1GB of RAM.

Page 21: I Phone Developer Cookbook

xx Contents

n At least one available USB 2.0 port. This enables you to tether your develop-ment iPhone or iPod touch to your computer for file transfer and testing.

n An Internet connection. This connection enables you to test your programswith a live WiFi connection as well as with EDGE.

n Familiarity with Objective-C. The SDK is built around Objective-C 2.0.Thelanguage is based on standard C with object-oriented extensions. If you have anyobject-oriented and C background, making the move to Objective-C is bothquick and simple. Consult any Objective-C/Cocoa reference book to get up tospeed.

NoteAlthough the SDK supports development for the iPhone and iPod touch, as well as possibleyet-to-be-announced platforms, this book refers to the target platform as iPhone for thesake of simplicity. When developing for the touch, most material is applicable. Thisexcludes certain obvious features such as telephony and onboard speakers. This bookattempts to note such exceptions in the manuscript.

Contacting the AuthorIf you have any comments or questions about this book, please drop me an e-mailmessage at [email protected] or stop by www.ericasadun.com. My Web site hostsmany of the applications discussed in this book. Please feel free to visit, downloadsoftware, read documentation, and leave your comments.

Page 22: I Phone Developer Cookbook

Acknowledgments

This book would not exist without the efforts of Chuck Toporek, Romny French, ChrisZahn, and the entire AW production team (specifically Gary Adair, Keith Cline, KristyHart, Cheryl Lenser, Chelsey Marti, Jake McFarland, and Erika Millen).

Thanks go as well to Neil Salkind, my agent of many years, to the tech reviewerswho helped keep this book in the realm of sanity rather than wishful thinking, and to allmy colleagues at TUAW and the Digital Media/Inside iPhone blog.

I am deeply indebted to the wide community of iPhone developers, including AlexSchaefer, Nick Penree, James Cuff, Jay Freeman, Mark Montecalvo,August Joki, MaxWeisel, Optimo, Kevin Brosius, Planetbeing, Pytey, Roxfan, UnterPerro,Youssef Francis,Bryan Henry, Daniel Peebles, ChronicProductions, Greg Hartstein, Emanuele Vulcano,np101137, and Sean Heber, among many others too numerous to name individually.Their techniques, suggestions, and feedback helped make this book possible.

Special thanks go out to my family and friends, who supported me through monthafter month of new beta releases and who patiently put up with my unexplainedabsences 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 hunchedback and the sound of clicking keys is a pale substitute for a proper mother.

Page 23: I Phone Developer Cookbook

About the Author

Erica Sadun has written, coauthored, and contributed to about three dozen booksabout technology, particularly in the areas of programming, digital video, and digitalphotography.An unrepentant geek, Sadun has never met a gadget she didn’t need. Hercheckered past includes run-ins with NeXT, Newton, iPhone, and myriad successful andunsuccessful technologies.When not writing, she and her geek husband parent threeadorable geeks-in-training, who regard their parents with restrained bemusement.

Page 24: I Phone Developer Cookbook

1Introducing the iPhone SDK

The iPhone and iPod touch introduce innovative mobile platforms that are a joy toprogram.They are the first members of Apple’s new family of pocket-based computingdevices. Despite their diminutive proportions, they run a first-class version of OS X witha rich and varied SDK that enables you to design, implement, and realize a wide rangeof applications. For your projects, you can take advantage of the iPhone’s multitouchinterface and fabulous onboard features using Xcode. In this chapter, you discover howthe SDK is put together and learn to build your first iPhone-based Hello Worldapplications.

Apple’s iPhone SDKAre you ready to start programming for the iPhone? You’ll need Apple’s iPhone SoftwareDeveloper Kit (SDK), which is free and available to members of Apple’s online (free)developer program. Download your copy of the iPhone SDK from Apple’s site athttp://developer.apple.com/iphone. It consists of several components that form thebasis of the iPhone development environment.These components include the followingsoftware:

n Xcode. Xcode is the most important tool in the iPhone development arsenal. It provides a comprehensive project development and management envi-ronment, complete with source editing, comprehensive documentation, and agraphical debugger. Xcode is built around several open source GNU tools, namelygcc (compiler) and gdb (debugger).

n Instruments. Instruments profiles how iPhone applications work under thehood. It samples memory usage and monitors performance.This lets you identifyand target problem areas in your applications and work on their efficiency.Instruments offers graphical time-based performance plots that show where yourapplications are using the most resources. Instruments is build around the opensource DTrace package developed by Sun Microsystems. Instruments plays a criticalrole in tracking down memory leaks and making sure your applications run effi-ciently on the iPhone platform.

Page 25: I Phone Developer Cookbook

n Dashcode. Dashcode creates stand-alone Web-based applications that run outsideof a traditional browser environment. Conceptually, the iPhone version works justlike the desktop version, complete with layout and debugging tools. Dashboardprovides a Web-based development approach rather than native application compi-lation and is not covered in this book.

n Simulator. The iPhone Simulator runs on the Macintosh and enables you to createand test applications on your desktop.You can do this without connecting to anactual iPhone or iPod touch.The Simulator offers the same API used on theiPhone and provides a preview of how your concept designs will look.Whenworking with the Simulator, Xcode compiles Intel x86 code that runs natively onthe Macintosh rather than ARM-based code used on the iPhone.

n Interface Builder. Interface Builder (IB) provides a rapid prototyping tool thatenables you to lay out user interfaces graphically and link to those prebuilt inter-faces from your Xcode source code.With IB, you draw out your interface usingvisual design tools and then connect those onscreen elements to objects andmethod calls in your application.

Together, the components of this iPhone SDK suite enable you to develop both tra-ditional and Web-based applications. From a native application developer’s point of view,the most important components are Xcode and the Simulator, with Instruments provid-ing an essential tuning tool. In addition to these tools, there’s an important piece not onthis list.This piece ships with the SDK but is easy to overlook. I refer to Cocoa Touch.

Cocoa Touch is the library of classes provided by Apple for rapid iPhone applicationdevelopment.This library, which takes the form of a number of framework libraries,enables you to build graphical event-driven applications using user interface elementssuch as windows, text, and tables. Cocoa Touch on the iPhone is analogous to AppKit onMac OS X and supports creating rich, reusable interfaces on the iPhone.

Many developers are surprised by the size of iPhone applications; they’re tiny. CocoaTouch’s library support is the big reason for this. By letting Cocoa Touch handle all theheavy UI lifting, your applications can focus on getting their individual tasks done.Theresult is compact, focused code that does a single job at a time.

Assembling iPhone ProjectsiPhone Xcode projects contain varied standard and custom components. Figure 1-1shows a typical project. Project elements include source code, linked frameworks, andmedia such as image and audio files. Xcode compiles your source, links it to the frame-works, and builds an application bundle suitable for iPhone installation. It adds yourmedia to this application bundle, enabling your program to access that media as theapplication runs on the iPhone.

iPhone code is normally written in Objective-C 2.0.This is an object-orientedsuperset of ANSI C, which was developed from a mix of C and Smalltalk. If you’re unfamiliar with the language,Apple provides several excellent online tutorials at its

2 Chapter 1 Introducing the iPhone SDK

Page 26: I Phone Developer Cookbook

iPhone developer site.Among these are an introduction to object-oriented programmingwith Objective-C and an Objective-C 2.0 reference.These will quickly get you up tospeed with the language.

Frameworks are software libraries provided by Apple that supply the reusable classdefinitions for Cocoa Touch.Add frameworks to Xcode by dragging them onto yourproject’s Frameworks folder.After including the appropriate header files (such asUIKit/UIKit.h or QuartzCore/QuartzCore.h), you call their routines from yourprogram.

Associated media might include audio, image, and video files to be bundled with thepackage as well as text-based files that help define your application to the iPhone operat-ing system. Drop media files into your project and reference them from your code.

The project shown in Figure 1-1 is an especially simple one. It consists of a singlesource file (main.m) along with the default iPhone project frameworks (UIKit,Foundation, and Core Graphics) and a few supporting files (helloworld.png, Default.png,Icon.png, Info.plist).Together these items form all the materials needed to create a basicHello World–style application.

NoteThe HelloWorld_Prefix.pch file is created automatically by Xcode. It contains precompiledheader files. NIB and XIB files (.nib, .xib) refer to files created in Interface Builder. Theseuser interface definition files are linked to your application and called by your app at runtime.

3Assembling iPhone Projects

Figure 1-1 Xcode projects bring source code, frameworks, and media together to form the basis for iPhone applications.

Page 27: I Phone Developer Cookbook

iPhone Application ComponentsLike their Macintosh cousins, iPhone applications live in application bundles.Applicationbundles are just folders named with an .app extension.Your program’s contents andresources reside in this folder, including the compiled executable, supporting media (suchas images and audio), and a few special files that describe the application to the OS.Thefolder is treated by the operating system as a single bundle.

Application Folder HierarchyUnlike the Mac, iPhone bundles do not use Contents and Resources folders to storedata or a MacOS folder for the executable.All materials appear at the top level of thefolder. For example, instead of putting a language support .lproj folder intoContents/Resources/, Xcode places it directly into the top .app folder.You can still usesubfolders to organize your project, but these are ad hoc user-defined folders and do notfollow any standard.

The iPhone SDK’s core OS support includes the NSBundle class.This class makes iteasy to locate your application’s root folder and to navigate down to your custom sub-folders to point to and load resources.

NoteAs on a Macintosh, user domains mirror system ones. Official Apple-distributed applicationsreside in the primary /Applications folder. Third-party applications live in /var/mobile/Applications instead. For the most part, the underlying UNIX file system is obscured by theiPhone’s sandbox, which is discussed later in this section.

The ExecutableThe executable file of your application resides at the top-level folder of the applicationbundle. It must carry executable permissions to run properly and must be authenticatedby SpringBoard, the iPhone’s version of Finder. Starting with firmware 1.2, which wasreleased only to developers, SpringBoard instituted a watchdog feature to prevent arbitrarycode execution.This feature put a damper on the use of command-line utilities that youfind on other UNIX platforms. SpringBoard’s watchdog feature also added memory utilization limits.The system shuts down any process that uses too many system resources.

The Info.plist FileAs on a Macintosh, the iPhone application folder contains that all-important Info.plistfile. Info.plist files are XML property lists that describe the application to the operatingsystem. Property lists store key-value pairs for many different purposes and can be savedin readable text-based or compressed binary formats. In an Info.plist file, you specify theapplication’s executable (CFBundleExecutable) and identifier (CFBundleIdentifier).This identifier is critical to proper behavior and execution.

4 Chapter 1 Introducing the iPhone SDK

Page 28: I Phone Developer Cookbook

Use the standard Apple domain naming formats (for example, com.sadun.appname) inyour applications by editing your project’s settings in Xcode (see Figure 1-2). Specifyyour personal domain and let Xcode append the product identifier.To change identifiers,right-click your project file in Xcode and choose Get Info from the pop-up. Use theSearch field to find Product_Name and then edit that value as needed.

5iPhone Application Components

Figure 1-2 Customize your application’s bundle identifier by editing the Info.plist file. The PRODUCT_NAME identifier is specified in your project’s settings.

The product identifier enables you to communicate with other applications and toproperly register your application with SpringBoard, the “Finder” of the iPhone.SpringBoard runs the home screen from which you launch your applications.The prod-uct identifier also forms the basis for the built-in preferences system, the user defaults.

Applications preferences are automatically stored in the user Library (in /var/mobile/Library/Preferences) using the application’s identifier.This identifier is appended withthe .plist extension (for example, com.sadun.appname.plist), and the preferences arestored using a binary plist format.You can read a binary plist by transferring it to aMacintosh. Use Apple’s plutil utility to convert from binary to a text-based XML format:plutil –convert xml1 plistfile.Apple uses binary plists to lower storage require-ments and increase system performance.

As with the Macintosh, Info.plist files offer further flexibility and are highly customiz-able.With them, you can set SpringBoard variables (for example, SBUsesNetwork) orspecify how your icon should display (for example, UIPrerenderedIcon). SomeSpringBoard variables enable you to define multiple roles for a single application.For example, the Photos and Camera utilities are actually the same application,MobileSlideShow, playing separate “roles.”You can also specify whether the application ishidden from view.

Page 29: I Phone Developer Cookbook

Other standard Info.plist keys include UIStatusBarStyle for setting the look andcolor of the status bar and UIStatusBarHidden for hiding it altogether.UIInterfaceOrientation lets you override the accelerometer to create a landscape-only (UIInterfaceOrientationLandscapeRight) presentation. Register yourcustom application URL schemes (for example, myCustomApp://) by settingCFBundleURLTypes. See Chapter 10,“Connecting to Services,” for more informationabout URL schemes.

The Icon and Default ImagesIcon.png and Default.png are two key image files. Icon.png acts as your application’sicon, the image used to represent the application on the SpringBoard home screen.Default.png (officially known as your “launch image”) provides the splash screen dis-played during application launch. Unlike Default.png, the icon filename is arbitrary. Ifyou’d rather not use “icon.png,” set the CFBundleIconFile key in your Info.plist file towhatever filename you want to use.

Apple recommends matching Default.png to your application’s background. Manydevelopers use Default.png launch images for a logo splash or for a “Please wait” message.These go against Apple’s human interface guidelines (launch images should provide visualcontinuity, not advertising or excuses for delays) but are perfectly understandable uses.

The “official” application icon size is 57-by-57 pixels. SpringBoard automaticallyscales larger art. Provide flat (not glossy) art with squared corners. SpringBoard smoothesand rounds those corners and adds an automatic gloss and shine effect. If for some com-pelling reason you need to use prerendered art, set UIPrerenderedIcon to <true/> inyour Info.plist file.

NoteIf you plan to submit your application to App Store, you need to create a high-resolution(512-by-512 pixel) version of your icon. Although you can up sample your 57-by-57 icon.pngart, it won’t look good. Going the other way allows you to maintain high-quality art that youcan compress to your icon as needed.

XIB (NIB) filesInterface Builder creates XIB (also called NIB on the Macintosh) files that store precookedaddressable user interface classes.These files appear at the top level of your applicationbundle and are called directly from your program.At the time of this writing, theInterface Builder filename has not yet stabilized, although the .xib (Xcode InterfaceBuilder) extension seems to be winning out for iPhone.

NoteWhen you develop programs that do not use XIB or NIB Interface-Builder bundles, removethe NSMainNibFile key from Info.plist and discard the automatically generatedMainWindow.xib file from to your project.

6 Chapter 1 Introducing the iPhone SDK

Page 30: I Phone Developer Cookbook

Files Not Found in the Application BundleAs with the Macintosh, things you do not find inside the application bundle includepreferences files (generally stored in the application sandbox in Library/Preferences),application plug-ins (stored in /System/Library at this time and not available for generaldevelopment), and documents (stored in the sandbox in Documents).

Another thing that seems to be missing (at least from the Macintosh programmerpoint of view) is Application Support folders. Copy support data, which more rightfullywould be placed into an Application Support structure, to your Documents or Libraryfolders.

SandboxesThe iPhone OS restricts all SDK development to application “sandboxes” for the sake ofsecurity.The iPhone sandbox limits your application’s access to the file system to a mini-mal set of folders, network resources, and hardware. It’s like attending an overly restric-tive school with a paranoid principal:

n Your application can play in its own sandbox, but it can’t visit anyone else’s sandbox.n You cannot share toys.You cannot share data.You cannot mess in the administrative

offices.Your files must stay in the folders provided to you by the sandbox, and youcannot copy files to or from other application folders.

n Your application owns its own Library, Documents, and /tmp folders.These mimicthe standard folders you’d use on a less-restrictive platform but specifically limitsyour ability to write and access this data.

In addition to these limitations, your application must be signed digitally and authen-ticate itself to the operating system with a coded application identifier, which you mustcreate at Apple’s developer program site. On the bright side, sandboxing ensures that allprogram data gets synced whenever your device is plugged into its home computer. Onthe downside, at this time Apple has not clarified how that synced data can be accessedfrom a Windows- or Macintosh-based desktop application. (Chapter 7,“Media,” discussesrecovering data from the mdbackup files created by iTunes and its Mobile Devicesframework.)

NoteSandbox specification files (using the .sb extension) are stored in /var/mobile/Applicationsalong with the actual sandbox folders. These files control privileges such as read-and-writeaccess to various bits of the file system. If such a possibility should present itself, do notedit this file directly. You will render your application unusable. An exemplar sandbox fileusually appears in /usr/share/sandbox.

7iPhone Application Components

Page 31: I Phone Developer Cookbook

Platform LimitationsWhen talking about mobile platforms like the iPhone, several concerns always arise, suchas storage, interaction limits, and battery life. Mobile platforms can’t offer the same diskspace their desktop counterparts do.And along with storage limits, constrained interfacesand energy consumption place very real restrictions on what you as a developer canaccomplish.

With the iPhone, you can’t design for a big screen, for a mouse, for a physical keyboard,or even for a physical always-on A/C power supply. Instead, platform realities must shapeand guide your development. Fortunately,Apple has done an incredible job designing anew platform that somehow leverages flexibility from its set of limited storage, limitedinteraction controls, and limited battery life.

Storage LimitsThe iPhone hosts a powerful yet compact OS X installation.Although the entire iPhoneOS fills no more than a few hundred megabytes of space—almost nothing in today’s cul-ture of large operating system installations—it provides an extensive framework library.These frameworks of precompiled routines enable iPhone users to run a diverse range ofcompact applications, from telephony to audio playback, from e-mail to Web browsing.The iPhone provides just enough programming support to create flexible interfaceswhile keeping system files trimmed down to fit neatly within tight storage limits.

Data Access LimitsEvery iPhone application is sandboxed.That is, it lives in strictly regulated portion ofthe file system.Your program cannot access from other applications and from certaincordoned-off folders including the onboard iTunes library. It can, however, access any datathat is freely available over the Internet when the iPhone is connected to a network.

Memory LimitsOn the iPhone, memory management is critical.The iPhone does not support disk-swap-based virtual memory.When your run out of memory, the iPhone reboots—as Apple putsit, random reboots are probably not the user experience you were hoping for.With noswap file, you must carefully manage your memory demands and be prepared for theiPhone OS to terminate your application if it starts swallowing too much memory atonce.You must also take care as to what resources your applications use.Too many high-resolution images or audio files can bring your application into the autoterminate zone.

NoteXcode automatically optimizes your PNG images using the pngcrush utility shipped with the SDK. (You’ll find the program in the iPhoneOS platform folders in /Developer. Run itfrom the command line with the –iphone switch to convert standard PNG files to iPhone-formatted ones.) For this reason, use PNG images in your iPhone apps where possible asyour preferred image format.

8 Chapter 1 Introducing the iPhone SDK

Page 32: I Phone Developer Cookbook

Interaction LimitsLosing physical input devices and working with a tiny screen doesn’t mean you loseinteraction flexibility.With multitouch, you can build user interfaces that defy the rules.The iPhone’s touch technology means you can design applications complete with textinput and pointer control using a virtual screen that’s much larger than the actual physicalreality held in your palm.

A smart autocorrecting onscreen keyboard and an accelerometer that detects orienta-tion provide just two of the key technologies that separate the iPhone from the rest ofthe mobile computing pack.What this means, however, is that you need to cut back onthings such as text input and scrolling windows.

Focus your design efforts on easy-to-tap interfaces rather than on desktop-like mimicry.Remember, you can use just one window at a time—unlike desktop applications that arefree to use multiwindow displays.

NoteThe iPhone screen supports up to five touches at a time, although it’s rare to find anyapplication that uses more than two at once.

Energy LimitsFor mobile platforms, you cannot ignore energy limitations.That being said,Apple’sSDK features help to design your applications to limit CPU use and avoid runningdown the battery.A smart use of technology (for example, like properly suspending programs) lets your applications play nicely on the iPhone and keeps your software fromburning holes in users’ pockets (sometimes almost literally). Some programs when leftrunning produce such high levels of waste heat that the phone becomes hot to the touchand the battery quickly runs down.The Camera application is one notable example.

Application LimitsApple has instituted a strong “one-application-at-a-time” policy.That means as a third-party developer you cannot develop applications that run in the background like Apple’sMail and Phone utilities. Each time your program runs, it must clean up and metaphori-cally get out of Dodge before passing control on to the next application selected by theuser.You can’t leave a daemon running that checks for new messages or that sends outperiodic updates.An “Open SDK” created by hobbyists exists that bypasses this limitation,but applications built with those tools cannot be added to and sold through the iPhoneApp Store.

On the other hand,Apple does support push data from Web services. Registered servicescan push badge numbers and messages to users, letting them know that data is waitingon those servers.

9Platform Limitations

Page 33: I Phone Developer Cookbook

NoteAccording to the iPhone Terms of Service, you may not create external frameworks for youriPhone application or use Cocoa’s plug-in architecture for applications submitted to the AppStore.

User Behavior LimitsAlthough it’s not a physical device-based limitation, get used to the fact that iPhoneusers approach phone-based applications sporadically.They enter a program, use it quickly,and then leave just as quickly.The handheld nature of the device means you must designyour applications around short interaction periods and prepare for your application to becut off as a user sticks the phone back into a pocket. Save your application state betweensessions and relaunch quickly to approximate the same task your user was performing thelast time the program was run.

SDK LimitationsAs you might expect, building applications for the iPhone is similar to building applica-tions for the Macintosh.You use Objective-C 2.0.You compile by linking to an assort-ment of frameworks. In other ways, the iPhone SDK is limited. Here are some keypoints to keep in mind:

n Garbage Collection is MIA and probably always will be. Apple insiderssuggest that platform limitations simply do not allow for garbage collection to beimplemented in any sane and useful manner.You are responsible for retaining andreleasing objects in memory.

n Many libraries are only partly implemented. Core Animation is partiallyavailable through the Quartz Core framework, but many classes and methodsremain missing in action.The lesson here is that you’re working in early-releasesoftware.Work around the missing pieces and make sure to submit your bugreports to Apple so that it (we hope) fixes the parts that need to be used. Be awarethat Apple has deliberately cut access to some proprietary classes.

n The public SDK frameworks are not as varied as the private ones. In theoriginal iPhone open SDK jailbreak world, you used to be able to call on theiTunes Store frameworks to search the mobile store and the Celestial frameworkfor easy QuickTime-like audio/video playback.With the debut of the officialSDK, these are no longer publicly available, and Apple has limited third-partydevelopment strictly to a public framework subset.

10 Chapter 1 Introducing the iPhone SDK

Page 34: I Phone Developer Cookbook

Programming ParadigmsiPhone programming centers on two important paradigms: objected-oriented program-ming and the Model-View-Controller (MVC) design pattern.The iPhone SDK isdesigned around supporting these concepts in the programs you build.To do this, it hasintroduced delegation (controller) and data source methods (model) and customizedview classes (view). Here is a quick rundown of some important iPhone/Cocoa Touchdesign vocabulary used through this book.

Object-Oriented ProgrammingObjective-C is heavily based on Smalltalk, one of the most historically important object-oriented languages. Object-oriented programming uses the concepts of encapsulationand inheritance to build reusable classes with published external interfaces and privateinternal implementation.You build your applications out of concrete classes that can bestacked together like Lego toys, because it’s always made clear which pieces fit togetherthrough class declarations.

Multiple inheritance is an important feature of Objective-C’s approach to object-oriented programming. iPhone classes can inherit behaviors and data types from morethan one parent.Take the class UITextView, for example. It’s both text and a view. Likeother view classes, it can appear onscreen. It has set boundaries and a given opacity.Atthe same time, it inherits text-specific behavior.You can easily change its display font,color, or text size. Objective-C and Cocoa Touch combine these behaviors into a singleeasy-to-use class.

Model-View-ControllerMVC separates the way an onscreen object looks from the way it behaves.An onscreenbutton (the view) has no intrinsic meaning. It’s just a button that users can push.Thatview’s controller acts as an intermediary. It connects user interactions such as button tapsto targeted methods in your application, which is the model.The application suppliesand stores meaningful data and responds to interactions such as these button taps byproducing some sort of useful result.

Each MVC element works separately.You might swap out a push button with, forexample, a toggle switch without changing your model or controller.The program con-tinues to work as before, but the GUI now has a different look.Alternatively, you mightleave the interface as is and change your application where a button triggers a differentkind of response in your model. Separating these elements enables you to build main-tainable program components that can be updated independently.

The MVC paradigm on the iPhone breaks down into the following categories:n View. View components are provided by children of the UIView class and by its

associated (and somewhat misnamed) UIViewController class.

11Model-View-Controller

Page 35: I Phone Developer Cookbook

n Controller. The controller behavior is implemented through three key technolo-gies: delegation, target-action, and notification.

n Model. Model methods supply data through protocols such as data sourcing andmeaning by implementing callback methods triggered by the controller.

Together these three elements form the backbone of the MVC programming para-digm. Let’s look at each of these elements of the iPhone MVC design pattern in a bitmore detail.The following sections introduce each element and its supporting classes.

View ClassesThe iPhone builds its views based on two important classes: UIView andUIViewController.These two classes are responsible for defining and placing allonscreen elements.

As views draw things on your screen, UIView represents the most abstract view class.Nearly all user interface classes descend from UIView and its parent UIResponder.Viewsprovide all the visual application elements that make up your application. ImportantUIView classes include UITextViews, UIImageViews, UIAlertViews, and so forth.TheUIWindow class, a kind of UIView, provides a viewport into your application and providesthe root for your display.

Because of their onscreen nature, all views establish a frame of some sort.This is anonscreen rectangle that defines the space each view occupies.The rectangle is establishedby the view’s origin and extent.

Views are hierarchical and are built with trees of subviews.You can display a view bysetting it as your main window’s content view, or you can add it to another view byusing the addSubview method to assign a child to a parent.You can think about viewsas attaching bits of transparent film to a screen, each of which has some kind of drawingon it.Views added last are the ones you see right away.Views added earlier may beobscured by other views sitting on top of them.

Despite the name, the UIViewController class does not act strictly as controllers inthe MVC sense.They’re responsible for laying items out on the screen and obscuringmany of the more intricate layout details.Apple terminology does not always match theMVC paradigm taught in computer science classes.

First and foremost, view controllers are there to make your life easier.They takeresponsibility for rotating the display when a user reorients his or her iPhone.They resizeviews to fit within the boundaries when using a navigation bar or a toolbar.They handleall the interface’s fussy bits and hide the complexity involved in directly managing inter-action elements.You can design and build iPhone applications without ever using aUIViewController or one of its subclasses, but why bother? The class offers so muchconvenience it’s hardly worth writing an application without them.

In addition to the base controller’s orientation and view resizing support, two specialcontrollers, the UINavigationController and UITabBarController, magically handleview shifting for you.The navigation version enables you to drill down between views,smoothly sliding your display between one view and the next. Navigation controllers

12 Chapter 1 Introducing the iPhone SDK

Page 36: I Phone Developer Cookbook

remember which views came first and provide a full breadcrumb trail of “back” buttonsto return to previous views without any additional programming.

The tabbed view controller lets you easily switch between view controller instancesusing a tabbed display. So if your application has a top ten list, a game play window, anda help sheet, you can add a three-buttoned tab bar that instantly switches between theseviews without any additional programming to speak of.

Every UIViewController subclass implements its own loadView method.This isthe method that lays out the controller’s subviews and sets up all the triggers, callbacks,and delegates. So in that sense alone, the UIViewController does act as a controller byproviding these links between the way things look and how interactions are interpreted.And, because you almost always send the callbacks to the UIViewController itself, itoften acts as your model in addition to its primary role as a controller for whateverviews you create and want to display. It’s not especially MVC, but it is convenient andeasy to program.

ControllerWhen Apple designs interactive elements such as sliders and tables, they have no ideahow you’ll use them.The classes are deliberately general.With MVC, there’s no pro-grammatic meaning associated with row selection or button presses. It’s up to you as adeveloper to provide the model that adds meaning.The iPhone provides several ways inwhich prebuilt Cocoa Touch classes can talk to your custom ones. Here are the threemost important: delegation, target-action, and notifications.

DelegationMany UIKit classes use delegation to hand off responsibility for responding to userinteractions.When you set an object’s delegate, you tell it to pass along any interactionmessages and let that delegate take responsibility for them. UITableViews are a goodexample of this.When a user taps on a table row, the UITableView has no built-in wayof responding to that tap. Instead, it consults its delegate—usually a view controller classor your main application delegate—and passes along the selection change through a delegate method.

The UITableView delegate method tableView: didSelectRowAtIndexPath:

is a typical example.Your model takes control of this method and implements how itshould react to the row change.You might display a menu or navigate to a subview orplace a check mark next to the current selection.The response depends entirely on howyou implement the delegated selection change method.

To set an object’s delegate, use some variation on the setDelegate: method.Thisinstructs your application to redirect interaction callbacks to the delegate.You let Xcodeknow that your object implements delegate calls by adding a mention of the delegateprotocol it implements in the class declaration.This appears in angle brackets, to theright of the class inheritance. Listing 1-1 shows a kind of UIViewController thatimplements delegate methods for UITableView views.The MergedTableControllerclass is, therefore, responsible for implementing all required table delegate methods.

13Model-View-Controller

Page 37: I Phone Developer Cookbook

Delegation isn’t limited to Apple’s classes. It’s simple to add your own protocol decla-rations to your classes and use them to define callback vocabularies. Listing 1-1 createsthe FTPHostDelegate protocol, which declares the ftpHost instance variable.Whenused, that object must implement all three methods declared in the protocol.

NoteIf your application is built around a central table view, use UITableViewControllerinstances to simplify table creation and use.

Listing 1-1 Defining and Adding Delegate Protocols Declarations to a Class Definition

@protocol FTPHostDelegate <NSObject>- (void) percentDone: (NSString *) percent;

- (void) downloadDone: (id) sender;

- (void) uploadDone: (id) sender;

@end

@interface MergedTableController : UIViewController <UITableViewDelegate,UITableViewDataSource>

{

UIView *contentView;

UITableView *subView;

UIButton *button;

id <FTPHostDelegate> *ftpHost;

SEL finishedAction;

}

@end

Target-ActionTarget-actions are a lower-level way of redirecting user interactions.You’ll encounterthese almost exclusively for children of the UIControl class.With target-action, you tellthe control to contact a given object when a specific user event takes place. For example,you’d specify which object to contact when users press a button.

Listing 1-2 shows a typical example.This snippet defines a UIBarButtonIteminstance, a typical button-like control used in iPhone toolbars. It sets the item’s target toself and the action to @selector(setHelvetica:).When tapped, it triggers a call tothe defining object sending the setHelvetica: message.

Listing 1-2 Using Target-Actions for Adding Responses to Controls

UIBarButtonItem *helvItem = [[[UIBarButtonItem alloc]initWithTitle:@"Helvetica" style:UIBarButtonItemStyleBordered

target:self action:@selector(setHelvetica:)] autorelease];

14 Chapter 1 Introducing the iPhone SDK

Page 38: I Phone Developer Cookbook

As you can see, the name of the method (setHelvetica:) is completely arbitrary.Target-actions do not rely on an established method vocabulary the way delegates do.In use, however, they work exactly the same way.The user does something, in this casepresses a button, and the target implements the selector to provide a meaningfulresponse.

Whichever object defines this UIBarButtonItem instance must implement asetHelvetica: method. If it does not, the program will crash at runtime with anundefined method call error.

Standard target-action pairs always pass a single argument, the interaction object. Inthis case, this is the UIBarButtonItem instance that was pressed.This self-reference,where the triggered object is included with the call, enables you to build more generalaction code. Instead of building separate methods for setHelvetica:, setGeneva:,and setCourier:, you could create a single setFontFace: method to update a fontbased on which button the user pressed.

To build target-action into your own classes, add a target variable of type id (anyobject class) and an action variable of type SEL (method selector).

NotificationsIn addition to delegates and target-actions, the iPhone uses yet another way to commu-nicate about user interactions between your model and your view—and about otherevents, for that matter. Notifications enable objects in your application to talk amongthemselves, as well as to talk to other applications on your system. By broadcasting infor-mation, notifications enable objects to send state messages:“I’ve changed,”“I’ve starteddoing something,”or “I’ve finished.”

Other objects might be listening to these broadcasts, or they might not. For yourobjects to “hear” a notification, they must register with a notification center and start lis-tening for messages.The iPhone implements at least four kinds of notification centers:

n NSNotificationCenter. This is the gold standard for in-application notification.You can subscribe to any or all notifications with this kind of notification centerand listen as your objects talk to each other.The notifications are fully implementedand can carry data as well as the notification name.This name + data implementationoffers great flexibility, and you can use this center to perform complex messaging.

n NSDistributedNotificationCenter. This center is meant for interapplicationnotification. It is not fully implemented on the iPhone and should be avoided.

n DarwinNotificationCenter. The iPhone relies on Darwin notification centersfor interapplication messaging. It’s limited in that it enables you to only broadcastannouncements and will not let you send data with those announcements. So youcan announce that something has changed, but you can’t send along informationabout which item has changed. Despite this limitation, Darwin notification is reli-able and robust. Messages arrive dependably. Darwin notification is built usingstandard BSD notification (for example, notify_post(), notify_register_mach_port(), and so on).

15Model-View-Controller

Page 39: I Phone Developer Cookbook

n TelephonyNotificationCenter. Telephony notifications are private andunpublished. Unfortunately,Apple did not open up this center, but if you sneakyour way into listening to this special-purpose center, you’ll know when phonecalls and SMS messages arrive.

It’s easy to subscribe to a notification center.Add your application delegate or, moretypically, your UIViewController as a registered observer.You supply an arbitrary selec-tor to be called when a notification arrives, in this case trackNotifications:.Themethod takes one argument, an NSNotification. Ensure that your callback method willhear all application notifications by setting the name and object arguments to nil.

All notifications contain three data elements: the notification name, an associatedobject, and a user information dictionary. If you’re unsure what notifications UIKitobjects in your application produce, have your callback print out the name from all thenotifications it receives—for example, NSLog(@"%@", [notification name]).Appledoes not document notification protocols with the same love and care that it documentsdelegate and data source protocols.

The kinds of notification vary by the task you are performing. For example,notifications when rotating an application includeUIApplicationWillChangeStatusBarOrientation Notification andUIDeviceOrientationDidChangeNotification. In some cases, these correspondstrongly with existing delegate methods. In other cases, they don’t, so you’d be wise tomonitor notifications while writing programs to find any gaps in Apple’s delegate proto-col implementations. Here’s how you listen:

[[NSNotificationCenter defaultCenter] addObserver:self

selector:@selector(trackNotifications:) name:nil object:nil];

NoteThe recipes in this book generally use printf rather than NSLog() as the former workedmore reliably during the SDK beta period.

ModelYou’re responsible for building all application semantics—the model portion of any MVCapp.You create the callback methods triggered by your application’s controller and providethe required implementation of any delegate protocol.There’s one place, however, thatthe iPhone SDK gives you a hand with meaning, and that’s with data sources. Datasources enable you to fill UIKit objects with custom content.

Data SourcesA data source refers to any object that supplies another object with on demand data. SomeUI objects are containers without any native content.When you set another object as itsdata source, usually via a call like [uiobject setDataSource:applicationobject],you enable the UI object (the view) to query the data source (the model) for data such astable cells for a given UITableView. Usually the data source pulls its data in from a file

16 Chapter 1 Introducing the iPhone SDK

Page 40: I Phone Developer Cookbook

such as a local database, from a Web service such as an XML feed, or from a scannedsource such as locally detected WiFi hotspots.UITableView and UIPickerView are twoof the few Cocoa Touch classes that support or require data sources.

Data sources are like delegates in that you must implement their methods in anotherobject, typically the UITableViewController that owns the table.They differ in thatthey create/supply objects rather than react to user interactions.

Listing 1-3 shows a typical data source methods method that returns a table cell for agiven row. Like other data source methods, it enables you to separate implementationsemantics that fill a given view from the Apple-supplied functionality that builds theview container.

Objects that implement data source protocols must declare themselves just as theywould with delegate protocols. Listing 1-1 showed a class declaration that supports bothdelegate and data source protocols for UITableViews.Apple thoroughly documents datasource protocols.

Listing 1-3 Data Source Methods Supply Information That Fills a View with MeaningfulContent

// Return a cell for the ith row, labeled with its number

- (UITableViewCell *)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath

{

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

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZeroreuseIdentifier:@"any-cell"] autorelease];

}

// Set up the cell

cell.text = [tableTitles objectAtIndex:[indexPath row]];

cell.editingStyle = UITableViewCellEditingStyleDelete;

return cell;

}

The UIApplication ObjectIn theory, you’d imagine that the iPhone “model” component would center on theUIApplication class. In practice, it does not, at least not in any MVC sense of theword model. In the world of the Apple SDK, each program contains precisely oneUIApplication instance, which you can refer to via [UIApplicationsharedInstance].

For the most part, unless you need to open a URL in Safari, recover the key window,or adjust the look of the status bar, you can completely ignore UIApplication. Buildyour program around a custom application delegate class that is responsible for settingthings up when the application launches and closing things down when the applicationterminates. Otherwise, hand off the remaining model duties to methods in your customUIViewController classes or to custom model classes.

17Model-View-Controller

Page 41: I Phone Developer Cookbook

NoteUse [[UIApplication sharedInstance] keyWindow] to locate your application’smain window object.

Uncovering Data Source and Delegate MethodsIn addition to monitoring notifications, message tracking can prove to be an invaluabletool.Add the following snippet to your class definitions to expose all the methods—bothdocumented and undocumented, data source and delegate—that your class responds to:

-(BOOL) respondsToSelector:(SEL)aSelector {

printf("SELECTOR: %s\n", [NSStringFromSelector(aSelector) UTF8String]);

return [super respondsToSelector:aSelector];

}

Building an iPhone Application SkeletonNearly every iPhone application you build will contain a few key components. Here is aquick rundown of those components, how often you’ll use them, and what they do:

n The main() function (always use). Every C-based program centers around amain() function. For the iPhone, this function primes memory management andstarts the application event loop.

n The applicationDidFinishLaunching: method (always use). This methodis the first thing triggered in your program.This is where you create a basic win-dow, set its contents, and tell it to become the key responder for your application.

n The applicationWillTerminate: method (usually use). This methodenables you to handle any status finalization before handing control back toSpringBoard. Use this to save defaults, update data, and close files.

n The loadView method (always use). Assuming you’ve built your applicationaround a UIViewController—and there’s usually no reason not to do so—themandatory loadView method sets up the screen and lays out any subviews. Makesure to call [super loadView] whenever you inherit from a specialized subclasssuch as UITableViewController or UITabBarController.

n The shouldAutorotateToInterfaceOrientation: method (usually use).Unless you have pressing reasons to force your user to remain in portrait orientation,add the should-autorotate method to allow the UIViewController methodto automatically match your screen to the iPhone’s orientation.

There are many other methods you’ll use over and over again.These five provideimportant touch points that you should consider every time you create a new application.

18 Chapter 1 Introducing the iPhone SDK

Page 42: I Phone Developer Cookbook

The Hello World ApplicationListing 1-4 provides a basic image-based Hello World application.This application is built using a UINavigationController and a custom UIViewController (calledHelloController here) that displays a picture.As the name suggests, the applicationdoes little more than open itself and present itself onscreen.What you get here, therefore,are all the essentials.These are the skeletal features on which you can hang the body ofyour application when you’re ready to start writing more meaningful code.

The ClassesA navigation controller is responsible for creating that blue-gray header with the titleyou see in Figure 1-3.Although the header does little in this simple demonstration, inreal-world apps it’s a critical player.You’ll see navigation bars in action when looking atthe iPhone Mail and Safari applications.The UINavigationController class simplifiesadding these features to your application.

19The Hello World Application

Figure 1-3 UIViewController controllers can automatically handle view orientation changes by scaling their subviews. When working with images, you’ll probably want to catch orientation changes and swap out a landscape-friendly version rather than

accept the squeezed aspect shown here.

Here, the view controller loads an image and handles reshaping it for orientationchanges. Figure 1-3 shows the interface built by this source in both portrait and landscape

Page 43: I Phone Developer Cookbook

orientation. Clearly, when dealing with images, you’ll probably want to catch orientationchanges and swap out landscape image versions for portrait ones and vice versa. I chosenot to in this sample for two reasons. First, so you would get a clear visual of how theautomatic reshaping takes place. Second, to understand how powerful this reshaping is—with almost no code, the application handles reorientation all by itself.

The CodeThis program contains definitions for two classes (HelloController andSampleAppDelegate) plus the main() function.This code, like all code in this book,appears together as a single file. Rather than provide the five separate files this codewould normally entail, a combined listing makes this far more readable in book form. Istrongly encourage you to use the standard multifile system for your real applications.

The main() function was generated automatically by Xcode and remains essentiallyuntouched by human hands. It calls UIApplicationMain(), passing it the name of themain application delegate, which in this case is called SampleAppDelegate—for noother reason than I call all the sample-code application delegates in this book that. Feelfree to name your own delegates as desired.

The applicationDidFinishLaunching[colon] method is called next. Here,the code creates a new window using the standard mainScreen geometry. It builds anavigation controller and assigns a new HelloController—my UIViewControllersubclass—as its root view controller.That assigns the HelloController view as thenavigation controller’s primary subview. Doing just this enables this program to supportthe full range of navigation bar features.

To finish setting up the window, this program sets nav.view (the entire view headedby that navigation bar) as the window’s subview and orders the window out.

In the HelloController class, loadView creates the main application view,which in this case is nothing more than a UIImageView loaded with the contents ofhelloworld.png. Using self.view = contentView assigns the image view to the controller.

Setting a few flags for autorotation and adding shouldAutorotateToInterfaceOrientation: ensures the application responds toiPhone orientation changes.

A Note About Sample Code and Memory ManagementThe Hello World code that follows is the first of many samples throughout this iPhoneDeveloper’s Cookbook. This book was written during the public SDK beta period startingin March 2008. During that time,Apple released several iterations of the SDK, whichconstantly underwent changes in terms of class definitions and, more important, memorymanagement.All the sample code has been written and rewritten, tested and retested tomake sure that it continues to work with each SDK revision.

I have loosened memory management in the code throughout this book to ensure thatthe samples remain robust and working. If you wonder, while reading through code, about

20 Chapter 1 Introducing the iPhone SDK

Page 44: I Phone Developer Cookbook

seemingly extraneous retains and a lack of matching releases, it is all to that end, to ensurethat the sample code continues to operate. Feel free to tighten things where desired.

All samples in this book use the same base project (for example, Hello World).Thisensures that the samples will not overwhelm your device with a hundred odd stray iconsand names.You can find up-to-date sample code, Xcode projects, and video at my site,ericasadun.com.

Listing 1-4 Hello World

#import <UIKit/UIKit.h>

@class UIImageView;

// Custom UIViewController displays a simple image

@interface HelloController : UIViewController

{

UIImageView *contentView;

}

@end

@implementation HelloController

- (id)init

{

if (self = [super init])

{

self.title = [[[NSBundle mainBundle] infoDictionary]objectForKey:@"CFBundleName"];

// place any further initialization here

}

return self;

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"helloworld.png"]];

self.view = contentView;

[contentView release]; // reduce retain count by one

// Provide support for auto-rotation and resizing

contentView.autoresizesSubviews = YES;

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

}

// Allow the view to respond to iPhone Orientation changes

21The Hello World Application

Page 45: I Phone Developer Cookbook

Listing 1-4 Continued

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

return YES;

}

-(void) dealloc

{

// add any further clean-up here

[contentView release];

[super dealloc];

}

@end

// Application Delegate handles application start-up and shut-down

@interface SampleAppDelegate : NSObject <UIApplicationDelegate> {

}

@end

@implementation SampleAppDelegate

// On launch, create a basic window

- (void)applicationDidFinishLaunching:(UIApplication *)application {

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

UINavigationController *nav = [[UINavigationController alloc]➥initWithRootViewController:[[HelloController alloc] init]];

[window addSubview:nav.view];

[window makeKeyAndVisible];

}

- (void)applicationWillTerminate:(UIApplication *)application {

// handle any final state matters here

}

- (void)dealloc {

[super dealloc];

}

@end

int main(int argc, char *argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

int retVal = UIApplicationMain(argc, argv, nil, @"SampleAppDelegate");

[pool release];

return retVal;

}

22 Chapter 1 Introducing the iPhone SDK

Page 46: I Phone Developer Cookbook

Building Hello WorldThe following sections create the Hello World iPhone application using Xcode and theiPhone SDK.You’ll create a new iPhone project, customize it a bit, and swap in theHello World sample code from Listing 1-4.

23Building Hello World

Figure 1-4 The New Project window contains three iPhone applicationtemplates that you can use to build your programs.

Create an iPhone ProjectTo create Hello World, you need a fresh, new project in Xcode. Select File, New Project(Command-Shift-N).The New Project window (see Figure 1-4) opens. Select iPhone,Application, Cocoa Touch Application (View Based Application in newer versions ofXcode), and click Choose. Use the standard Leopard controls to navigate to where youwant to save your new application, enter a name (Hello World), and click Save. Xcodeopens a new iPhone project window and populates it with all the basic elements andframeworks you need to build your first iPhone application. Items you will see in thisproject include the following:

n Foundation and Core Graphics frameworks. These essential frameworksenable you to build your iPhone applications using the same fundamental classesand calls you are familiar with from the Macintosh.

Page 47: I Phone Developer Cookbook

24 Chapter 1 Introducing the iPhone SDK

n UIKit framework. This framework provides iPhone-specific user interface ele-ments and is key to developing applications that can be seen and interacted withon the iPhone screen.

n HelloWorld.app. Displayed in red, this placeholder will be used to store yourfinished application. Like on the Macintosh, iPhone applications are bundles andconsist of many items stored in a central folder.

n Info.plist. This file describes your application to the iPhone’s system and enablesyou to specify its executable, its application identifier, and other key features. Itworks in the same way Info.plist files work on the Mac.

n mainWindow.xib. This Interface Builder file creates an unpopulated window.You will not use this for this first walk-through.

n main.m, HelloWorldAppDelegate.h, HelloWorldAppDelegate.m, and soon. These files contain a rough skeleton that you can customize and expand tocreate your application.

Running the SkeletonIf you like, choose Run, Run.The program displays an all-black screen, and you canexit by tapping the simulated Home key.The standard project skeleton does little morethan launch an empty window. By default, projects always compile for and launch inthe simulator.

You tell Xcode whether to build your application for the Leopard-based simulator orfor the iPhone by choosing Project, Set Active SDK.You cannot at the time of writingload your software onto an iPhone without a developer certificate.The iPhone DeveloperProgram starts at $99/year and offers access to the on-iPhone App Store.To apply for theprogram, visit http://developer.apple.com/iphone/program.

Customize the iPhone ProjectThe following steps convert the standard skeleton into your Hello World project:

1. Remove classes. Select the Classes folder in the Groups & Files column on theleft and press Delete. Xcode asks you to confirm. Click Also Move to Trash.

2. Remove the .xib file. You won’t want to use it for this project.

3. Remove two lines from the Info.plist file. These are the lines with theNSMainNibFile key and the line that immediately follows after, the MainWindowstring.

4. Add an Images folder. Right-click Hello World in the Groups & Files columnand choose Add, New Group. Name the new folder Pictures.

5. Add images to the project. Drag the three images from the Chapter One proj-ect folder provided with this book onto the Pictures folder: Icon.png, Default.png,and helloworld.png. Check Copy Items into Destination Group’s Folder (if needed)and click Add.

Page 48: I Phone Developer Cookbook

6. Replace main.m. Open main.m and replace all the text in that file with the textfrom the main.m file provided in the Chapter One project folder. Save yourchanges with File, Save (Command-S) and close the main.m window.

7. Select the iPhone Simulator. Choose Project, Set Active SDK, iPhoneSimulator.

8. Run it. Choose Build, Build & Run (Command-R).The program will compileand launch in the iPhone simulation window.

Here are a few how-tos you’ll want to know about while running a simulated iPhoneapplication the first time:

n Reorienting the screen. With the Hello World application running, you canreorient the Simulator using Command-Left arrow and Command-Right arrow.

n Going home. Tap the Home button (Command-Shift-H) to leave the applicationand return to the SpringBoard home screen.

n Locking the “phone.” Hardware, Lock (Command-L) simulates locking thephone.

n Viewing the console. In Xcode, choose Run, Console (Command-Shift-R) toview the Xcode console window.This window is where your printf, NSLog, andCFShow messages are sent by default. In the unlikely case where NSLog messagesdo not appear, use the Xcode organizer (Window, Organizer, Console) or open/Applications/Utilities/Console to view NSLog messages instead.

n Running Instruments. Use Run, Start with Performance Tool to select anInstruments template (such as Object Allocations or CPU Sampler) to use with theSimulator. Instruments monitors memory and CPU use as your application runs.

NoteAlthough the Organizer window offers direct access to crash logs, you’ll probably want to enable Developer Mode for Crash Reporter, too. This enables you to see tracebacks immediately, without having to click Report all the time. To do this, open/Developer/Applications/Utilities/CrashReporterPrefs, select Developer, and quit. (Tip iscourtesy of Emanuele Vulcano of infinite-labs.net.)

Editing Identification InformationThe default Info.plist file enables you to create a working program but won’t, in itsdefault state, properly handle iPhone provisioning, a necessary precondition for on-phone application testing and distribution, which is discussed later in this chapter.TheiPhone requires a signed identity, which you generate on Apple’s iPhone developer programsite (http://developer.apple.com/iphone/program). For Simulator use only, you can dou-ble-click the Info.plist file to open it in a new window. Here, you’ll see the applicationidentifier for your program, and you can edit just the company name entry.

25Building Hello World

Page 49: I Phone Developer Cookbook

Customization does not end with the Info.plist file. Close the Info and double-click InfoPlist.strings.This file is listed among your other project files. Edit "©__MyCompanyName__, 2008" to match the actual copyright you want to use for yourprogram.Again choose File, Save (Command-S) to save your changes.

Using the DebuggerXcode’s integrated debugger provides a valuable tool for iPhone application development.The following walk-through shows you where the debugger is and provides a few basicways to use it with your program. In these steps, you’ll discover how to set breakpointsand use the debugger console to inspect program details.

Set a BreakpointLocate the loadView method in your Hello World program. Click in the leftmostXcode window column, just to the left of the closing bracket.A blue breakpoint indicatorappears (see Figure 1-5).The dark blue color means the breakpoint is active.Tap once todeactivate—the breakpoint turns light blue—and once more to reactivate.You can removebreakpoints by dragging them offscreen and add them by clicking in the column, next toany line of code.

26 Chapter 1 Introducing the iPhone SDK

Figure 1-5 Blue breakpoint indicators appear in the leftmost Xcode window column.

Run the ProgramMake sure the breakpoint is dark blue and that the button at the top of the Xcodescreen says “Deactivate” (which means that the breakpoint is active) and run the program.The program will automatically stop when it hits the breakpoint and a red arrow pointappears on top of the blue arrow.

Open the Debugger WindowsChoose Run, Debugger (Command-Shift-Y) to open the main debugger and Run,Console (Command-Shift-R) to open the console window. Figure 1-6 shows both windows.The debugger provides a graphical front end for inspecting program objects.The console offers a standard Gnu Debugger (gdb) command line.

Page 50: I Phone Developer Cookbook

InspectOnce stopped at the breakpoint, you can use the interactive debugger or the gdb commandline to inspect objects in your program. For this example, navigate down the variablechain through Arguments, self, contentView, UIView,_viewFlags to confirm that theautoresizeMask has been properly set.Assuming Apple has not changed its constants, thismask should be 18, corresponding to 2 for flexible width and 16 for flexible height.

Consider Other Breakpoint OptionsThe breakpoint menu at the top of the Xcode window enables you to add customizedbreakpoint actions rather than use a generic break in execution. Figure 1-7 shows theseoptions.To learn more about the Xcode debugger and its features, consult Apple’s XcodeUser Guide: Debugging Programs.

27Building Hello World

Figure 1-6 Xcode’s graphical debugger enables you to interactively inspect program state. A command-line version of gdb runs concurrently in the console window,as shown by the (gdb) prompt. A red arrow appears at the active breakpoint.

Figure 1-7 Use predefined breakpoint actions to automatically execute debugger actions.

Page 51: I Phone Developer Cookbook

Apple’s iPhone Developer ProgramYou cannot run programs directly on the iPhone until you’ve entered Apple’s (paid)iPhone developer program.The program starts at $99 and allows you to develop iPhoneapplications as an individual ($99/year), a company ($99/year), or for in-house enterprise($299/year).Apply at http://developer.apple.com/iphone/program.

The iPhone uses code signing. Only properly signed applications are allowed to runon the iPhone. Once accepted into this program, you’ll be able to create a signing cer-tificate that authenticates you as a developer and provision iPhones or iPod touch unitsfor testing.The details for this program, for ad hoc distribution (for small controlledaudiences of users), for the general provisioning process, and for access to Apple’s iPhoneApp Store remain in flux. Check with Apple for up-to-date information.

Development PhonesDevelopment phones and iPods must be registered with Apple and provisioned throughXcode.When you’ve successfully joined the developer program and paid for your access,you’ll be able to use Apple’s program portal to obtain your certificates and register yourunits.The process is fussy. I urge you to follow Apple’s instructions exactly as you createdevelopment and distribution certificates and build provisioning profiles for your applications.

When you first tether your iPhone to your computer using a standard USB cable,Xcode detects your unit (see Figure 1-8). If you want to use your device for develop-ment, confirm that; otherwise click Ignore.A development unit needs to be devotedexclusively to development. Using a device as a development unit means that it is subjectto onboard data changes and might no longer work reliably as a field unit.

28 Chapter 1 Introducing the iPhone SDK

Figure 1-8 Xcode automatically detects new iPhones and iPod touchesand offers to add them as development devices.

Page 52: I Phone Developer Cookbook

Application IdentifiersApple uses two kinds of provisions: development provisions and distribution ones.Development provisioning allows you to download software to your iPhone for testing.Distribution provisions create applications suitable for App Store or ad hoc distribution.The iPhone lists its active provisions in Settings, General, Profile.

To distribute applications on the iPhone,Apple requires that you register ApplicationIDs at their site.To do this, you must be a registered team administrator. Use theDeveloper Program portal at Apple’s iPhone Dev Center. Use Program Portal,App IDsto enter the new ID, and then create a provisioning profile at Program, Provisioning.

To develop applications, you can use specific application identifiers for on-phone testingor, better, create a single reverse domain wildcard com.mydomain.* application identifierthat provisions all your applications for testing.This wildcard provision must match eachapplication ID in your Info.plist file. I personally use com.sadun.*. Pick a reversedomain that describes your business or individual identity.

After creating the wildcard provision at the program portal, download the new develop-ment provisioning profile. Back in Xcode, do the following:

1. Drag the mobileprovision file into your organizer.Alternatively, you can drag itonto the Xcode application icon.

2. Select Debug or Release from your project window.

3. In your project window’s Groups & Files list, open Targets and double-click yourapplication name.A Target Info dialog opens.

4. Select Debug or Release in the Target Info window, matching whatever youselected in the project window.

5. Click the Properties tab. Enter your new application ID into the Identifier field.If you provisioned with a wildcard, enter the actual application name, not the wild-card (for example,com.mydomain.myapplication and not com.mydomain.*).

6. Click the Build tab. Set your Code Signing Identity to Any iPhone OS Deviceand iPhone Developer.This phrase is used to match the development certificate in your keychain.To check your keychain, open /Applications/Utilities/KeychainAccess.The category My Certificates should contain at least one certificate con-taining iPhone Developer in its name. If you’ve signed up for distribution, you’lllikely have an iPhone distribution certificate, too.

7. Set your Code Signing Provision Profile to Any iPhone OS Device, and the nameof the provision you just created for your application ID from the pop-up menu.(Do not select Default Provisioning Profile for Code Signing Identity.Your appli-cation will neither load to nor run on the iPhone.)

After following these steps, you should be able to compile your application for theiPhone (Project, Set Active SDK, Device—iPhone OS), install it, and run it from thedevice itself. If you run into problems, consult Apple’s documentation and DeveloperTechnical Support.

29Apple’s iPhone Developer Program

Page 53: I Phone Developer Cookbook

From Xcode to Your iPhone: The OrganizerInterfaceOnce added to Xcode, manage your development units using Xcode’s Organizer tool(Window, Organizer; Control-Command-O).This window (shown in Figure 1-9) formsthe control hub for access between your development computer and your iPhone or iPodtestbed.This window allows you to add and remove applications, view midtest consoleresults, examine crash logs, and snap screenshots of your unit while testing your code.Here’s a quick rundown of the major features available to you through the Organizerconsole.

30 Chapter 1 Introducing the iPhone SDK

Figure 1-9 The Xcode-based iPhone Organizer window (Window,Organizer) provides a single control hub for most of your application testingneeds. Here, you can load firmware, install and remove applications, read

through crash logs, snap device-based screenshots, and more.

Projects and Sources ListKeep your current projects in easy reach by dragging them onto the Organizer console.Once added, double-click the project name to open that project.You can add individualsource files as well as complete projects. Use the Build, Clean, Run, and Action optionsat the top of the Organizer window, to perform even more development tasks directlyfrom the Organizer.

Page 54: I Phone Developer Cookbook

Devices ListThe Devices list shows the name and status of those devices you’ve authorized as develop-ment platforms.The indicators to the right of each name show if the device is attached(green light) or not (red light).

Summary TabThe Summary tab tells you the name, capacity, serial number, and identifier of youriPhone or iPod touch. Here is where you can provision your unit (that is, authorize it towork with the projects you build in Xcode), add and remove applications, and load thelatest firmware.

Each developer license allows you to provision up to five iPhones/iPod touches at atime for testing.The Provisioning list shows a list of application provisions available toyour unit.Add a check to each provision you want to use.The provision determines whichapplications may or may not be run on the device.As a rule, distribution provisions arelisted here (as are the development ones, but they are grayed out).

A list of installed applications appears at the bottom of the Summary tab. Use the +and – buttons to add or remove applications. Open the disclosure triangle next to eachapplication name to disclose the application data (the Documents and Library folders)associated with that application.

NoteApple offers full instructions on how to provision your iPhone and create your personalsigning certificates on its iPhone program portal pages. See http://developer.apple.com/iphone/program.

Console TabUse the console to view system messages from your connected units.This screen showsNSLog() calls as you’re running software on the tethered iPhone. In addition to thedebugging messages you add to your iPhone applications, you’ll also see system notices,device information, and debugging calls from Apple’s system software. It’s basically a text-based mess. Logged data also appears on the Xcode debugging console (Run, Console)along with any printf output.

Crash Logs TabGet direct access to your crash logs by selecting a particular crash (labeled with theiPhone application name and the date and time of the crash) from the screen’s leftmostcolumn.The crash details, including thread information, exception types, and so forth,appear in the text view to the right of that column.

31From Xcode to Your iPhone: The Organizer Interface

Page 55: I Phone Developer Cookbook

Screenshot TabSnap your tethered iPhone’s screen by clicking the Capture button on the Screenshottab.The screenshot feature takes a picture of whatever is running on the iPhone,whether your applications are open. So you can access shots of Apple’s built-in softwareand any other applications running on the iPhone.

Once snapped, you can drag snapped images onto the desktop or save them as anopen project’s new Default.png image.Archival shots appear in a library on the left sideof the window. Select one and press the Delete key to permanently remove it.

NoteScreenshots are stored in your Application Support folder in Developer/Shared/Xcode/Screenshots.

About TetheringAt this time,Apple provides no way to transfer, debug, or monitor applications wirelessly.That means you’ll do nearly all your work tethered over a standard iPhone USB cable.The physical reality of tethered debugging can be problematic. Reasons for this includethe following points:

n When you unplug the cable, you unplug all the interactive debugging, console, andscreenshot features. So you need to keep that cable plugged in all the time.

n You cannot reasonably use the iPhone with a dock. Sure, the dock is stable, buttouching the screen while testing interfaces is extremely awkward when theiPhone is seated at a 75-degree angle.

n The tether comes to the bottom not the top of the unit, meaning it’s very easy tocatch that cable and knock your iPhone to the floor.

Obviously, untethered testing would vastly improve many of these issues. Unfortu-nately,Apple has not yet introduced that option. If you like, you can Rube Goldberg-izeyour iPhone to get around these problems. One solution is to attach Velcro to the backof an iPhone case—a case that leaves the bottom port connector open—and use that tostabilize your iPhone on your desk. It’s ugly, but it keeps your iPhone from gettingknocked to the floor all the time.

Testing Applications on Your iPhoneBefore you can test your application on the iPhone, you must first compile your sourcefor the iPhone’s ARM processor and get that bundle copied over to your unit.Fortunately, Xcode allows you to easily choose whether to compile your iPhone applica-tion for the simulator or for a tethered device. Here are the steps to take:

32 Chapter 1 Introducing the iPhone SDK

Page 56: I Phone Developer Cookbook

1. Tether a provisioned iPhone to your Macintosh.

2. Choose Project, Set Active SDK, Device, and compile just as you did for theSimulator. Make sure you’ve provisioned the application (Target, Build, CodeSigning Identity and Target, Build, Code Signing Provisioning Profile and haveadded a matching project identifier to Target, Properties, Identifier.

3. Compile your project and run it via Run, Go or Run, Run.These menu choicescompile your program, preparing the application for the iPhone.Then they installand run it.

4. Wait. It takes a little time to compile, sign, sandbox, and install your application onan iPhone, especially with bigger project files and especially when you have severalapplications on your iPhone. It’s often a good chance to take a quick break as yourprogram loads.

After you’ve installed the application, it automatically launches, and if you’ve compiledwith the debugger (Project, Set Active Build Configuration, Debug), the Gnu Debugger(gdb) opens in the Xcode debugging console window.The debugging functionalityremains available to you until you quit the application.You can quit either throughXcode (typically by compiling a new version of the program and preparing to install it)or by pressing Home on the iPhone.

Testing on the iPhone is vital.As simple and convenient as the Simulator is, it falls farshort of the mark when it comes to a complete iPhone testing experience.At the timeof writing, you cannot use the Simulator to test the onboard camera (see Chapter 7),keychain access (see Chapter 10), or accelerometer feedback (see Chapter 9,“People,Places, and Things”).And, of course, given that the iPhone is the target platform, it’simportant that your software runs its best on its native system rather than on theSimulator.The iPhone itself offers the fully leaded un-watered-down testing platform, atleast as far as a fully leaded sandboxed application with limited system read-and-writeaccess will allow.

NoteSpringBoard learns about new and removed applications through two system notifications: com.apple.mobile.application_installed andcom.apple.mobile.application_uninstalled. Xcode sends these notificationsto a Darwin notification center, and SpringBoard listens in.

Compiling for DistributionSubmitting applications to App Store requires that you compile your program for distri-bution rather than development. Here’s a quick rundown of the steps you need to take:

1. Create a distribution certificate at the iPhone program portal. Download it to yourMacintosh and add it to your keychain.You need do this only once.After creatinga development or distribution certificate, it stays in your keychain regardless of thenumber of applications you build.

33From Xcode to Your iPhone: The Organizer Interface

Page 57: I Phone Developer Cookbook

2. If you haven’t done so already, register a wildcard application identifier at the pro-gram portal.

3. Create a distribution provisioning profile for that wildcard application identifier.Name it with an easy-to-identify name (for example, My Wildcard DistributionProfile). Download it and add it to Xcode by dropping it onto the Xcode applica-tion icon or into the Organizer window.

4. Open the Project Info window (Command-I). In the Configurations tab, selectRelease. Click Duplicate and rename the new copy to Distribution.This newdistribution configuration will store all the information you need for creating anApp Store–compatible build. Once created, you can switch between the Debug,Release, and Distribution profiles, and they’ll remember their settings. Just beaware that you need to set the configuration in two places: on the main projectwindow and in the Target Info window, as you’re about to see.

5. Close the Project Info window. Select Distribution from the Active BuildConfiguration pop-up in the main project window (Figure 1-1).This is the first oftwo places that you’ll set the build configuration.

6. Open the Target Info window (double-click Targets, Project Name in the main proj-ect window) and select the Build tab.

7. In the Target Info, Build window, select Distribution from the Configuration pop-up.This is the second required configuration setting.

8. Under Code Signing Identity, change iPhone Developer to iPhone Distribution.

9. Select the new wildcard distribution profile from the Code Signing ProvisioningProfile pop-up.

10. Build your project.Then, choose Groups & Files,Project Name, Products, ApplicationName, Reveal in Finder to find the newly created application bundle.

These steps enable you to build a distribution version of your program.After buildingyour project, you can then zip up the new bundle and submit it to App Store.

Using Undocumented API CallsOfficially,Apple prohibits the use of undocumented API calls. If you want your applica-tion to conform to the App Store restrictions, restrain from using any unpublished items.Unofficially,Apple’s policy has always been that you can use any API call in publicframeworks as long as you accept the risk that your program may stop working whenApple releases new operating system updates.This unofficial policy currently crosses over to the iPhone. If an item appears in a public framework, you may use it at yourown risk.

That spirit of open access informs this book. Undocumented calls appear throughout,particularly in Chapter 4,“Alerting Users.” Every use is marked.You will not find arecipe that uses an undocumented call without it being stated explicitly.

34 Chapter 1 Introducing the iPhone SDK

Page 58: I Phone Developer Cookbook

At the same time, this book does not cross certain lines.The rules for book samplesare as follows:

n Private frameworks are private. You may link to and access only those publicitems that appear in the Apple-sanctioned Frameworks folder.

n You don’t run daemons. All code executes in the primary application.n You must quit when the iPhone says so. The code respects Apple’s one

application at a time policy.n You cannot use plug-ins or execute arbitrary code. Apple and App Store

approve all code.n The sandbox is the sandbox. Nothing in this book shows you how to access

private areas such as the onboard iTunes Media folder.

Courteous computing doesn’t mean limited computing. If you’d rather avoid undocu-mented API calls—and I know a large number of readers will—the remaining majorityof tricks and tips throughout this book will still allow you to add excitement and noveltyinto your iPhone development experience.

Ad Hoc DistributionApple allows you to distribute your applications outside the App Store via Ad Hoc dis-tribution.With Ad Hoc, you can send your applications to up to 100 registered devicesand run those applications using a special kind of mobile provision that allows the appli-cations to execute under the iPhone’s FairPlay restrictions.Ad Hoc distribution is espe-cially useful for beta testing and for submitting review applications to news sites andmagazines.

Start by registering your device. Use the iPhone Developer Program Portal to registerdevice identifiers (Program Portal, Devices). Recover these identifiers from the iPhonedirectly (use the UIDevice calls from Chapter 9), from iTunes (click on the word SerialNumber in the iPhone’s summary tab), or from System Profiler (select USB, iPhone,Serial Number). Enter the identifier and a unique username.

Next, build your provision.To build a mobile provision, select Program Portal,Provisioning, Distribution. Click Add Profile. Select Ad Hoc, enter a profile name, yourstandard wildcard Application identifier (for example, com.yourname.*), and select thedevice or devices to deploy on. Click Submit and wait for Apple to build the new mobileprovision. Download the provision file. Drop it onto Xcode.You will use it to build yourapplication.

You need to include a special entitlement file in Ad Hoc projects. In Xcode, chooseFile, New File, Code Signing, Entitlements. Click Next. Create a new entitlement calleddist.plist. Click Finish.The entitlement property list appears in the Products folder.Double-click it to open and uncheck get-task-allow. (That is, set it to a Booleanvalue of FALSE.)

35Ad Hoc Distribution

Page 59: I Phone Developer Cookbook

After setting up your entitlement, you need to add it to your target settings. Selectyour Distribution configuration from the project window. Double-click the project target, (Targets > Your Project Name).The Target Info window opens. In the Build tab,set your configuration again to Distribution if it is not already set. Double-click CodeSigning Entitlements.Add the filename dist.plist to the Code Signing Entitlement andclick OK.

Now you’re ready to build your application. Make sure your Code Signing Identity is set to iPhone Distribution. Select your new ad-hoc mobile provision from the AnyiPhone OS Device pop-up. Select Build, Clean (Command-Shift-K) and then Build,Build (Command-B) your project. Select the newly compiled product from the Productsfolder in the project window. Right-click it and choose Reveal in Finder.A Finder window opens, showing the compiled item.

Distribute a copy of this application, which you just compiled with the mobile adhoc provision, along with the provision itself that you downloaded from Apple.Your usercan drop the provision and the application into iTunes before syncing your applicationto his or her iPhone.The application will run only on those phones you registered,providing a secure way to distribute these apps directly to your user.

SummaryThis chapter has introduced you to the iPhone application, how it is built, and how youcan build your own basic Hello World utility using just a few steps. Here are some thingsyou may want to take away with you before leaving this chapter:

n Downloading and installing the SDK and building and testing your first applicationshouldn’t take you more than a few hours to do based on your download speed.The SDK is over a gigabyte in size, but once downloaded it’s really easy to createyour first application.

n The iPhone application bundle is much simpler and less structured than itsMacintosh brother, although it shares many common features, including Info.plistand lproj folders.

n Unfortunately, the iPhone SDK requires you to test only on a tethered device.WiFi testing is not yet available. Despite that, the Organizer in Xcode provides asuperb organizer for testing your applications with its console, crash logs, andscreenshots.

n If you come from a Cocoa background, you’ll be prepared if not overprepared tocreate iPhone applications. Familiarity with Objective-C and Cocoa best practiceswill put you on a firm development footing.

36 Chapter 1 Introducing the iPhone SDK

Page 60: I Phone Developer Cookbook

2Views

Pretty much everything that appears on the iPhone’s screen is a view.Views act like little canvases that you can draw on with colors, pictures, and buttons.You can drag themaround the screen.You can resize them.You can layer them. In this chapter, you discoverhow to design and build screen content using Cocoa Touch and UIViews.You learnabout view hierarchy, geometry, and animation, and find out how to combine eventfeedback from UITouches into meaningful UIView responses.There’s so much thatUIViews can do that a single chapter has no hope of covering the entire class with thethoroughness it deserves. Instead, this chapter introduces essential functionality andrecipes that you can use as a starting point for your own UIView exploration.

UIView and UIWindowThe iPhone rule goes like this: one window, many views. If you keep that idea in mind,the iPhone interface design scenario simplifies. Metaphorically speaking, UIWindow is theTV set, and UIViews are the actors on your favorite show.They can move around thescreen, appear, and disappear, and may change the way they look and behave over time.

The TV set, on the other hand, normally stays still. It has a set screen size that doesn’tchange even if the virtual world you see through it is practically unlimited.You mayeven own several TVs in the same household (just like you can create several UIWindowinstances in the same application), but you can watch just one at a time.

UIViews are GUI building blocks.They provide visual elements that are shownonscreen and invite user interaction. Every iPhone user interface is built from UIViewsdisplayed within one UIWindow, which is itself a specialized kind of UIView.The win-dow acts a container; it is the root of the display hierarchy. It holds all the visible applica-tion components within itself.The following sections will give you just a taste of thekind of ways you can control and manipulate views, their hierarchy, and their geometry.

HierarchyA tree-based hierarchy orders what you see on your iPhone screen. Starting with themain window, views are laid out in a specifically hierarchical way.All views may have

Page 61: I Phone Developer Cookbook

children, called subviews. Each view, including the root window, owns an ordered list ofthese subviews.Views might own many subviews; they might own none.Your applicationdetermines how views are laid out and who owns whom.

Subviews display onscreen in order, always from back to front.And because the iPhonesupports view transparency, this works exactly like a stack of animation cells—those trans-parent sheets used to create cartoons. Only the parts of the sheets that have been paintedare shown.The clear parts allow any visual elements behind that sheet to be seen.

Figure 2-1 shows a little of the layering used in a typical window. Here you see thewindow that owns a UINavigationController-based window.The window (repre-sented by the clear, rightmost element) owns a Navigation subview, which in turn ownstwo subview buttons (one left and one right) and a table.These items stack together tobuild the GUI.

38 Chapter 2 Views

Figure 2-1 Adding subview hierarchiesallows you to build complex GUIs.

Notice how the buttons appear over the navigation bar and how the table is sized sothat it won’t obscure either the buttons or bar.The button frames are small, taking upvery little space onscreen.The table frame is large, occupying the majority of screenspace. Here are some ways you can manage subviews in your programs:

n To add a subview, use a call to [parentView addSubview:child]. Newly addedsubviews are always frontmost on your screen.

n Query any view for its children by asking it for [parentView subviews].Thisreturns an array of views, ordered from back to front.

Page 62: I Phone Developer Cookbook

n Remove a subview from its parent with [childView removeFromSuperview].n Reorder subviews using [parentView exchangeSubviewAtIndex:iwithSubviewAtIndex:j]. Move subviews to the front or back usingbringSubviewToFront: or sendSubviewToBack:.

n Tag your subviews using setTag:.This identifies views by tagging them with anumber. Retrieve that view from the child hierarchy by calling viewWithTag: onthe parent.

NoteYou can tag any instance that is a child of UIView, including windows and controls. So ifyou have many onscreen buttons and switches, for example, add tags so that you can tellthem apart when users trigger them.

Geometry and TraitsEvery view uses a frame to define its boundaries.The frame specifies the outline of theview: its location, width, and height.You define the frame rectangle using Core Graphicsstructures. For frames, this usually means a CGRect rectangle made up of an origin (a CGPoint, x and y) and a size (a CGSize, width and height). Here are some quick factsabout these types.

CGRectThe CGRect structure defines an onscreen rectangle. It contains an origin (rect.origin)and a size (rect.size).These are CGRect functions you’ll want to be aware of:

n CGRectMake() method()(origin.x, origin.y, size.width,

size.height) Defines rectangles in your code.n NSStringFromCGRect()converts() method() A CGRect structure to a format-

ted string.n CGRectFromString()recovers() method()A rectangle from its string repre-

sentation.n CGRectInset() enables() method()You to create a smaller or larger a rectangle

that’s centered on the same point. Use a positive inset for smaller rectangles, nega-tive for larger ones.

n CGRectIntersectsRect() lets() method()You know whether rectangle struc-tures intersect. Use this function to know when two rectangular onscreen objectsoverlap.

n CGRectZero is() method()A rectangle constant located at (0,0) whose width andheight are zero.You can use this constant when you’re required to create a frame butyou’re still unsure what that frame size or location will be at the time of creatione.

CGPoint and CGSizePoints refer to locations defined with x and y coordinates; sizes have width and height.Use CGPointMake() method (x, y) to create points. CGSizeMake(width, height)

39UIView and UIWindow

Page 63: I Phone Developer Cookbook

creates sizes.Although these two structures appear to be the same (two floating-pointvalues), the iPhone SDK differentiates between them. Points refer to locations. Sizes referto extents.You cannot set myFrame.origin to a size.

As with rectangles, you can convert them to and from strings:NSStringFromCGPoint(), NSStringFromCGSize(), CGSizeFromString(), andCGPointFromString() perform these functions.

Defining LocationsYou can define a view’s location by setting its center (which is a CGPoint) or bounds(CGRect). Unlike the frame, a view’s bounds reflect the view’s frame in its own coordi-nate system. In practical terms, that means the origin of the bounds is (0.0, 0.0), and itssize is its width and height.

When you want to move or resize a view, update its frame’s origin, center, or size.Youdon’t need to worry about things such as rectangular sections that have been exposed orhidden.The iPhone takes care of the redrawing.This lets you treat your views like tangi-ble objects and delegate the rendering issues to Cocoa Touch. For example

[myView setFrame:CGRectMake(0.0f, 50.0f, mywidth, myheight)];

TransformsStandard Core Graphics calls transform views in real time. For example, you can applyclipping, rotation, or other 2D geometric effects. Cocoa Touch supports an entire suite ofaffine transforms (translate, rotate, scale, skew, and so on).The drawRect: method forany UIView subclass provides the entry point for drawing views through low-level CoreGraphics calls.

NoteWhen calling Core Graphics functions, keep in mind that Quartz lays out its coordinate system from the bottom left, whereas UIViews have their origin at the top left.

Other View TraitsIn addition to the physical screen layout, you can set the following view traits amongothers:

n Every view has a translucency factor (alpha) that ranges between opaque andtransparent.Adjust this by issuing [myView setAlpha:value], where the alphavalues falls between 0.0 (fully transparent) and 1.0 (fully opaque).

n You can assign a color to the background of your view. [myViewsetBackgroundColor:[UIColor redColor]] colors your view red.

View LayoutFigure 2-2 shows the layout of a typical iPhone application screen. For current releases ofthe iPhone, the screen size is 320x480 pixels in portrait mode, 480x320 pixels in landscape.At the top of the screen, whether in landscape or portrait mode, a standard status bar

40 Chapter 2 Views

Page 64: I Phone Developer Cookbook

occupies 20 pixels of height.To query the status bar frame, call [[UIApplicationsharedApplication] statusBarFrame].

If you’d rather free up those 20 pixels of screen space for other use, you can hide the status bar entirely. Use this UIApplication call: [UIApplication sharedApplication]setStatusBarHidden:YES animated:NO].Alternatively, set the UIStatusBarHiddenkey to <true/> in your application Info.plist file.

To run your application in landscape-only mode, set the status bar orientation tolandscape. Do this even if you plan to hide the status bar (that is, [[UIApplicationsharedApplication] setStatusBarOrientation:

UIInterfaceOrientationLandscapeRight]).This forces windows to display side toside and produces a proper landscape keyboard.

The UIScreen object acts as a stand in for the iPhone’s physical screen ([UIScreenmainScreen]).The screen object maps view layout boundaries into pixel space. Itreturns either the full screen size (bounds) or just the rectangle that applies to yourapplication (applicationFrame).This latter takes the size of your status bar and, if used,any toolbars/navigation bars into account.

By default, UINavigationBar, UIToolbar, and UITabBar objects are 44 pixels inheight each. Use these numbers to calculate the available space on your iPhone screenand lay out your application views when not using Interface Builder’s layout tools.

41UIView and UIWindow

Figure 2-2 On current generations of the iPhone, the status bar is20 pixels high, often followed below by a 44-pixel-high navigation bar. If youuse a toolbar at the bottom of your screen, that will also occupy 44 pixels.It helps to use Photoshop or some other image layout program to design

your screens taking these geometries into account.

Page 65: I Phone Developer Cookbook

GesturesViews intercept user touches.This integration between the way things look and the waythings react enables you to add a meaningful response to taps, drags, and what have you.Adding touch handlers like touchesBegan: withEvent: to your views allows you tointercept user touches, determine the phase of the touch (the equivalent of mouse down,mouse dragged, mouse up), and produce feedback based on those touches.

The UITouch class tells you where the event took place (locationInView:) and thetap count (tapCount), which is vital for distinguishing between single- and double-taps.Several recipes in this chapter demonstrate how to use these gesture responses and howto integrate view geometry and hierarchy into your applications for enticing, layered,direct-manipulation interfaces.

Recipe: Adding Stepwise SubviewsExpand your view hierarchy by calling addSubview:.This adds a subview to some otherview. Recipe 2-1 shows a simple UIViewController’s loadView method that defines aseries of stepped subviews. It demonstrates the basics of allocating, framing, and adding views.

These subviews are not nested, in that they all belong to the same parent.They’reindented so that you can see them all at once.The indentation uses the handyCGRectInset() function. Pass it a rectangle (using the CGRect structure) and twoinsets—horizontal and vertical—and it returns the inset, centered rectangle. Here, eachsubview is inset from its parent or sibling’s frame by 32 pixels on each side.

In their simplest form, views are little more than transparent placeholders. Coloringthe view backgrounds distinguishes one view from another in the absence of meaningfulcontent (see Figure 2-3). It’s a useful trick when trying to test layouts before committingto an actual design.

Always keep your coordinate system in mind.When working with view hierarchy,you must define a view’s frame in its parent’s coordinate system.The example in Recipe 2-1 requests the application frame to lay out the main view and then resets itsorigin to (0, 0). Resetting the origin updates the frame from the screen’s to the mainview’s coordinate system.This reset forms the basis for the view layout that follows.

Recipe 2-1 Adding Nested Subviews

- (void)loadView

{

// Create the main view

CGRect appRect = [[UIScreen mainScreen] applicationFrame];

contentView = [[UIView alloc] initWithFrame:appRect];

contentView.backgroundColor = [UIColor whiteColor];

// Provide support for autorotation and resizing

contentView.autoresizesSubviews = YES;

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

42 Chapter 2 Views

Page 66: I Phone Developer Cookbook

Recipe 2-1 Continued

self.view = contentView;

[contentView release];

// reset the origin point for subviews. The new origin is 0,0

appRect.origin = CGPointMake(0.0f, 0.0f);

// Add the subviews, each stepped by 32 pixels on each side

UIView *subview = [[UIView alloc] initWithFrame:CGRectInset(appRect, 32.0f,➥32.0f)];

subview.backgroundColor = [UIColor lightGrayColor];

[contentView addSubview:subview];

[subview release];

subview = [[UIView alloc] initWithFrame:CGRectInset(appRect, 64.0f, 64.0f)];

subview.backgroundColor = [UIColor darkGrayColor];

[contentView addSubview:subview];

[subview release];

subview = [[UIView alloc] initWithFrame:CGRectInset(appRect, 96.0f, 96.0f)];

subview.backgroundColor = [UIColor blackColor];

[contentView addSubview:subview];

[subview release];

}

43Recipe: Adding Stepwise Subviews

Figure 2-3 The code in Recipe 2-1 defines a UIView controller’s main view with three colored, nested subviews.

Page 67: I Phone Developer Cookbook

ReorientingExtending Recipe 2-1 to enable orientation changes takes thought.You cannot just swapeach subview’s heights and widths as you might assume.That’s because the shapes of horizontal and vertical applications on the iPhone use different aspect ratios.Assuming a20-pixel status bar, portrait view areas are 320 pixels wide by 460 pixels high; landscapesare 480 pixels wide by 300 pixels high (refer to Figure 2-2).This difference throws offinterfaces that depend solely on rotation to reorient.

To rotate this example, add code that distinguishes landscape orientations from portraitones and adjust the frames accordingly.This is shown in Recipe 2-2.

Avoid reorientation schemes that rely on toggling (for example,“I was just in portraitmode so, if the orientation changed, I must be in landscape mode.”) It’s entirely possibleto switch from left-landscape to right-landscape without hitting a portrait state in-between. Orientation is all about sensors and feedback, and the iPhone is not guaranteedto catch any middle state between two orientations. Fortunately, UIKit provides aUIViewController callback that alerts you to new orientations and that specifies whatthat orientation will be.

Recipe 2-2 Adding Reorientation Support to the Preceding Subview Example

- (void)willRotateToInterfaceOrientation:

(UIInterfaceOrientation)orientation

duration:(NSTimeInterval)duration {

CGRect apprect;

apprect.origin = CGPointMake(0.0f, 0.0f);

// adjust the frame size based on actual orientation

if ((orientation == UIInterfaceOrientationLandscapeLeft) || ➥(orientation == UIInterfaceOrientationLandscapeRight))

apprect.size = CGSizeMake(480.0f, 300.0f);

else

apprect.size = CGSizeMake(320.0f, 460.0f);

// resize each subview accordingly

float offset = 32.0f;

for (UIView *subview in [contentView subviews]) {

CGRect frame = CGRectInset(apprect, offset, offset);

[subview setFrame:frame];

offset += 32.0f;

}

}

// Allow the view to respond to iPhone Orientation changes

-(BOOL)shouldAutorotateToInterfaceOrientation:

(UIInterfaceOrientation)interfaceOrientation

44 Chapter 2 Views

Page 68: I Phone Developer Cookbook

Recipe 2-2 Continued

{

return YES;

}

Recipe: Dragging ViewsCocoa Touch simplifies direct view manipulation.When dealing with many onscreenviews, the iPhone takes charge of deciding which view the user touched and passes anytouch events to the proper view for you.This helps you write concrete direct-manipulationinterfaces where users touch, drag, and interact with onscreen objects.

Recipe 2-3 centers on touches in action.This example creates a child ofUIImageView called DragView that enables users to drag the view around the iPhonescreen. Being an image view, it’s important to enable its user interaction, via [draggersetUserInteractionEnabled:YES].This holds true for backdrops as well as direct-interaction views.Whenever working with UIImageView in direct-manipulation interfaces,make sure to enable interaction, no matter what role in the view hierarchy.With imageviews, the user interaction toggle affects all the view’s children as well as the view itself.

When a user first touches any DragView (see the flowers in Figure 2-4), the objectstores the start location as an offset from the view’s origin.As the user drags, the viewmoves along with the finger—always maintaining the same origin offset so that themovement feels natural.

45Recipe: Dragging Views

Figure 2-4 The code in Recipe 2-3 creates an interface with 16 flowers that can be dragged around the iPhone screen.

Page 69: I Phone Developer Cookbook

NoteThe way the example in Figure 2-4 is built, you can use multiple fingers to drag more thanone flower around the screen at once. It’s not multitouch per se because each flower(UIView) responds to only one touch at a time. A discussion of true multitouch interactionfollows later in this chapter.

Touching an object also does one more thing in this code: It pops that object to thefront of the parent view.This means any dragged object always floats over any otherobject onscreen. Do this by telling the view’s parent (its superview) to bring the viewto its front.

UITouchThe UITouch class defines how fingers move across the iPhone screen.Touches are sentwhile invoking the standard began, moved, and ended handlers.You can also query userevents (of the UIEvent class) to return touches affecting a given view throughtouchesForView: and touchesForWindow:.These calls return an unordered set(NSSet) of touches.

NoteSend allObjects to any NSSet to return an array of those objects.

A touch tells you several things: where the touch took place (both the current andmost recent previous location), what stage of the touch was used (essentially mousedown, mouse moved, mouse up), a tap count (for example, single-tap/double-tap), whenthe touch took place (through a time stamp), and so forth.

For nonmultitouch interaction styles, assume that you’re dealing with a single touchat any time.The code in Recipe 2-3 recovers the first available touch for each event bycalling anyObject on the returned touch set.

Recipe 2-3 Building Multiple Draggable Views

/*

* DragView: Draggable views

*/

@interface DragView : UIImageView

{

CGPoint startLocation;

}

@end

@implementation DragView

// Note the touch point and bring the touched view to the front

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

46 Chapter 2 Views

Page 70: I Phone Developer Cookbook

Recipe 2-3 Continued

{

CGPoint pt = [[touches anyObject] locationInView:self];➥startLocation = pt;

[[self superview] bringSubviewToFront:self];

}

// As the user drags, move the flower with the touch

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event

{

CGPoint pt = [[touches anyObject] locationInView:self];

CGRect frame = [self frame];

frame.origin.x += pt.x - startLocation.x;

frame.origin.y += pt.y - startLocation.y;

[self setFrame:frame];

}

@end

/*

* Hello Controller: The primary view controller

*/

@interface HelloController : UIViewController{

UIView *contentView;

}

@end

@implementation HelloController

#define MAXFLOWERS 16

CGPoint randomPoint() {return CGPointMake(random() % 256, random() % 396);}

- (void)loadView

{

// Create the main view with a black background

CGRect apprect = [[UIScreen mainScreen] applicationFrame];

contentView = [[UIView alloc] initWithFrame:apprect];

contentView.backgroundColor = [UIColor blackColor];

self.view = contentView;

[contentView release];

// Add the flowers to random points on the screen

for (int i = 0; i < MAXFLOWERS; i++)

{

47Recipe: Dragging Views

Page 71: I Phone Developer Cookbook

Recipe 2-3 Continued

CGRect dragRect = CGRectMake(0.0f, 0.0f, 64.0f, 64.0f);

dragRect.origin = randomPoint();

DragView *dragger = [[DragView alloc] initWithFrame:dragRect];

[dragger setUserInteractionEnabled:YES];

// select random flower color

NSString *whichFlower = [[NSArray arrayWithObjects:@"blueFlower.png",➥@"pinkFlower.png", @"orangeFlower.png", nil] objectAtIndex:(random() %➥3)];

[dragger setImage:[UIImage imageNamed:whichFlower]];

// add the new subview

[contentView addSubview:dragger];

[dragger release];

}

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

Adding PersistencePersistence represents a key iPhone design touch point.After users leave a program,Apple strongly recommends that they return to a state that matches as closely to wherethey left off as possible.Adding persistence to this sample code involves several steps:

1. Storing the data

2. Resuming from a saved session

3. Providing a startup image that matches the last session

Storing StateEvery view knows its position because you can query its frame.This enables you torecover and store positions for each onscreen flower.The flower type (green, pink, orblue) is another matter. For each view to report its current flower, the DragView classmust store that value, too.Adding a string instance variable enables the view to returnthe image name used. Listing 2-1 shows the extended DragView class definition.

Listing 2-1 The Updated DragView Class Includes a String to Store the Flower Type

@interface DragView : UIImageView

{CGPoint startLocation;

48 Chapter 2 Views

Page 72: I Phone Developer Cookbook

Listing 2-1 Continued

NSString *whichFlower;

}

@property (nonatomic, retain) NSString *whichFlower;

@end

Adding this extra variable enables the HelloController class to store both a list ofcolors and a list of locations to its defaults file.A simple loop collects both values fromeach draggable view and then stores them. Listing 2-2 presents an updateDefaultsmethod, as defined in HelloController.This method saves the current state to disk. Itshould be called in the application delegate’s applicationWillTerminate: method,just before the program ends.

Notice the use here of NSStringFromCGRect(). It provides a tight way to storeframe information as a string.To recover the rectangle, issue CGRectFromString().Each call takes one argument: a CGRect in the first case, an NSString * in the second.The UIKit framework provides calls that translate points and sizes as well as rectangles toand from strings.

Defaults, as you can see, work like a dictionary. Just assign an object to a key and theiPhone “automagically” updates the preferences file associated with your application ID.Your application ID is defined in Info.plist. Defaults are stored in Library/Preferencesinside your application’s sandbox. Calling the synchronize function updates thosedefaults immediately instead of waiting for the program to terminate.

Listing 2-2 Storing Flower Locations via User Defaults

// Collect all the colors and locations and save them for the next use

- (void) updateDefaults

{

NSMutableArray *colors = [[NSMutableArray alloc] init];

NSMutableArray *locs = [[NSMutableArray alloc] init];

for (DragView *dv in [contentView subviews]) {

[colors addObject:[dv whichFlower]];

[locs addObject:NSStringFromCGRect([dv frame])];

}

[[NSUserDefaults standardUserDefaults] setObject:colors forKey:@"colors"];

[[NSUserDefaults standardUserDefaults] setObject:locs forKey:@"locs"];

[[NSUserDefaults standardUserDefaults] synchronize];

[colors release];

[locs release];

}

49Recipe: Dragging Views

Page 73: I Phone Developer Cookbook

Recovering StatePersistence awareness generally resides in the view controller’s init or loadView (forexample, before the view actually appears).These methods should find any previous stateinformation and, for this example, match the flowers to that state.When querying userdefaults, this code checks whether state data is unavailable (for example, the valuereturned is nil).When state data goes missing, the method creates random flowers at ran-dom points. Listing 2-3 shows a state-aware version of loadView.

NoteWhen working with large data sources, you may want to initialize and populate your savedobject array in the UIViewController‘s init method, and then draw them in loadView.Where possible, use threading when working with many objects to avoid blocking.

Listing 2-3 Checking for Previous State

- (void)loadView

{

// Create the main view

CGRect apprect = [[UIScreen mainScreen] applicationFrame];

contentView = [[UIView alloc] initWithFrame:apprect];

contentView.backgroundColor = [UIColor blackColor];

self.view = contentView;

// Attempt to read in previous colors and locations

NSMutableArray *colors, *locs;

colors = [[NSUserDefaults standardUserDefaults] objectForKey:@"colors"];

locs = [[NSUserDefaults standardUserDefaults] objectForKey:@"locs"];

for (int i = 0; i < MAXFLOWERS; i++)

{

// Use a random point unless there’s a previous location

CGRect dragRect = CGRectMake(0.0f, 0.0f, 64.0f, 64.0f);

dragRect.origin = randomPoint();

if (locs && ([locs count] == MAXFLOWERS))➥dragRect = CGRectFromString([locs objectAtIndex:i]);

DragView *dragger = [[DragView alloc] initWithFrame:dragRect];

[dragger setUserInteractionEnabled:YES];

// Use a random color unless there’s a previous color

NSString *whichFlower = [[NSArray arrayWithObjects:@"blueFlower.png",➥@"pinkFlower.png", @"orangeFlower.png", nil] objectAtIndex:(random()➥% 3)];

if (colors && ([colors count] == MAXFLOWERS))➥whichFlower = [colors objectAtIndex:i];

[dragger setWhichFlower:whichFlower];

[dragger setImage:[UIImage imageNamed:whichFlower]];

50 Chapter 2 Views

Page 74: I Phone Developer Cookbook

Listing 2-3 Continued

// Add the subview

[contentView addSubview:dragger];

[dragger release];

}

}

Startup ImageApple has not yet included persistence screenshot capabilities into its official SDKrelease, although the functionality is partially available in the UIKit framework as anundocumented call.To access the _writeApplicationSnapshot feature shown inListing 2-4, you must add it by hand to the UIApplicationClass interface. Onceadded, you can build a cached shot of your screen before ending the application.

NoteSee Chapter 1, “Introducing the iPhone SDK,” for further discussion about using undocumented calls and features in your programs.

The idea is this:When you leave the application, you snap a picture of the screen.Then when your application starts up (presumably returning you to the same state youleft with), the cached image acts as the Default.png image, giving the illusion that you’rejumping directly back without any startup sequence.

Apple has yet to enable this feature with the iPhone SDK, and at the time of writing,applications cannot check in to find updated snapshots. Hopefully,Apple will providethis functionality in a future firmware release.

Listing 2-4 Screenshotting Before Application Termination

@interface UIApplication (Extended)

-(void) _writeApplicationSnapshot;

@end

[[UIApplication sharedApplication] _writeApplicationSnapshot];

Recipe: Clipped ViewsWhen working with direct-manipulation interfaces, it’s unlikely that you’ll want to dealsolely with rectangular views. Soft borders, rounded corners, and other visual enhance-ments are easily added to UIView instances.

Clipping creates view shapes that fill only part of a view’s frame.You can produceclipping with Core Graphics using the drawRect: method of a UIView object, just as

51Recipe: Clipped Views

Page 75: I Phone Developer Cookbook

you would on a Macintosh. Core Graphics enables you to build paths from sourcesincluding points, lines, standard shapes (such as ellipses), and Bézier curves. Clipping yourviews to these paths creates the illusion of nonrectangular onscreen objects. Figure 2-5shows a number of onscreen circular clipped views, clearly overlapping with each other.These views were created by the code shown in Listing 2-5.This code creates a path,performs the clipping, and then draws into the clipped view.

52 Chapter 2 Views

Figure 2-5 Clipping enables you to create nonrectangular views onscreen from rectangular source

material, using rectangular UIView frames.

Listing 2-5 Clipping a View to a Circular Path

- (void) drawRect: (CGRect) aRect

{

CGRect bounds = CGRectMake(0.0f, 0.0f, SIDELENGTH, SIDELENGTH);

// Create a new path

CGContextRef context = UIGraphicsGetCurrentContext();

CGMutablePathRef path = CGPathCreateMutable();

// Add circle to path

CGPathAddEllipseInRect(path, NULL, bounds);

CGContextAddPath(context, path);

Page 76: I Phone Developer Cookbook

Listing 2-5 Continued

// Clip to the circle and draw the logo

CGContextClip(context);

[logo drawInRect:bounds];

CFRelease(path);

}

Balancing Touches with ClippingVisual clipping does not affect how UIViews respond to touches.The iPhone senses usertaps throughout the entire view frame.This includes the undrawn area such as the corners of the frame outside the actual circles of Figure 2-5 just as much as the clippedpresentation.That means that unless you add some sort of hit test, users may attempt totap through to a view that’s “obscured” by the clear portion of the UIView frame.

Listing 2-6 adds a simple hit test to the clipped views, determining whether touchesfall within the clipping path. I implemented circular clipping and circular hit tests toprovide the simplest example. Use any computable test method you like to determinewhether a user touch intersects the view.Add pointInside:withEvent: to yourUIView subclass and return YES when the touch has properly hit your view or NO whenit does not.

Listing 2-6 Checking Circular Views against Touches

- (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 = hypoteneus length

float xsquared = pt.x * pt.x;

float ysquared = pt.y * pt.y;

// If the length < 1, the point is within the clipped circle

if ((xsquared + ysquared) < 1.0) return YES;

return NO;

}

53Recipe: Clipped Views

Page 77: I Phone Developer Cookbook

Accessing Pixel-by-Pixel ValuesThere are many ways to test user touches against views. Listing 2-6 computed whether atouch fell within a circle’s radius.With hit masks and variable transparency images, youcan test against a point’s alpha value.Translucency controls whether you trigger aresponse. Listing 2-7 extends the UIImageView class to add an image’s bitmap represen-tation. It tests touches against alpha values in the bitmap, point by point. Pixels whosealpha levels fall below 0.5 will not respond to touches using this code.

NoteThe code in this listing returns a bitmap context, and its bitmap data is based on Applesample code.

Listing 2-7 Testing Touch Hits Against a Bitmap

// Return a bitmap context using alpha/red/green/blue byte values

CGContextRef CreateARGBBitmapContext (CGImageRef inImage)

{

CGContextRef context = NULL;

CGColorSpaceRef colorSpace;

void * bitmapData;

int bitmapByteCount;

int bitmapBytesPerRow;

size_t pixelsWide = CGImageGetWidth(inImage);

size_t pixelsHigh = CGImageGetHeight(inImage);

bitmapBytesPerRow = (pixelsWide * 4);

bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);

colorSpace = CGColorSpaceCreateDeviceRGB();

if (colorSpace == NULL)

{

fprintf(stderr, "Error allocating color space\n");

return NULL;

}

// allocate the bitmap & create context

bitmapData = malloc( bitmapByteCount );

if (bitmapData == NULL)

{

fprintf (stderr, "Memory not allocated!");

CGColorSpaceRelease( colorSpace );

return NULL;

}

54 Chapter 2 Views

Page 78: I Phone Developer Cookbook

Listing 2-7 Continued

context = CGBitmapContextCreate (bitmapData, pixelsWide, pixelsHigh, 8,➥bitmapBytesPerRow, colorSpace, kCGImageAlphaPremultipliedFirst);

if (context == NULL)

{

free (bitmapData);

fprintf (stderr, "Context not created!");

}

CGColorSpaceRelease( colorSpace );

return context;

}

// Return Image Pixel data as an ARGB bitmap

unsigned char *RequestImagePixelData(UIImage *inImage)

{

CGImageRef img = [inImage CGImage];

CGSize size = [inImage size];

CGContextRef cgctx = CreateARGBBitmapContext(img, size);

if (cgctx == NULL) return NULL;

CGRect rect = {{0,0},{size.width, size.height}};

CGContextDrawImage(cgctx, rect, img);

unsigned char *data = CGBitmapContextGetData (cgctx);

CGContextRelease(cgctx);

return data;

}

// Create an Image View that stores a copy of its image as an addressable bitmap

@interface BitMapView : UIImageView

{

unsigned char *bitmap;

CGSize size;

UIView *colorView;

}

@end

@implementation BitMapView

// Hit test relies on the alpha level of the touched pixel

- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event

{

long startByte = (int)((point.y * size.width) + point.x) * 4;

int alpha = (unsigned char) bitmap[startByte];

return (alpha > 0.5);

}

55Recipe: Clipped Views

Page 79: I Phone Developer Cookbook

Listing 2-7 Continued

-(void) setImage:(UIImage *) anImage

{

[super setImage:anImage];

bitmap = RequestImagePixelData(anImage);

size = [anImage size];

}

@end

Recipe: Detecting MultitouchBy enabling multitouch interaction in your UIViews, the iPhone enables you to recoverand respond to multifinger interaction.This recipe, shown in Recipe 2-4, demonstrateshow to add multitouch to your iPhone applications.

To begin, set multipleTouchEnabled to YES or overrideisMultipleTouchEnabled for your view.This tells your application to poll for morethan one UITouch at a time. Now when you call touchesForView:, the returned setmay contain several touches. Use NSSet’s allObjects method to convert that set intoan addressable NSArray.When the array’s count exceeds one, you know you’re dealingwith multitouch.

In theory, the iPhone could support an arbitrary number of touches. In practice, multi-touch is limited to five finger touches at a time. Even five at a time goes beyond whatmost developers need.There aren’t many meaningful gestures you can make with five fingers at once.This particularly holds true when you grasp the iPhone with one handand touch with the other. Perhaps it’s a comfort to know that if you need to, the extrafinger support has been built in. Unfortunately, when you are using three or more touchesat a time, the screen has a tendency to lose track of one or more of those fingers. It’s hardto programmatically track smooth gestures when you go beyond two finger touches.

Touches are not grouped. If, for example, you touch the screen with two fingers fromeach hand, there’s no way to determine which touches belong to which hand.The touchorder is arbitrary.Although grouped touches retain the same finger order for the lifetimeof a single touch event (down, move, up), the order may change the next time your usertouches the screen.When you need to distinguish touches from each other, build a touchdictionary indexed by the touch objects.

NoteThe drawRect: routine in Recipe 2-4 clears its context each time it is called. Thisremoves previous circles and lines from the display. Comment out this line if you want tosee an event trail.

56 Chapter 2 Views

Page 80: I Phone Developer Cookbook

Recipe 2-4 Visualizing Multitouch

@interface MultiTouchView : UIView{

CGPoint loc1, loc2;

}

@property (nonatomic) CGPoint loc1;

@property (nonatomic) CGPoint loc2;

@end

@implementation MultiTouchView

@synthesize loc1;

@synthesize loc2;

- (BOOL) isMultipleTouchEnabled {return YES;}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

NSArray *allTouches = [touches allObjects];

int count = [allTouches count];

if (count > 0) loc1 = [[allTouches objectAtIndex:0] locationInView:self];

if (count > 1) loc2 = [[allTouches objectAtIndex:1] locationInView:self];

57Recipe: Detecting Multitouch

Figure 2-6 The iPhone enables you to capturemultitouch events as well as single-touch ones.In this example, two circles mark the points at

which the user has touched the screen.

Page 81: I Phone Developer Cookbook

Recipe 2-4 Continued

[self setNeedsDisplay];

}

// React to moved touches the same as to "began"

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event

{

[self touchesBegan:touches withEvent:event];

}

- (void) drawRect: (CGRect) aRect

{

// Get the current context

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextClearRect(context, aRect);

// Set up the stroke and fill characteristics

CGContextSetLineWidth(context, 3.0f);

CGFloat gray[4] = {0.5f, 0.5f, 0.5f, 1.0f};

CGContextSetStrokeColor(context, gray);

CGFloat red[4] = {0.75f, 0.25f, 0.25f, 1.0f};

CGContextSetFillColor(context, red);

// Draw a line between the two location points

CGContextMoveToPoint(context, loc1.x, loc1.y);

CGContextAddLineToPoint(context, loc2.x, loc2.y);

CGContextStrokePath(context);

CGRect p1box = CGRectMake(loc1.x, loc1.y, 0.0f, 0.0f);

CGRect p2box = CGRectMake(loc2.x, loc2.y, 0.0f, 0.0f);

float offset = -8.0f;

// circle point 1

CGMutablePathRef path = CGPathCreateMutable();

CGPathAddEllipseInRect(path, NULL, CGRectInset(p1box, offset, offset));

CGContextAddPath(context, path);

CGContextFillPath(context);

CFRelease(path);

// circle point 2

path = CGPathCreateMutable();

CGPathAddEllipseInRect(path, NULL, CGRectInset(p2box, offset, offset));

CGContextAddPath(context, path);

CGContextFillPath(context);

CFRelease(path);

}

@end

58 Chapter 2 Views

Page 82: I Phone Developer Cookbook

NoteApple provides many Core Graphics/Quartz 2D resources on its developer Web site.Although these forums, mailing lists, and source code samples are not iPhone specific,they offer an invaluable resource for expanding your iPhone Core Graphics knowledge.

UIView AnimationsUIView animation provides one of the odd but lovely perks of working with the iPhoneas a development platform. It enables you to slow down changes when updating views,producing smooth animated results that enhance the user experience. Best of all, this alloccurs without you having to do much work.

UIView animations are perfect for building a visual bridge between a view’s currentand changed states.With them, you emphasize visual change and create an animationthat links those changes together.Animatable changes include the following:

n Changes in location—moving a view around the screenn Changes in size—updating the view’s framen Changes in transparency—altering the view’s alpha valuen Changes in rotation or any other affine transforms that you apply to a view

Building UIView Animation BlocksUIView animations work as blocks, a complete transaction that progresses at once. Startthe block by issuing beginAnimations:context:. End the block withcommitAnimations.These class methods are sent to UIView and not to individualviews. In the block between these two calls, you define the way the animation works andperform the actual view updates.The animation controls you’ll use are as follows:

n beginAnimations:context. Marks the start of the animation block.n setAnimationCurve. Defines the way the animation accelerates and decelerates.

Use ease-in/ease-out (UIViewAnimationCurveEaseInOut) unless you have some compelling reason to select another curve.The other curve types are ease in(accelerate into the animation), linear (no animation acceleration), and ease out(accelerate out of the animation). Ease-in/ease-out provides the most natural-feeling animation style.

n setAnimationDuration. Specifies the length of the animation, in seconds.Thisis really the cool bit.You can stretch out the animation for as long as you need itto run. Be aware of straining your user’s patience and keep your animations belowa second or two in length.

n commitAnimations. Marks the end of the animation block.

59UIView Animations

Page 83: I Phone Developer Cookbook

Sandwich your actual view change commands after setting up the animation detailsand before ending the animation. Listing 2-8 shows UIView animations in action by set-ting an animation curve and the animation duration (here, one second).The actualchange being animated is a transparency update.The alpha value of the content viewgoes to zero, making it invisible. Instead of the view simply disappearing, this animationblock slows down the change and fades it out of sight.

NoteApple often uses two animation blocks one after another to add bounce to their anima-tions. For example, they might zoom into a view a bit more than needed and then use asecond animation to bring that enlarged view down to its final size. Use “bounces” to adda little more life to your animation blocks. Be sure that the animations do not overlap.Either add a delay so that the second animation does not start until the first ends(performSelector: withObject: afterDelay:) or assign an animation delegatecallback (animationDidStop: finished:) to catch the end of the first animation andstart the second.

Listing 2-8 Using UIView Animation Calls

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];[UIView setAnimationDuration:1.0];

[contentView setAlpha:0.0f];

[UIView commitAnimations];

Recipe: Fading a View In and OutThere are times you’ll want to add information to your screen that overlays your viewbut does not of itself do anything. For example, you might show a top scores list or someinstructions or provide a context-sensitive tool tip. Recipe 2-5 demonstrates how to usea UIView animation block to slowly fade a noninteractive overlay view into and out ofsight.

This is done by creating a custom ToggleView.As defined by this code,ToggleViews are UIViews with one child, an image view.When tapped, the animationblock toggles the alpha setting from off to on or on to off.The key bits for making thishappen well and reliably are as follows:

n Make sure the child does not look for interaction events. Cocoa Touch does notallow transparent views to catch touches. So you must allow the parent, theToggleView, to handle all user interactions instead.When creating the child, themethod sets the child’s property userInteractionEnabled to NO.

60 Chapter 2 Views

Page 84: I Phone Developer Cookbook

n Make sure to catch only mouse down events. For simple on-off-on-off toggles,catch and respond only to presses for the most natural user feedback. Otherwise,user taps will hide and then immediately show your image view again.

n Pick a reasonable animation time. If you lengthen the animation beyond what youruser is willing to handle, you’ll end up handling new taps before the first anima-tion has completed.The one-second animation shown here is just about thelongest time you’ll want to use. Half- or quarter-second animations are better forcommon interface changes.

Recipe 2-5 Using UIView Animations with Transparency Changes

@interface ToggleView: UIView{

BOOL isVisible;

UIImageView *imgView;

}

@end

@implementation ToggleView

- (id) initWithFrame: (CGRect) aFrame;

{

self = [super initWithFrame:aFrame];

isVisible = YES;

imgView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[imgView setImage:[UIImage imageNamed:@"alphablend.png"]];

imgView.userInteractionEnabled = NO;

[self addSubview:imgView];

[imgView release];

return self;

}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

// only respond to mouse down events

UITouch *touch = [touches anyObject];

if ([touch phase] != UITouchPhaseBegan) return;

isVisible = !isVisible;

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];[UIView setAnimationDuration:1.0];

[imgView setAlpha:(float)isVisible];

61Recipe: Fading a View In and Out

Page 85: I Phone Developer Cookbook

Recipe 2-5 Continued

[UIView commitAnimations];

}

- (void) dealloc

{

[imgView release];

[super dealloc];

}

@end

Recipe: Swapping ViewsThe UIView animation block doesn’t limit you to a single change. Recipe 2-6 combinesframe size updates with transparency changes to create a more compelling animation.You do this by adding several directives at once to the animation block. Recipe 2-6 per-forms four actions at a time. It zooms and fades one view into place, while zooming outand fading away another. Figure 2-7 provides a preview of this animation in action.

62 Chapter 2 Views

Figure 2-7 Issuing several view changeswithin a single UIView animation block can

create complex visual effects.

Page 86: I Phone Developer Cookbook

Recipe 2-6 Combining Multiple View Changes in Animation Blocks

@interface ToggleView: UIView{

BOOL isOne;

UIImageView *imgView1, *imgView2;

}

@end

@implementation ToggleView

#define BIGRECT CGRectMake(0.0f, 0.0f, 320.0f, 435.0f)

#define SMALLRECT CGRectMake(130.0f, 187.0f, 60.0f, 60.0f)

- (id) initWithFrame: (CGRect) aFrame;

{

self = [super initWithFrame:aFrame];

// Load both views, make them noninteractive

imgView1 = [[UIImageView alloc] initWithFrame:BIGRECT];

imgView2 = [[UIImageView alloc] initWithFrame:SMALLRECT];

[imgView1 setImage:[UIImage imageNamed:@"one.png"]];

[imgView2 setImage:[UIImage imageNamed:@"two.png"]];

imgView1.userInteractionEnabled = NO;

imgView2.userInteractionEnabled = NO;

// image 1 is in front of image 2 to begin

[self addSubview:imgView2];

[self addSubview:imgView1];

isOne = YES;

[imgView1 release];

[imgView2 release];

return self;

}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

// Determine which view occupies which role

UIImageView *big = isOne ? imgView1 : imgView2;

UIImageView *little = isOne ? imgView2 : imgView1;

isOne = !isOne;

// Pack all the changes into the animation block

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];[UIView setAnimationDuration:1.0];

[big setFrame:SMALLRECT];

63Recipe: Swapping Views

Page 87: I Phone Developer Cookbook

Recipe 2-6 Continued

[big setAlpha:0.5];

[little setFrame:BIGRECT];

[little setAlpha:1.0];

[UIView commitAnimations];

// Hide the shrunken "big" image.

[big setAlpha:0.0f];

[[big superview] bringSubviewToFront:big];

}

-(void) dealloc

{

[imgView1 release];

[imgView2 release];

[super dealloc];

}

@end

Recipe: Flipping ViewsTransitions enable you to extend your UIView animation blocks to add even more visual flair.Two transitions—UIViewAnimationTransitionFlipFromLeft andUIViewAnimationTransitionFlipFromRight—enable you to do just what theirnames suggest.At this time, you can flip views left or flip views right.These are the onlytwo official transitions available for UIViews.

NoteDuring the SDK beta period, Apple promised additional animations that were never realized, specifically UIViewAnimationTransitionCurlUp andUIViewAnimationTransitionCurlDown. These extra animations may appear at somefuture time.

To use transitions in UIView animation blocks, you need to do two things. First, youmust add the transition as a block parameter. Use setAnimationTransition: to assignthe transition to the enclosing UIView animation block. Second, you should rearrangethe view order while inside the block.This is best done withexchangeSubviewAtIndex: withSubviewAtIndex:. Recipe 2-7 demonstrates howto create a simple flip view using these techniques.When tapped, the views use the ani-mation to flip from one side to the next, as shown in Figure 2-8.

Do not confuse the UIView animation blocks with the Core AnimationCATransition class. Unfortunately, you cannot assign a CATransition to your UIViewanimation.To use a CATransition, you must apply it to a UIView’s layer, which isshown in the next recipe.

64 Chapter 2 Views

Page 88: I Phone Developer Cookbook

Recipe 2-7 Using Transitions with UIView Animation Blocks

@interface FlipView : UIImageView

@end

@implementation FlipView

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event

{

// Start Animation Block

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationTransition: UIViewAnimationTransitionFlipFromLeft➥forView:[self superview] cache:YES];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];[UIView setAnimationDuration:1.0];

// Animations

[[self superview] exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

// Commit Animation Block

[UIView commitAnimations];

}

@end

65Recipe: Flipping Views

Figure 2-8 Use UIView’s built-intransition animations to flip yourway from one view to the next.

Page 89: I Phone Developer Cookbook

Recipe: Applying CATransitions to LayersCore Animation Transitions expand your UIView animation vocabulary with just a fewsmall differences in implementation. CATransitions work on layers rather than onviews. Layers are the Core Animation rendering surfaces associated with each UIView.When working with Core Animation, you apply CATransitions to a view’s defaultlayer ([myView layer]) rather than the view itself.

You don’t set your parameters through UIView the way you do with UIView animation.You create a Core Animation object, set its parameters, and then add the parameterizedtransition to the layer. Listing 2-9 shows a simple pushFromLeft method that you mightswap out for the flip method shown in Recipe 2-7.

Animations use both a type and a subtype. The type specifies the kind of transitionused.The subtype sets its direction.Together the type and subtype tell how the viewsshould act when you apply the animation to them.

Core Animation Transitions are distinct from the two UIView flips discussed in theprevious recipe. Cocoa Touch offers four types of Core Animation.These available typesinclude cross fades, pushes (used in Listing 2-9), reveals (where one view slides off another),and covers (where one view slides onto another).The last three types enable you tospecify the direction of motion for the transition through subtypes. For obvious reasons,cross fades do not have a direction and they do not use subtypes.

Core Animation is part of the Quartz Core framework.To use this sample code, youmust add the Quartz Core framework to your project and import <QuartzCore/QuartzCore.h> into your code.

NoteApple’s Core Animation features 2D and 3D routines built around Objective-C classes.These classes provide graphics rendering and animation for your iPhone and Macintoshapplications. Core Animation avoids many low-level development details associated with,for example, direct OpenGL while retaining the simplicity of working with hierarchical views.

Listing 2-9 Adding a Core Animation Transition to a UIView Layer

@implementation PushView

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event

{

CATransition *animation = [CATransition animation];

[animation setDelegate:self];

[animation setDuration:1.0f];

[animation setTimingFunction:UIViewAnimationCurveEaseInOut];[animation setType: kCATransitionPush];

[animation setSubtype: kCATransitionFromLeft];

[[self superview] exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

66 Chapter 2 Views

Page 90: I Phone Developer Cookbook

Listing 2-9 Continued

[[[self superview] layer] addAnimation:animation➥forKey:@"transitionViewAnimation"];

}

@end

Undocumented Animation TypesThe iPhone actually implements more animation types than official documents wouldsuggest.As Listing 2-10 shows, the iPhone is perfectly capable of handling map curls à lathe Google Maps application.This code, which works on the iPhone but not theSimulator, relies on extracting animation names from the UIKit binary framework file.

Like all undocumented calls, this is not without risk.Apple may change or deletethese animations at any time. Other animation types include pageCurl, pageUnCurl,suckEffect, spewEffect, cameraIris (from the Photos application),cameraIrisHollowOpen, cameraIrisHollowClose, genieEffect (typically usedfor deleting garbage), unGenieEffect, rippleEffect, twist, tubey, swirl,charminUltra, zoomyIn, zoomyOut, and oglFlip.

Note the use of setRemovedOnCompletion: NO.This freezes the animation at itsend, allowing the curled map to remain visible, as shown in Figure 2-9.

67Recipe: Applying CATransitions to Layers

Figure 2-9 This eye-catching effect uses an undocumented Core Animation type called mapCurl.

Page 91: I Phone Developer Cookbook

Listing 2-10 Calling Undocumented Animation Types

- (void) performCurl

{

// Curl the image up or down

CATransition *animation = [CATransition animation];

[animation setDelegate:self];

[animation setDuration:1.0f];

[animation setTimingFunction:UIViewAnimationCurveEaseInOut];

[animation setType:(notCurled ? @"mapCurl" : @"mapUnCurl")];

[animation setRemovedOnCompletion:NO];

[animation setFillMode: @"extended"];

[animation setRemovedOnCompletion: NO];

notCurled = !notCurled;

[[topView layer] addAnimation:animation forKey:@"pageFlipAnimation"];

}

General Core Animation CallsThe iPhone provides partial support for Core Animation calls. By partial, I mean thatmany standard classes are missing in action. CIFilter is one such class. It’s not includedin Cocoa Touch, although the CALayer and CATransition classes are both filter-aware.If you’re willing to work through these limits, you can freely use standard CoreAnimation calls in your programs.

Listing 2-11 shows iPhone native Core Animation code based on a sample fromLucas Newman (http://lucasnewman.com).When run, this method scales down andfades away the contents of a UIImageView.The source adds a translucent reflection layer,which follows the view.

This code remains virtually unchanged from the Mac OS X sample it was based on.More complex Core Animation samples may offer porting challenges, but for simplereflections, shadows, and transforms, all the functionality you need can be had at thenative iPhone level.

Listing 2-11 Native iPhone Core Animation Calls

// Adapted from http://lucasnewman.com/animationsamples.zip

- (void) scaleAndFade

{

// create the reflection layer

CALayer *reflectionLayer = [CALayer layer];

// share the contents image with the screen layer

reflectionLayer.contents = [contentView layer].contents;

reflectionLayer.opacity = 0.4;

reflectionLayer.frame = CGRectOffset([contentView layer].frame, 0.5, ➥416.0f + 0.5);

68 Chapter 2 Views

Page 92: I Phone Developer Cookbook

Listing 2-11 Continued

// flip the y-axis

reflectionLayer.transform = CATransform3DMakeScale(1.0, -1.0, 1.0);reflectionLayer.sublayerTransform = reflectionLayer.transform;

[[contentView layer] addSublayer:reflectionLayer];

#define ANIMATION_DURATION (4.0)

[CATransaction begin];

[CATransaction setValue:[NSNumber numberWithFloat:ANIMATION_DURATION]➥forKey:kCATransactionAnimationDuration];

// scale it down

CABasicAnimation *shrinkAnimation = [CABasicAnimation➥animationWithKeyPath:@"transform.scale"];

shrinkAnimation.timingFunction = [CAMediaTimingFunction➥functionWithName:kCAMediaTimingFunctionEaseIn];

shrinkAnimation.toValue = [NSNumber numberWithFloat:0.0];

[[contentView layer] addAnimation:shrinkAnimation forKey:@"shrinkAnimation"];

// fade it out

CABasicAnimation *fadeAnimation = [CABasicAnimation➥animationWithKeyPath:@"opacity"];

fadeAnimation.toValue = [NSNumber numberWithFloat:0.0];

fadeAnimation.timingFunction = [CAMediaTimingFunction functionWithName:➥kCAMediaTimingFunctionEaseIn];

[[contentView layer] addAnimation:fadeAnimation forKey:@"fadeAnimation"];

[CATransaction commit];

}

Recipe: Swiping ViewsSwipes are a convenient but often-overlooked iPhone interaction style.When a userquickly drags his or her finger across the screen, the UITouch objects returned for thatgesture include an info property.This property defines the direction in which the userswiped the screen, (for example, up, down, left, or right).This behavior is best seen in theiPhone’s Photos application, when users swipe left or right to move between album pictures.

Early versions of the iPhone SDK offered swipe detection as a standard part of theUITouch object, but later releases dropped that capability. Instead,Apple offeredworkaround code in its iPhone Developers Guide. Recipe 2-8 is based on that code. Itensures that a user continues finger movement in one direction by defining a safety zonearound the movement. If the user strays diagonally more than 6 pixels off course, theswipe cancels. Stay on-course for at least 12 pixels and the swipe is set.

69Recipe: Swiping Views

Page 93: I Phone Developer Cookbook

Recipe 2-8 applies a Core Animation Transition on completion of a successful swipe.It uses the swipe direction to set the animation’s subtype. Subtypes are used in CoreAnimation to specify the overall movement of the animation, whether up, down, or sideways.

This sample mimics the interaction style used for browsing through album pictures inPhotos but allows you to move up and down as well as left and right. If you commentout the kCATransitionPush animation type and replace it with the undocumentedoglFlip in the line that immediately follows it, you’ll receive an even nicer surprise. Farfrom being limited to the two core flip directions, the iPhone actually supports a fullfour-way flip style, albeit one that Apple has not included in its public SDK.

NoteIn early releases of the iPhone SDK, swipes didn’t work in the Simulator. In later versions,they did. Should you encounter platform limitations while developing (for example, whenworking with the Camera), you can easily add workarounds based on testing the platform.Add compiler directives such as #if defined(TARGET_IPHONE_SIMULATOR) to yoursource.

Recipe 2-8 Detecting and Responding to User Swipes in Your Views

- (CATransition *) getAnimation:(NSString *) direction

{

CATransition *animation = [CATransition animation];

[animation setDelegate:self];

[animation setType:kCATransitionPush];

// [animation setType:@"oglFlip"];

[animation setSubtype:direction];

[animation setDuration:1.0f];

[animation setTimingFunction:[CAMediaTimingFunction➥functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

return animation;

}

#define HORIZ_SWIPE_DRAG_MIN 12

#define VERT_SWIPE_DRAG_MAX 4

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

UITouch *touch = [touches anyObject];

startTouchPosition = [touch locationInView:self];

dirString = NULL;

}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{

70 Chapter 2 Views

Page 94: I Phone Developer Cookbook

Recipe 2-8 Continued

UITouch *touch = touches.anyObject;

CGPoint currentTouchPosition = [touch locationInView:self];

if (fabsf(startTouchPosition.x - currentTouchPosition.x) >=

HORIZ_SWIPE_DRAG_MIN &&

fabsf(startTouchPosition.y - currentTouchPosition.y) <=

VERT_SWIPE_DRAG_MAX)

{

// Horizontal Swipe

if (startTouchPosition.x < currentTouchPosition.x) {

dirString = kCATransitionFromLeft;

}

else

dirString = kCATransitionFromRight;

}

else if (fabsf(startTouchPosition.y - currentTouchPosition.y) >=

HORIZ_SWIPE_DRAG_MIN &&

fabsf(startTouchPosition.x - currentTouchPosition.x) <=

VERT_SWIPE_DRAG_MAX)

{

// Vertical Swipe

if (startTouchPosition.y < currentTouchPosition.y)

dirString = kCATransitionFromBottom;

else

dirString = kCATransitionFromTop;

} else

{

// Process a non-swipe event.

// dirString = NULL;

}

}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

{

if (dirString)

{

CATransition *animation = [self getAnimation:dirString];

[[self superview] exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

[[[self superview] layer] addAnimation:animation forKey:kAnimationKey];

}

}

71Recipe: Swiping Views

Page 95: I Phone Developer Cookbook

Recipe: Transforming ViewsAffine transforms enable you to change an object’s geometry by mapping that objectfrom one view coordinate system into another.The iPhone SDK fully supports standardaffine 2D transforms.With them, you can scale, translate, rotate, and skew your viewshowever your heart desires and your application demands.

Transforms are defined in Core Graphics, and consist of calls such asCGAffineTransformMakeRotation and CGAffineTransformScale.These build andmodify the 3-by-3 transform matrices. Once built, use UIView‘s setTransform: call toapply 2D affine transformations to UIView objects.

Recipe 2-9 demonstrates how to build and apply an affine transform of a UIView.Tocreate the sample, I kept things simple. I build an NSTimer that ticks every 1/30th of asecond. On ticking, it rotates a view by 1% of pi and scales over a cosine curve. I use thecosine’s absolute value for two reasons. It keeps the view visible at all times, and it pro-vides a nice bounce effect when the scaling changes direction.This produces a rotatingand undamped bounce animation.

This is one of those samples that it’s best to build and view as you read through thecode.You’ll be better able to see how the handleTimer: method correlates to the visu-al effects you’re looking at.

Recipe 2-9 Example of an Affine Transform of a UIView

#import "math.h"

#define PI 3.14159265

@interface HelloController : UIViewController{

UIView *contentView;

UIImageView *rotateView;

int theta;

}

@end

@implementation HelloController

- (id)init

{

if (self = [super init]) self.title = @"Affine Demo";

return self;

}

- (void) handleTimer: (NSTimer *) timer

{

// Rotate each iteration by 1% of PI

float angle = theta * (PI / 100);

CGAffineTransform transform = CGAffineTransformMakeRotation(angle);

theta = (theta + 1) % 200;

72 Chapter 2 Views

Page 96: I Phone Developer Cookbook

Recipe 2-9 Continued

// For fun, scale by the absolute value of the cosine

float degree = cos(angle);

if (degree < 0.0) degree *= -1.0f;

degree += 0.5f;

CGAffineTransform scaled = CGAffineTransformScale(transform, degree, degree);

// Apply the affine transform

[rotateView setTransform:scaled];

}

- (void)loadView

{

theta = 0;

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

rotateView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 240.0f,➥240.0f)];

[rotateView setImage:[UIImage imageNamed:@"rotateart.png"]];

[rotateView setCenter:CGPointMake(160.0f, 208.0f)];

[contentView addSubview:rotateView];

[rotateView release];

self.view = contentView;

[contentView release];

[NSTimer scheduledTimerWithTimeInterval: 0.03f target: self selector:➥@selector(handleTimer:)

userInfo: nil repeats: YES];

}

// Allow the view to respond to iPhone Orientation changes

-(BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

return NO;

}

-(void) dealloc

{

[contentView release];

[rotateView release];

[super dealloc];

}

@end

73Recipe: Transforming Views

Page 97: I Phone Developer Cookbook

Centering Landscape ViewsUse the same affine transform approach to center landscape-oriented views. Listing 2-12creates a 480-by-320 pixel view, centers it at [160, 240] (using portrait view coordinates),and then rotates it into place. Half of pi corresponds to 90 degrees, creating a landscape-right rotation. Centering keeps the entire view onscreen.All subviews, including textfields, labels, switches, and so on rotate into place along with the parent view.

If you want to work with a landscape keyboard for this view, make sure to call[[UIApplication sharedApplication] setStatusBarOrientation:

UIInterfaceOrientationLandscapeRight].This sets the status bar orientation,which controls the keyboard regardless of whether the status bar is hidden or shown.

Listing 2-12 Rotating Landscape Views into Place

@implementation HelloController

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 480.0f,➥320.0f)];

[contentView setCenter:CGPointMake(160.0f, 240.0f)];

[contentView setBackgroundColor:[UIColor blackColor]];

[contentView setTransform:CGAffineTransformMakeRotation(3.141592f / 2.0f)];

self.view = contentView;

[contentView release];

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

SummaryUIViews provide the onscreen components your users see and interact with.As thischapter has shown, even in their most basic form, they offer incredible flexibility andpower.You’ve discovered how to use views to build up elements on a screen, create multiple interaction objects, and introduce eye-catching animation. Here’s a collection ofthoughts about the recipes you’ve seen in this chapter that you might want to ponderbefore moving on:

n When dealing with multiple onscreen views, hierarchy should always remain front-most in your mind—no pun! Use your view hierarchy vocabulary(bringSubviewToFront:, sendSubviewToBack:,

74 Chapter 2 Views

Page 98: I Phone Developer Cookbook

exchangeSubviewAtIndex:withSubviewAtIndex:) to take charge of yourviews and always present the proper visual context to your users.

n You’re not limited to rectangles. Use UIView / Core Graphics clipping to createcompelling interaction objects that don’t necessarily have right corners.

n Be concrete.The iPhone has a perfectly good touch screen.Why not let your usersdrag items around the screen with their fingers? It adds to the reality and the plat-form’s interactive nature.

n Animate everything.Animations don’t have to be loud, splashy, or bad design.TheiPhone’s strong animation support enables you to add smooth transitions betweenuser tasks. Short, smooth, focused changes are the iPhone’s bread and butter.

n Users typically have five fingers per hand. Don’t limit yourself to a one-fingerinterface when it makes sense to expand your interaction into multitouch territory.

n 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’dlike, including text, Bézier curves, scribbles, and so forth.

n Explore! This chapter has only touched lightly on the ways you can use UIViewsin your applications. Use this material as a jumping-off point to explore the fullvocabulary of the UIView class.

75Summary

Page 99: I Phone Developer Cookbook
Page 100: I Phone Developer Cookbook

3View Controllers

The iPhone screen isn’t big by any measure. It’s just a few inches across by a few incheshigh.This diminutive size constrains programs by limiting a user’s physical viewport.Although your virtual worlds may be very large indeed, the small physical screen meansthat iPhone classes must cleverly expand their practical interaction space. In this chapter,you’re about to discover numerous ways to create virtual presentations that enlarge yourapplication’s physical reality through the use of view controllers.You’ll discover how touse view controllers to build navigable worlds and how to connect to these classes usingInterface Builder.

View ManagementOn the iPhone, view controllers centralize basic view management.They provide practi-cal utility by linking the view functionality covered in Chapter 2,“Views,” with thepragmatic reality of your device.View controllers handle reorientation events such aswhen users tip the iPhone on its side to landscape mode and navigation issues such aswhen users need to move their attention from view to view.The following sectionsintroduce the kinds of view controllers used in the iPhone SDK.You see what classes areused in the view controller paradigm, including both core classes and specialized ones.

Core ClassesThe iPhone SDK offers any number of view controllers and their subclasses.These rangefrom the general to the specific. Just three of these represent core controller classes.Theyare as follows:

n UIViewController. This is the parent class for view controllers and the one youuse to specifically link to a single view. It’s the workhorse of view controllers andthe kind you’ll spend most of your time customizing. Most important,UIViewController handles all view reorientation tasks, enabling you to easilyprogram for both landscape and portrait orientation.

Page 101: I Phone Developer Cookbook

n UINavigationController. Use navigation controllers for navigating up anddown through tree-based view hierarchies (for example, drilling).As their namesuggests, they create the blue- or gray-colored navigation bars that appear at thetop of a standard iPhone application.They enable you to add buttons through theuse of Navigation Items and automatically generate “back” buttons showing thetitle of the calling view controller.All navigation controllers use a “root” view con-troller to establish the top of their navigation tree.

n UITabBarController. Parallel views are like stations on a radio.A tab barenables users to select which UIViewController to “tune in to,” without therebeing a specific navigation hierarchy.You see this best in applications like YouTubeand iPod, where users select whether to see a “Top 25” list or view albums orplaylists.When TabBar instances offer more than five view controller choices at atime, users can customize them through the More > Edit screen.The More > Editscreen lets users drag their favorite controllers down to the button bar at the bottom of the screen.

Specialized ClassesIn addition to the three general classes you just read about,Apple offers many highlyadapted controllers.These specialized items leverage access to the iPhone’s core databasesand functions.While you can throw instances of these classes into your applications anduse them like any other UIViewController, they’re preloaded with advanced function-ality. Here are a few examples of the kinds of specialized view controllers available in theSDK:

n UIImagePickerController. This utility controller enables users to select imagesfrom onboard albums or to snap a photo from the iPhone camera.Apple has addedan advanced image-selection interface.The controller even lets the users orient andzoom an image before finishing.

n ABNewPersonViewController. This controller enables users to create a newcontact for their Address Book. Other Address Book controller styles includeABPersonViewController, ABUnknownPersonViewontroller, andABPeoplePickerNavigationController.

n UITableViewController. Table view controllers simplify using tables in youriPhone projects.This controller class provides a standard already-connectedUITableView instance and automatically sets delegation and data sources to pointto itself.All you have to do is supply those delegate and data source methods to fillup the table with data and react to user taps. UITableViewController isdiscussed at length in Chapter 5,“Basic Tables.”

n MPMoviePlayerController. When your user needs to watch a movie or listento audio, this controller does the trick. Just supply it with a path to the media

78 Chapter 3 View Controllers

Page 102: I Phone Developer Cookbook

resource and drill down to its display.The controller provides a Done button forthe user or automatically returns a delegate call when playback finishes.

In a way, these specialized controllers are both a blessing and a curse. On the positiveside, they introduce enormous functionality, essentially with no additional programmingburden. On the downside, they’re so specialized that they often hide core features thatdevelopers might prefer to work with.

For example, there’s no simple audio playing classes.You either work at the lowestlevel through AudioQueue, dealing with sample rates and encoding styles, or at the high-est through MPMoviePlayerController.You have no access to a general scrubber baror pause/play button.These are actually features that are hidden within the Media Playerframework, but they have no publicly defined methods or classes.There used to be apublic framework called Celestial that provided a QuickTime-like interface to audio-visual controllers and handy playback features, but Apple moved this framework to itsPrivate Frameworks hierarchy for firmware versions after 1.1.4.

Creating a UIViewControllerUIViewController instances provide tons of great core view management.What theydon’t supply is an actual view. It’s up to you to create one and install it into the viewcontroller via the loadView method. Every UIViewController and subclass imple-ments this method, which is where you connect the controller to a view instance.Youneed to do two things: Initialize the view and then connect it to the controller:

n Initialize and size your content view by calling initWithFrame: using[[UIScreen mainScreen] applicationFrame] as its argument.This returnsthe standard bounds for the application.The math automatically takes any onscreennavigation bars and tab bars into account.The Hello World example in Chapter 1,“Introducing the iPhone SDK,” provided a simple example of this layout in action.

n Connect the view to the controller by assigning self.view to a UIView instanceof some kind. Do this in your loadView method (for example, self.view =contentView).

When working with specialized controllers, always make sure to call the superclass’sversion of the method from the child (that is, [super loadView]).This ensures thatchildren inherit the parent’s layout behavior as well as the class methods and variables.Other key UIViewController methods include the following.

initUse the class init method to set the view controller’s title and other features that needto be set up before the class is actually used.To match the title to the application name,assign self.title to [[[NSBundle mainBundle] infoDictionary]

objectForKey:@“CFBundleName”].This returns the name defined in the bundle’sInfo.plist file.

79View Management

Page 103: I Phone Developer Cookbook

Unlike UIView, the UIViewController instance is an abstract object and has nophysical onscreen presence. Do not set a frame the way you would with a normalUIView. UIViews use initWithFrame: and UIViewControllers use init. Set up anyview frames in the well-named loadView method.

shouldAutorotateToInterfaceOrientation:This method returns either YES or NO, depending on whether you want to supportautorotation in your program.When returning YES, the view controller uses several flagsto determine how the autorotation takes place. Listing 3-1 shows a typical code snippetfrom a loadView method that specifies that subviews should automatically resize bothhorizontally and vertically.

Listing 3-1 Setting Up Autorotation Flags

contentView.autoresizesSubviews = YES;

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |

UIViewAutoresizingFlexibleHeight);

viewDidAppear: and viewDidDisappear:These methods are called each time a UIViewController’s view is brought onscreen orpushed offscreen—typically due to navigation between views. Use these methods torefresh the view to reflect the current data state and update stored data.These methodsenable you to claim or resign first-responder status for text elements, remove text selec-tions if needed, or perform any other bookkeeping that needs to be done before return-ing control to the user.

didRotateFromInterfaceOrientation:Programmatically catch orientation changes by implementing this method in yourUIViewController subclass.This method enables you to respond to the new orientation and passes the previous orientation in case you want to perform a specificanimation type based on the change involved. It is entirely possible to move from oneorientation to another without passing through the intermediate phases you’d expect,so avoid assumptions. Use the actual data passed to this method.

NoteApple has promised support for separate landscape and portrait views in the SDK at somepoint. At the time of writing, this functionality has not yet been implemented. It’s up to youto rearrange your screen when the orientation changes. To force your application into land-scape mode, return NO for shouldAutorotateToInterfaceOrientation:, call[[UIApplication sharedApplication] setStatusBarOrientation:UIInterfaceOrientationLandscapeRight] from yourapplicationDidFinishLaunching: method, and set UIInterfaceOrientationto UIInterfaceOrientationLandscapeRight in your Info.plist file.

80 Chapter 3 View Controllers

Page 104: I Phone Developer Cookbook

Working with Interface Builder to Build Views forUIViewControllersInterface Builder, with its interactive GUI layout tools, helps create UIView content. Itmakes it possible for you to add interactive controls, moving them around the screen byhand to design one-of-a-kind interfaces. Interface Builder excels at UIView layout. Itoffers little or no direct support for UIViewController behavior.That’s because theUIViewController is an abstract class, and UIView is a concrete one.

While taking that limitation into account, you can use Interface Builder to embedyour UIView material into UIViewController instances and into other view controllerclasses.The workflow you’re about to read through lets you combine the power ofInterface Builder’s design tools with the convenience of view controller classes.

Temperature Conversion ExampleThis example creates a classic Fahrenheit to Celsius conversion using a combination of Xcode and Interface Builder.A UIView subclass defines the conversion behavior.This view connects the buttons and text fields and gives them meaning by adding aconvert: function to perform the math.

The view is embedded into a trivial UIViewController subclass.This controller typically takes over any autorotation and view navigation tasks. In this example, it doesn’tactually do much other than demonstrate how views are created and added to controllersusing the Interface Builder tool.

In the following steps, you create a new Xcode project, populate it with the convertsource code, and then lay out the GUI using Interface Builder’s tools:

1. Create a new Xcode project.

In Xcode, choose File, New Project. Create a new Cocoa Touch Application View-Based project and name it however desired. Keep the name short. Both Xcode andInterface Builder use that project name to define your application delegate, so asimple name works best.

2. Clean up the project and replace the code.

Select the Classes subfolder and delete it.This leaves main.m as your sole remain-ing source file. Open it and replace its contents with Listing 3-2. Change the nameof the application delegate (here WalkThroughAppDelegate) to match your proj-ect name. Notice the two IBOutlet text fields and the IBAction return type.TheIBOutlet keyword indicates objects to be defined in Interface Builder. IBActiontells Interface Builder to treat methods as the action for target/action pairs.IBAction is functionally equivalent to (void).

Listing 3-2 Converter Source

#import <UIKit/UIKit.h>

// Custom view that converts temperatures

81Working with Interface Builder to Build Views for UIViewControllers

Page 105: I Phone Developer Cookbook

Listing 3-2 Continued

@interface MyView : UIView {

IBOutlet UITextField *infield;

IBOutlet UITextField *outfield;

}

@end

@implementation MyView

- (IBAction) convert: (id) sender

{

float invalue = [[infield text] floatValue];

float outvalue = (invalue - 32.0f) * 5.0f / 9.0f;

[outfield setText:[NSString stringWithFormat:@"%3.2f", outvalue]];

}

@end

// View controller that autorotates

@interface MyViewController : UIViewController

@end

@implementation MyViewController

-(void) loadView

{

[super loadView];

self.view.autoresizesSubviews = YES;

self.view.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

return YES;

}

@end

// Application delegate that uses NIB objects

@interface WalkThroughAppDelegate : NSObject {

IBOutlet UIWindow *window;

IBOutlet MyView *contentView;

}

@end

@implementation WalkThroughAppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application {

[window addSubview:contentView];

[window makeKeyAndVisible];

}

82 Chapter 3 View Controllers

Page 106: I Phone Developer Cookbook

Listing 3-2 Continued

- (void)dealloc {

[contentView release];

[window release];

[super dealloc];

}

@end

int main(int argc, char *argv[])

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

int retVal = UIApplicationMain(argc, argv, nil, nil);

[pool release];

return retVal;

}

3. Launch Interface Builder.

Double-click the MainWindow.xib file in your project (see Figure 3-1).This opensit in Interface Builder.

83Working with Interface Builder to Build Views for UIViewControllers

Figure 3-1 Xcode automatically creates a MainWindow.xib file in your project. Double-click this

file to launch Interface Builder.

Page 107: I Phone Developer Cookbook

4. Set up your windows.

When Interface Builder launches, your MainWindow.nib project contains fiveobjects: a file owner, an application delegate object (named with your projectname), a First Responder object, a view controller, and a window (see Figure 3-2).

5. Open the Inspector and Library.

Select Tools, Inspector (Command-Shift-I) and Tools, Library (Command-Shift-L).These two windows provide two of the key tools for working in Interface Builder:

n The Library provides a set of reusable Cocoa Touch objects.You can drag theseobjects from the Library window into your application.

n The Inspector enables you to view and customize the interface objects in yourproject. Use the Inspector to see how objects are connected together and whatclasses they link to.

6. Set Up your application delegate.

Tap the application delegate object in your main XIB window. (It’s the one that’snot the file’s owner or window).Then either tap the rightmost tab in the Inspectoror press Command-4. Both of these actions open the Identity Inspector. In theIdentity Inspector, locate the class name at the top of the window (see Figure 3-3).

84 Chapter 3 View Controllers

Figure 3-2 The default Xcode XIB file contains a file owner, an application delegate, and a window.

Page 108: I Phone Developer Cookbook

If you want to change the class name used by your application delegate, here iswhere you update it.

The owner, delegate, and window objects mirror the classes used in standard appli-cations.The file’s owner object defines the class that “owns” the XIB file (that is,the shared UIApplication instance).The application delegate object connects tothe delegate, which is responsible for handling the applicationDidLauncheventsapplicationDidFinishLaunching: callback and setting up the primarywindow and ordering it out for display.

7. Add outlets to the delegate.

Add two outlets: window and contentView. Change the window class from id toUIWindow, and the view class to MyView.Typing these outlets adds a layer of con-trol to your IB setup.When you make your connections, they’ll only connect toobjects of the proper class.Adding class types isn’t required—and in a few stepsyou’ll see how to do this without typing—but it’s something you should be awareof as a developer.

85Working with Interface Builder to Build Views for UIViewControllers

Figure 3-3 Use the Identity Inspector to add outlets or actions and update class names associated with Interface Builder objects.

Page 109: I Phone Developer Cookbook

8. Add an object to your project window.

In your Library locate the UIViewController (it’s toward the top of the library)and UIView (it’s near the bottom). Drag one of each into your project window.Figure 3-4 shows the project window after adding these objects.These two itemsuse this object.This item will be used to represent instances of the classesan instanceof the MyView class you defined in the code:MyView and MyViewController.

9. Change the View Controller class.

Tap the new View Controller. In the Identity Inspector (Command-4), change itsclass to MyViewController.The View Controller’s name in the project windowupdates to My View Controller.

10. Change the view class and add its outlets and action.

Tap the new view. Use the Identity Inspector to change its class to MyView.The view in the project window changes its name to My View.Add one action(convert:) and two outlets (infield and outfield) to the view, as shown inFigure 3-5.

Actions correspond to instance methods in your source code, and outlets corre-spond to instance variables. If you refer back to the code from Step 2 of theseinstructions, you see that the MyView class was defined with two IBOutletinstances and with a method to do the conversion. In the next steps, you see howto connect onscreen objects with these instance variables and methods.

11. Edit the view.

Double-click My View in the project window to open its editor. From the Library,drag over two labels, one button and two fields, as shown in Figure 3-6.

86 Chapter 3 View Controllers

Figure 3-4 Dragging objects from the Library into your main XIB window adds

instances of those classes to your project.

Page 110: I Phone Developer Cookbook

87Working with Interface Builder to Build Views for UIViewControllers

Figure 3-6 Drag interface elements from the Interface Builder Library (Tools, Library)

onto a view to begin laying out its GUI.

Figure 3-5 The Identity Inspector enables you to add outlets and actionsto your objects. These correspond to instance variables and instance

methods in your source code.

Page 111: I Phone Developer Cookbook

88 Chapter 3 View Controllers

12. Edit the view.

Arrange the elements in your view to roughly mimic the layout shown in Figure 3-7. Putting items toward the top of the screen leaves room for the key-board at the bottom.

If you like, use the built-in horizontal and vertical guides (Layout,Add HorizontalGuide and Layout,Add Vertical Guide) to simplify layout.These guides are similarto the ones you find in Photoshop.They are virtual placement helpers only.Theydo not appear in your final application. If you choose to use them, pay attention tothe left and right numeric indicators they show while being moved.They let youknow how many pixels are to their left and right.A vertical guide at 160 | 160 isdirectly in the middle of the screen.

Figure 3-7 Place your interface elements toward the top of the screen to leave room for the keyboard, which will appear at the bottom.

13. Edit the left field traits.

Select the left (input) field and use the Attributes Inspector (Command-1) to set itskeyboard type to numbers and punctuation.There are additional traits here youwill want to explore on your own time.

Page 112: I Phone Developer Cookbook

14. Connect the application delegate to its window and view.

While holding down the Control key, drag from the application delegate object inthe project window to the Window object. Select Window from the pop-up.Thisassigns the Window object to the window instance variable of your application delegate.

Next, Control-drag from the application delegate to the My View object. SelectcontentView from the pop-up.

These actions set up the application delegate’s instance variables, creating associa-tions between the IBOutlet instances declared in the code with the actual windowand view laid out in Interface Builder.

15. Connect the view to its outlets.

Open the My View window so that it can be seen. Control-drag between the MyView object and the left text field. Select Infield from the pop-up.Then Control-drag from the view to the right text field and select Outfield.Again, you’re assign-ing objects to instance variables.

16. Connect the button to the view.

Right-tap Control-drag from the Convert button you added to the custom viewto the My View object.A customization pop-up appears. Scroll down the pop-upto find TouchUpInside. Drag from the circle to its right to the MyView instance inthe XIB project window. Select, select convert: from the pop-up.

This action connects a touch inside the button to the conversion action youdefined in your object. Check on the connection by right-tapping the Convertbutton again.The pop-up confirms this connection, as shown in Figure 3-8.

89Working with Interface Builder to Build Views for UIViewControllers

Figure 3-8 Once connected, you can view both the target and the action associated

with a touch event.

17. Add the view to its controller.

Double-tap My View Controller to open its editor. Drag the My View instancefrom the project window onto this editor. It automatically jumps into place.Notice the right-pointing arrow at the top right of the View Controller editor.

Page 113: I Phone Developer Cookbook

This allows you to preview your view in landscape as well as in portrait mode. Goahead and do this, and make any adjustments needed to provide a satisfactory land-scape presentation.

By assigning the view to a controller, you enable that view controller to assumeresponsibilities for that view in your application.You do not need to make any further formal connections in Interface Builder.

18. Save.

Save your changes in Interface Builder and return to Xcode.

19. Run.

In Xcode, set your active SDK to either the device or the Simulator and run thecompleted application. Figure 3-9 shows the final result with a view whose elements have been fully defined in Interface Builder connected to a simpleUIViewController of your design.

90 Chapter 3 View Controllers

Figure 3-9 The completed application binds the custom-built view to the UIViewController you specified.

Loading XIB Files DirectlyCocoa Touch lets you recover objects from any XIB file by calling loadNibNamed:owner: options:.This returns an array of objects initialized from the NIB/XIB bundle, which you can then grab and use in your program.

Page 114: I Phone Developer Cookbook

NSArray *niblets = [[NSBundle mainBundle] loadNibNamed:@”sample” owner:selfoptions:NULL];

for (id theObject in niblets) {

if ([theObject isKindOfClass:[UIViewController class]])

[self.navigationController pushViewController:theObject➥animated:YES];

}

Navigation ControllersThe UINavigationController class provides all the high-calorie goodness of aUINavigationBar-based interface with little programming. Navigation controllersenable you to smoothly move between views using built-in animation.You also get history control.The controller handles all the Back button functionality.This means thatparent view titles automatically appear as Back buttons and you can “pop the stack,” soto speak, without any programming.

And if that weren’t enough, the navigation controller also offers a simple menu bar.You can add buttons—or even more complicated controls—into the bar to build actionsinto your application. Between these three features of navigation, history, and menus,navigation controllers build a lot of wow into a simple-to-program package.

The following recipes introduce these core navigation controller features from build-ing menus to building a history stack. In these examples, you see how to use theUINavigationBar class to create a variety of novel and useful interfaces.

Setting Up a Navigation ControllerWhether you plan to use a navigation controller to simplify moving between views—itsintended use—you should understand how the navigation controller works.At their sim-plest level, navigation controllers manage view controller stacks.

Every navigation controller owns a root view controller.This controller forms thebase of the stack.You can push other controllers onto the stack.This extends the naviga-tion breadcrumb trail and automatically builds a Back button.You can pop controllers offthe stack by tapping one of these Back buttons.And you can pop back until you reachthe root.Then you can go no further.The root is the root, and you cannot pop belowthat 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 tobuild a two-button menu, for example.This would disregard any navigational advantageof the stack.You still need to set that one controller as the root viainitWithRootViewController:.

Listing 3-3 shows the creation and initialization of a navigation controller from a typical applicationDidFinishLaunching: method.The controller is allocated,initialized with a root view, and then added to the main window via its view property.

91Navigation Controllers

Page 115: I Phone Developer Cookbook

Listing 3-3 Initializing a Navigation Controller

- (void)applicationDidFinishLaunching:(UIApplication *)application

{

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

UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController: [[HelloController alloc] init]];

[window addSubview:nav.view];

[window makeKeyAndVisible];

}

Pushing and Popping View ControllersAdd new items onto the navigation stack by pushing a new controller withpushViewController: animated:. Send this call to the parent navigation controllerof any UIViewController (for example, [self navigationController]).Whenpushed, the new controller slides onscreen (assuming you set animated to YES).A left-pointing Back button appears, leading you one step back on the stack.The Back buttonuses the title of the previous view controller.

There are many reasons you’d push a new view.Typically, these involve navigating tosubviews like detail views or drilling down a file structure.You normally push controllersonto the navigation controller stack after your user taps a button, a table item, or a disclosure accessory.

Perform push requests and other navigation bar customization inside yourUIViewController subclasses.There’s no reason or need to subclassUINavigationController.You have direct access to the navigation bar and its features.Here is a line from a loadView method that customizes the navigation bar style using achain of properties:

self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;

Modal PresentationModal presentation offers another way to present a view controller.After sending thepresentModalViewController: animated: message, a new view controllerslides up into the screen and takes control until it’s dismissed withdismissModalViewControllerAnimated:.This enables you to add special-purposedialogs into your applications.Typically, modal controllers are used to pick data such ascontacts from the Address Book or photos from the Library. Chapters 7,“Media,” and 9,“People, Places, and Things,” show modal view controllers in action.

The Navigation Item ClassThe objects that populate the navigation bar are put into place using theUINavigationItem class.This class enables you to attach buttons, text, and other UI

92 Chapter 3 View Controllers

Page 116: I Phone Developer Cookbook

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 theUIViewController’s title) in the middle, and a Back-styled button on the left. Butyou’re not limited to that layout.You can add custom controls to any of these three locations. So you can build navigation bars with search fields, with segment controls,with toolbars, with pictures, and more.

Assign these items via SetCustomLeftItem: animated:, SetCustomRightItem:animated: and by updating the customTitleView property to point to a view of yourchoice. Listing 3-4 shows how to add a custom button to the right navigation item view.This is a standard navigation-style UIButton (UIButtonTypeNavigation). In thisexample, it invokes the randomize method when pushed.

Listing 3-4 Adding a Custom Button to a UINavigationItem

// Add a randomize button

UIBarButtonItem *randomButton = [[[UIBarButtonItem alloc]

initWithTitle:@"Randomize"

style:UIBarButtonItemStylePlain

target:self

action:@selector(randomize)] autorelease];

self.navigationItem.rightBarButtonItem = randomButton;

Adding a custom view to the title space is just as simple. Instead of adding a control,assign a view.This example (from the first recipe in Chapter 5) adds a custom UILabel:

self.navigationItem.titleView = [[UILabel alloc] initWithFrame:CGRectMake(0.0f,➥4.0f, 320.0f, 36.0f)];

Recipe: Building a Simple Two-Item MenuAlthough many applications demand serious user interfaces, often you don’t need com-plexity.A simple one- or two-button menu can accomplish a lot. Use these steps to create basic interfaces for simple utilities:

1. Create a UIViewController subclass that you’ll use as your primary interactionspace.

2. Allocate a navigation controller and assign an instance of your custom view controller to its root view.

3. In the custom view controller, create one or two buttons and add them to theview’s navigation item.

4. Build the callback routines that get triggered when a user taps a button.

Recipe 3-1 demonstrates these steps. It creates a simple view controller calledHelloController and assigns it as the root view for a UINavigationController. Inthe loadView method, two buttons populate the left and right custom slots for the

93Recipe: Building a Simple Two-Item Menu

Page 117: I Phone Developer Cookbook

view’s navigation item.When tapped, these update the background color for the mainview.This recipe is not feature rich, but it provides an easy-to-build two-item menu.Figure 3-10 shows the interface in action.

If you’re looking for more complexity than two items can offer, consider having thebuttons trigger UIActionSheet menus.Action sheets, which are discussed in Chapter 4,“Alerting Users,” enable you to select actions from a short list of options (usuallybetween two and five options) and can be seen in use in the Photos and Mail applica-tions for sharing and filing data.

94 Chapter 3 View Controllers

Figure 3-10 Create a basic two-button menu for simple iPhone applications by adding custom buttons to a UINavigationController.

NoteYou can add images in addition to or instead of text to the UIButtons used in your navigation bar.

Recipe 3-1 Creating a Two-Item Menu Using a Navigation Controller

- (void) goRed

{

contentView.backgroundColor = [UIColor colorWithRed: 1.0f green:0.45f➥blue:0.45f alpha:1.0f];

Page 118: I Phone Developer Cookbook

Recipe 3-1 Continued

}

- (void) goBlue

{

contentView.backgroundColor = [UIColor colorWithRed: 0.45f green:0.45f➥blue:1.0f alpha:1.0f];

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view = contentView;

contentView.backgroundColor = [UIColor whiteColor];

[contentView release];

// Add a left button

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Red"

style:UIBarButtonItemStylePlain

target:self

action:@selector(goRed)]

autorelease];

// Add a right button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Blue"

style:UIBarButtonItemStylePlain

target:self

action:@selector(goBlue)]

autorelease];

}

Recipe: Adding a Segmented ControlThe preceding recipe showed how to use the two available button slots in your naviga-tion bar to build mini-menus. Recipe 3-2 expands on that idea by introducing a four-item UISegmentedControl and adding it to a navigation bar’s custom title view, asshown in Figure 3-11.When tapped, each item updates the main view with its number.

The key thing to pay attention to in this recipe is the momentary attribute assignedto the segmented control.This transforms the interface from a radio button style into anactual menu, where items can be selected independently and more than once. So after

95Recipe: Adding a Segmented Control

Page 119: I Phone Developer Cookbook

Recipe 3-2 Adding a Segmented Control to the Navigation Bar

- (void)loadView

{

// Set up the content view

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

contentView.autoresizesSubviews = YES;

contentView.textAlignment = UITextAlignmentCenter;

contentView.font = [UIFont fontWithName:@"Georgia" size:64.0f];

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

[contentView setEditable:NO];

self.view = contentView;

[contentView release];

96 Chapter 3 View Controllers

tapping item three, for example, you can tap it again.That’s an important behavior formenu interaction.

Unlike Recipe 3-1, all items in the segmented control trigger the same action (in thiscase, segmentAction:). Determine which action to take by querying the control for itsselectedSegmentIndex and matching that value to the appropriate behavior.

Figure 3-11 Adding a segmented control to the custom title viewenables you to build a multi-item menu. Notice that no items remain

highlighted even after an action takes place. (In this case, the three buttonwas pressed.)

Page 120: I Phone Developer Cookbook

Recipe 3-2 Continued

// Build the Segmented Control

NSArray *buttonNames = [NSArray arrayWithObjects:@"One", @"Two", @"Three",➥@"Four", nil];

UISegmentedControl* segmentedControl = [[UISegmentedControl alloc]➥initWithItems:buttonNames];

segmentedControl.momentary = YES;

[contentView setText:@""];

// Customize the Segmented Control

segmentedControl.autoresizingMask = UIViewAutoresizingFlexibleWidth;

segmentedControl.segmentedControlStyle = UISegmentedControlStyleBar;

segmentedControl.frame = CGRectMake(0, 0, 400, 30);

[segmentedControl addTarget:self action:@selector(segmentAction:)➥forControlEvents:UIControlEventValueChanged];

// Add the control to the navigation bar

self.navigationItem.titleView = segmentedControl;

[segmentedControl release];

}

-(void) segmentAction: (id) sender

{

switch([sender selectedSegmentIndex] + 1)

{

case 1: [contentView setText:@"\n\nOne"]; break;

case 2: [contentView setText:@"\n\nTwo"]; break;

case 3: [contentView setText:@"\n\nThree"]; break;

case 4: [contentView setText:@"\n\nFour"]; break;

default: break;

}

}

Recipe: Adding a UIToolbar to a Navigation BarThis next recipe builds on the essence of Recipe 3-2, which used a segment control inthe navigation bar and takes it a step further. Recipe 3-3 adds a UIToolbar into thenavigation item’s custom title view and adds it to the basic interface seen in Recipe 3-1.This accomplishes two things:

n It adds the power of a toolbar without taking up space at the bottom of the screen.n It works without interfering with the two core button slots of the navigation bar.

97Recipe: Adding a UIToolbar to a Navigation Bar

Page 121: I Phone Developer Cookbook

Clutter should be a concern when using this approach. Figure 3-12 shows the updated interface, including both the menu buttons and the toolbar while retaining aclean look.The toolbar uses flexible spaces to the left and right of the arrows to centerthe controls and maintain a simple presentation.Also beware of landscape presentation.The toolbar height, which works perfectly with the portrait navigation bar, might notmatch the landscape bar in newer SDK releases.

NoteThe two arrows use custom art, up.png and down.png, which are loaded from the applica-tion bundle at runtime and added to the toolbar buttons. Created in Photoshop, they’rebasic PNG images, white on a clear background, saved with transparency.

98 Chapter 3 View Controllers

Figure 3-12 A well-designed custom toolbar takes advantage of the space between navigation bar buttons

while freeing up room at the bottom of the screen.

Recipe 3-3 Adding a Toolbar into the Custom Title View

- (void)loadView

{

// Set up the text view to show the current value

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]applicationFrame]];

contentView.autoresizesSubviews = YES;

Page 122: I Phone Developer Cookbook

Recipe 3-3 Continued

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight);

contentView.editable = NO;

contentView.textAlignment = UITextAlignmentCenter;

contentView.font = [UIFont fontWithName:@"American Typewriter" size:120];

self.view = contentView;

[contentView release];

// Initialize at 50

[contentView setText:@"\n50"];

value = 50;

// The app style is black

self.navigationController.navigationBar.barStyle = UIBarStyleBlackTranslucent;

NSMutableArray *buttons = [[NSMutableArray alloc] init];

UIBarButtonItem *flexibleSpaceItem;

flexibleSpaceItem = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace

target:nil action:NULL];

[buttons addObject:flexibleSpaceItem];

[flexibleSpaceItem release];

// Add the up and down items

UIBarButtonItem *item;

item = [[UIBarButtonItem alloc]

initWithImage:[UIImage imageNamed:@"down.png"]

style:UIBarButtonItemStylePlain

target:self

action:@selector(decrement:)];

[buttons addObject:item];

[item release];

item = [[UIBarButtonItem alloc]

initWithImage:[UIImage imageNamed:@"up.png"]

style:UIBarButtonItemStylePlain target:self

action:@selector(increment:)];

[buttons addObject:item];

[item release];

flexibleSpaceItem = [[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace

99Recipe: Adding a UIToolbar to a Navigation Bar

Page 123: I Phone Developer Cookbook

Recipe 3-3 Continued

target:nil action:NULL];

[buttons addObject:flexibleSpaceItem];

[flexibleSpaceItem release];

UIToolbar *toolbar = [[UIToolbar alloc] init];

toolbar.barStyle = UIBarStyleBlackOpaque;

[toolbar setItems:buttons animated:YES];

[toolbar sizeToFit];

self.navigationItem.titleView = toolbar;

[toolbar release];

// Add a left button

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Red"

style:UIBarButtonItemStylePlain

target:self

action:@selector(goRed)]autorelease];

// Add a right button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Blue"

style:UIBarButtonItemStylePlain

target:self

action:@selector(goBlue)]autorelease];

}

-(void) increment: (id) sender

{

[contentView setText:[NSString stringWithFormat:@"\n%d", ++value]];

}

-(void) decrement: (id) sender

{

[contentView setText:[NSString stringWithFormat:@"\n%d", —value]];

}

Recipe: Navigating Between View ControllersIn addition to providing spiffy menus, navigation controllers do the job they aredesigned to do: managing hierarchy as you navigate between views. Recipe 3-4 introduces the navigation controller as an actual navigation controller, pushing views onthe stack.These views consist of three UIViewController subclasses, each with an

100 Chapter 3 View Controllers

Page 124: I Phone Developer Cookbook

Recipe 3-4 Drilling Through Views with UINavigationController

@implementation OrangeController

- (id) init

{

// The view controller title sets the name for back buttons

// once it’s been pushed

if (self = [super init]) self.title = @"Orange";

101Recipe: Navigating Between View Controllers

Figure 3-13 The navigation controller automatically creates Back and Forward buttons.

identifying color: lime, pink, and orange. In real use, you’d use more meaningful viewcontrollers.This sample demonstrates things at their simplest level. Here are a couple ofpoints to keep in mind about this recipe:

n The navigation controller automatically creates the Back button shown in Figure 3-13 (Lime) as an effect of pushing the new (Orange) controller from the(root) Lime controller.The rightmost button (Pink) triggers navigation to the next controller by calling pushViewController: animated:.When pushed, the nextBack button would read Orange.

n The interface works in both landscape and portrait orientations.When testing, youmay want to try out both styles. In the Simulator, choose Hardware, Rotate Left(Command-Left arrow) or Hardware, Rotate Right (Command-Right arrow) tosee this in action.

Page 125: I Phone Developer Cookbook

Recipe 3-4 Continued

return self;

}

- (void) loadButton

{

// Create the button that activates the switch

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Pink"

style:UIBarButtonItemStylePlain

target:self

action:@selector(switch:)] autorelease];

}

- (void) loadView

{

[super loadView];

contentView.backgroundColor = ORANGE;

}

- (void) switch: (id) sender

{

// On a button press, push the next controller into place

[[self navigationController] pushViewController:[[PinkController alloc] init]➥animated:YES];

}

@end

Popping Back to the RootAlthough you’ll usually want to pop to the previous view controller upon hitting theBack button, be aware that there are times you’ll want to pop the entire stack instead. Forexample, you might have just given an interactive quiz, or a museum visitor might havefinished his walking tour. For both cases, it makes little sense to move back up a longcomplex tree a screen at a time. Instead, use popToRootViewControllerAnimated: toreturn all the way back to the root or popToViewController: animated: to stopsomewhere short of the root.These pop view controllers until either the root or the spec-ified controller becomes the top controller, updating the display accordingly.

Loading a View Controller ArrayYou can also create and assign an NSArray of UIViewController objects to aUINavigationController’s viewControllers property.The top (that is, active)

102 Chapter 3 View Controllers

Page 126: I Phone Developer Cookbook

viewController occupies the last position (n-1) in that array while the root object livesat index 0.Arrays are handy when jumping within a conceptual tree. For example, youmight be navigating directories and then need to jump through a symbolic link tosomewhere else. By setting the entire array, you avoid the detail work of popping andthen pushing the stack.

View controller arrays also help restore previous states after quitting and then return-ing to an application. Store a state list to your local defaults and then re-create the samearray on launch.Assign the re-created array to viewControllers and you return youruser to the same place he or she left from.

Tab BarsThe UITabBarController class enables users to move between multiple view controllers and to customize the bar at the bottom of the screen.This is best seen in theYouTube and iPod applications. Both offer one-tap access to different views, and bothoffer a More button leading to user selection and editing of the bottom bar.

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 atab bar. Just set the bar’s viewControllers property. It really is that simple. CocoaTouch does all the rest of the work for you. Set allowsCustomizing to YES to enableuser reordering of the bar.

Recipe 3-5 creates 11 simple view controllers of the BrightnessController class.This class builds a UIView and sets it background to a specified gray level, in this casefrom 0% to 100% in steps of 10%.These 11 view controllers, in the form of an array,become the options a user can navigate through and select from, as shown in Figure 3-14.

Notice that this recipe adds those 11 controllers twice.The first time assigns them tothe list of view controllers available to the user:

tbarController.viewControllers = controllers;

The second time specifies that the user can select from the entire list when interac-tively customizing the bottom tab bar:

tbarController.customizableViewControllers = controllers;

The second line is optional, the first mandatory.After setting up the view controllers,you can add all or some to the customizable list. If you don’t, you’ll still be able to seethe extra view controllers using the More button, but users won’t be able to includethem in the main tab bar on demand.

Note that art appears inverted in color on the More screen.According to Apple, thisis the expected and proper behavior.They have no plans to change this. It does providean interesting view contrast when your 100% white swatch appears as pure black on thatscreen.

103Tab Bars

Page 127: I Phone Developer Cookbook

Recipe 3-5 Creating a Tab View Controller

@interface BrightnessController : UIViewController

{

UIView *contentView;

int brightness;

}

@end

@implementation BrightnessController

-(BrightnessController *) initWithBrightness: (int) aBrightness

{

self = [super init];

brightness = aBrightness;

self.title = [NSString stringWithFormat:@"%d%%", brightness * 10];

[self.tabBarItem initWithTitle:self.title image:createImage(((float) brightness / 10.0f)) tag:NULL];

return self;

}

- (void)loadView

{

// Create each view with the given brightness

contentView = [[UIView alloc] init];

float percent = brightness * 0.1;

104 Chapter 3 View Controllers

Figure 3-14 Tab bar controllers enable users to pick view controllers froma 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).

Page 128: I Phone Developer Cookbook

Recipe 3-5 Continued

contentView.backgroundColor = [UIColor colorWithRed:percent green:percent➥blue:percent alpha:1.0];

contentView.autoresizesSubviews = YES;

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

self.view = contentView;

[contentView release];

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

@interface SampleAppDelegate : NSObject <UIApplicationDelegate,➥UITabBarControllerDelegate> {

}

@end

@implementation SampleAppDelegate

- (void)applicationDidFinishLaunching:(UIApplication *)application {

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

// Create the array of UIViewControllers

NSMutableArray *controllers = [[NSMutableArray alloc] init];

for (int i = 0; i < 11; i++) {

BrightnessController *bControl = [[BrightnessController alloc]initWithBrightness:i];

UINavigationController *nav = [[UINavigationController alloc]initWithRootViewController:bControl];

nav.navigationBar.barStyle = UIBarStyleBlackTranslucent;

[controllers addObject:nav];

[bControl release];

[nav release];

}

UITabBarController *tbarController = [[UITabBarController alloc] init];

tbarController.viewControllers = controllers;

tbarController.customizableViewControllers = controllers;

105Tab Bars

Page 129: I Phone Developer Cookbook

Recipe 3-5 Continued

tbarController.delegate = self;

tbarController.allowsCustomizing = YES;

[window addSubview:tbarController.view];

[window makeKeyAndVisible];

[controllers release];

}

- (void)dealloc {

[super dealloc];

}

@end

SummaryThis chapter introduced the UIViewController, UINavigationController, andUITabBarController classes and showed how to use them.These classes handle view management and user navigation. Between them, developers can expand virtualinteraction space and create multipage interfaces as demanded by their applications.

Between the walk-through and recipes, you’ve learned how you can connect viewcontrollers to interfaces designed in Interface Builder and how to create interfacesentirely within Xcode. Before moving on to the next chapter, here are a few points toconsider about view controllers:

n Interface Builder excels at laying out the content of UIView instances. Use its tools to connect those instances to the view controllers in your program and use Interface Builder to refine WYSIWYG-style interfaces like the temperatureconverter example covered in this chapter.

n Know when Interface Builder isn’t the right solution.When you’re building tabbars and navigation controllers with minimal window design (such as for table-based or text-based applications), you don’t especially need IB’s view layout tools.When skipping IB, make sure to delete the XIB file from your project and removethe main NIB window key from Info.plist.

n Use navigation trees to build hierarchical interfaces.They work great for looking atfile structures or building a settings tree.When you think “disclosure view” or“preferences,” consider pushing a new controller onto a navigation stack.

n Don’t be afraid to use conventional UI elements in unconventional ways.A largepart of this chapter covered innovative uses for the UINavigationControllerthat didn’t involve any navigation.The tools are there for the using.

106 Chapter 3 View Controllers

Page 130: I Phone Developer Cookbook

4Alerting Users

At times, you need to grab your user’s attention. New messages might arrive or thesystem status might change.You might want to tell your user that there’s going to be await before anything more happens—or that the wait is over and it’s time to come backand pay attention.The iPhone offers many ways to provide that heads-up to the user:from alerts and progress bars to audio pings and status bar updates. In this chapter, youdiscover how to build these indications into your applications and expand your user-alertvocabulary.You’re about to see real-life examples that showcase these classes and discoverhow to make sure your user pays attention at the right time.

Talking Directly to Your User Through AlertsUIActionSheet and UIAlertView objects speak to your user.They pop up or scroll inabove other views to deliver their message.These lightweight classes add two-way dialogto your apps.Alerts visually “speak” to users and can prompt them to reply.You presentyour alert onscreen, get user acknowledgment, and then dismiss the alert to move onwith other tasks.

If you think that alerts are nothing more than messages with an attached OK button(as in Figure 4-1), think again. UIAlertSheet objects provide incredible versatility—assuming that Apple continues to let you access that incredible versatility.With alertsheets, you can actually build menus, text input, queries, and more. Unfortunately, muchof this behavior falls into the undocumented or barely supported category. In this chap-ter’s recipes, you see how to create a wide range of useful alerts that you can use in yourown programs.

Much of the functionality covered in this chapter was removed from the official SDKyet remains within the public frameworks. Because this chapter relies so heavily on“unofficial” calls, think long and hard about the trade-offs between reliability and powerprogramming.Apple discourages developers from using private routines because theychange them at will. But the bottom line is the same story as with Mac OS X. If youfigure out how to use private routines in public frameworks, you can use them.You crossthe line when you start accessing private frameworks in your applications.

Page 131: I Phone Developer Cookbook

NoteIn early releases of the iPhone firmware, UIActionSheet and UIAlertView were implemented by the same class, UIAlertSheet. This one class provided both pop-upalert and menu functionality. Then Apple replaced alert sheets with UIModalView andsubclassed these new objects from that base class. Later, Apple removed UIModalView,and in new versions of the SDK, UIActionSheet and UIAlertView are no longerderived from that class. (They both descend from UIView.) Like their predecessors, theyremain siblings in their behavior and use similar underlying technology to present them-selves onscreen.

Logging Your ResultsDue to their sample nature, most of these recipes use printf to output their results into aviewable format. During the initial SDK beta period,printf provided the most reliableway to examine internal program data.NSLog() is now fully implemented and stable inrecent iPhone SDK releases. In addition, here are a few tricks you may want to use.

Redirecting stderrThe standard freopen() function redirects stderr output to a log file of your choice.Supply it a local path. Here, logPath is a standard NSString such as @"/tmp/mylog",which is converted to a local file system representation.After this call, all stderr results(this includes NSLog) go directly to your log file. Use "w" (write) rather than "a"(append) to restart the log when your program runs:

freopen([logPath fileSystemRepresentation], "a", stderr);

Building a Custom Log FunctionBuilding your own log function allows you to add functionality that goes beyond stan-dard NSLog. Here is a basic log function that mimics NSLog‘s functionality. It’s not hardto adapt this to produce, for example, an alert notification or a text view update ratherthan a print to stderr:

#include <stdarg.h>void dolog(id formatstring,...){

va_list arglist;if (formatstring){

va_start(arglist, formatstring);id outstring = [[NSString alloc] initWithFormat:formatstring argu-

ments:arglist];fprintf(stderr, "%s\n", [outstring UTF8String]);[outstring release];va_end(arglist);

}

}

108 Chapter 4 Alerting Users

Page 132: I Phone Developer Cookbook

Building AlertsTo create alert sheets, allocate a UIAlertView object. Initialize it with a title and a but-ton array, along with a few other options.The title is an NSString, and the button arrayincludes NSStrings, where each string represents a single button. Listing 4-1 creates avery simple alert, as shown in Figure 4-1.

When working with alerts, space is often at a premium. Placing buttons on a singleline minimizes the alert height and is the default presentation.When you define morebuttons, they’re added side by side. So, limit the number of buttons you add at any timeto no more than three or four. Fewer buttons work better, one or two being ideal.

UIAlertView objects provide no visual “default” button highlights. In the iPhonecontext, button highlights are more confusing than helpful. (For example, should userspress on the light button or the dark?) There’s no way to match the interface to userexpectations because the presentation is ambiguous. So unlike OS X,Apple has omittedhighlights in newer iPhone firmware.

NoteThe Cancel button appears at the bottom of menus and at the left of pop-up alerts.

Listing 4-1 Creating a Basic Alert

UIAlertView *baseAlert = [[UIAlertView alloc]

initWithTitle:@"Alert" message:@""

delegate:self cancelButtonTitle:nil

otherButtonTitles:@"OK", nil];

109Talking Directly to Your User Through Alerts

Figure 4-1 Use the UIAlertViewclass to build simple pop-up alerts.

Page 133: I Phone Developer Cookbook

Unless you have some compelling reason to do otherwise, set the delegateto your primary UIViewController object.The delegate implements theUIAlertViewDelegate protocol. UIAlertView instances require this delegate supportto handle button taps, at a minimum.

Delegate methods enable you to customize your responses when different buttons arepressed.You can actually omit that delegate support if all you need to do is throw upsome message with an OK button.

After the user has seen and interacted with your alert, they raise the following dele-gate method call: alertView: clickedButtonAtIndex:. From this call, you candetermine which button was pressed and recover any options that have been set on thesheet. Button numbering begins with zero.

Displaying the AlertUse the show method to tell your alert to appear onscreen (that is, [myAlert show]).When shown, the alert works in a modal fashion.That is, it dims the screen behind itand blocks user interaction with your application.This modal interaction continues untilyour user acknowledges the alert through a button tap, typically by selecting OK orCancel.

NoteAfter creating the alert sheet, you may customize the alert by updating its message. That’soptional text that appears below the alert title and above its buttons. Other customizableproperties include title, delegate, and visible.

Recipe: Creating Multiline Button DisplaysBy default, iPhone alerts present their buttons in a single line. Figure 4-2 shows this stan-dard look on the left.You can override the single row presentation using an undocu-mented call.This call creates a multiline alert, as shown on the right. UsesetNumberOfRows: to build alerts that use one row per button, as demonstrated inListing 4-2. Supply a single argument, an integer, specifying the requested number ofrows.

NoteSee Chapter 1, “Introducing the iPhone SDK,” for further discussion about using undocu-mented calls and features in your programs.

Earlier versions of the iPhone software worked in reverse.The standard was originallymultiple rows, and you had to use setNumberOfRows: to get the phone to move thebuttons onto a single line.

The sample created by Recipe 4-1 pop ups its alert midscreen, dimming the screenaround it. Because it runs modal by default, everything waits until the user taps the OK

110 Chapter 4 Alerting Users

Page 134: I Phone Developer Cookbook

button. Once tapped, the alert closes, passing control to the modalView:clickedButtonAtIndex: delegate method.This method, as defined here, does littlemore than print out the tapped button’s number.

NoteYou can disable background dimming using setDimsBackground:NO. This is anotherundocumented call. I can’t recommend it for general use. It breaks the Apple human inter-face standards and may confuse your user as to whether the background can be tappedduring the lifetime of the alert.

111Recipe: Creating Multiline Button Displays

Figure 4-2 An undocumented UIKit call allows you to select whether tolay out your buttons in a row or a column.

Recipe 4-1 Creating UIAlertViews with One Button per Line

- (void) presentSheet

{

UIAlertView *baseAlert = [[UIAlertView alloc]

initWithTitle:@"Alert"

message:@"Please select a button"

delegate:self cancelButtonTitle:nil

otherButtonTitles:@"One", @"Two", @"Three", nil];

[baseAlert setNumberOfRows:3];

[baseAlert show];

}

Page 135: I Phone Developer Cookbook

Recipe 4-1 Continued

- (void)alertView:(UIAlertView *)alertView➥clickedButtonAtIndex:(NSInteger)buttonIndex

{

printf("User Pressed Button %d\n", buttonIndex + 1);

[alertView release];

}

Recipe: Autotimed No-Button AlertsNo-button alerts present a special challenge because they do not properly call back to adelegate method.They do not autodismiss, even when tapped. Figure 4-3 shows a simpleno-button alert.As you can see from this screenshot, removing buttons produce awkward-looking alerts.You can enhance presentation by adjusting the message text.Add carriagereturns (@“\n”) to balance the bottom (where buttons normally go) with the spacing atthe top.

Because alerts run modally, you need to add a safeguard to ensure that the alert goesaway at some point and users can continue using their iPhone.A simple NSTimer thatdismisses the alert after a set period of time must return control back to the standardGUI. Recipe 4-2 creates an alert and dismisses it using a timer and either the undocu-mented dismiss method or the UIAlertView class’s dismissWithClickedButtonIndex:animated: method.

112 Chapter 4 Alerting Users

Figure 4-3 Use the undocumented dismiss method to return control toyour program after displaying a button-free UIAlertView. It helps to

balance the extra bottom space where the missing buttons would go byadding carriage returns to the message property.

Page 136: I Phone Developer Cookbook

Recipe 4-2 Creating No-Button UIAlertViews with Timer Fail-Safe

- (void) performDismiss: (NSTimer *)timer

{

[baseAlert dismissWithClickedButtonIndex:0 animated:NO];

}

- (void) presentSheet

{

baseAlert = [[UIAlertView alloc]

initWithTitle:@"Alert"

message:@"Message to user with asynchronous information"

delegate:self cancelButtonTitle:nil

otherButtonTitles: nil];

[NSTimer scheduledTimerWithTimeInterval:3.0f target:self selector:➥@selector(performDismiss:)

userInfo:nil repeats:NO];

[baseAlert show];

}

Recipe: Soliciting Text Input from the UserLeaving official SDK calls aside, alert views provide an especially simple way to promptyour user for text.The UIAlertView class takes total command of presenting and dis-missing an associated keyboard.All you have to do is add the text input fields, tell thealert how to handle them, and let it take care of the rest.

To add a text field, use the undocumented addTextFieldWithValue: label: call.Send the default text as the first argument and the text that displays in an otherwise-empty field as the second. Figure 4-4 shows both a filled and empty field.

You can recover each field via [alert textFieldAtIndex:0]and then pull the textfrom the text field with text. Because each field is addressable, you can set its attributesbefore presenting the alert. Customize the autocaps, the autocorrection, and the preferredkeyboard, as demonstrated in Recipe 4-3.

Space is an important consideration. If you manage your space carefully, you can placethe alert onscreen leaving enough room for the keyboard to present itself. By skipping atitle and body text, you can fit three (but not four) text entry fields onscreen at once.Two fields is a more practical maximum, as Figure 4-4 reveals.

Recipe 4-3 Using UIAlertView to Solicit Text from Users

- (void)alertView:(UIAlertView *)alertViewclickedButtonAtIndex:(NSInteger)buttonIndex

{

printf("User Pressed Button %d\n", buttonIndex + 1);

printf("Text Field 1: %s\n", [[[modalView textFieldAtIndex:0] text]➥cStringUsingEncoding:1]);

113Recipe: Soliciting Text Input from the User

Page 137: I Phone Developer Cookbook

Recipe 4-3 Continued

printf("Text Field 2: %s\n", [[[modalView textFieldAtIndex:1] text]➥cStringUsingEncoding:1]);

[alertView release];

}

- (void) presentSheet

{

UIAlertView *alert = [[UIAlertView alloc]

initWithTitle: @"Enter Information"

message:@"Specify the Name and URL"

delegate:self

cancelButtonTitle:@"Cancel"

otherButtonTitles:@"OK", nil];

[alert addTextFieldWithValue:@"" label:@"Enter Name"];

[alert addTextFieldWithValue:@"http://" label:@"Enter URL"];

// Name field

UITextField *tf = [alert textFieldAtIndex:0];

tf.clearButtonMode = UITextFieldViewModeWhileEditing;

tf.keyboardType = UIKeyboardTypeAlphabet;

tf.keyboardAppearance = UIKeyboardAppearanceAlert;

114 Chapter 4 Alerting Users

Figure 4-4 Using careful space management and omitting thetitle and body text, you can add up to three text entry fields to a

UIAlertSheet at once. You probably want to limit yourUIAlertSheets to one or two text fields.

Page 138: I Phone Developer Cookbook

Recipe 4-3 Continued

tf.autocapitalizationType = UITextAutocapitalizationTypeWords;

tf.autocorrectionType = UITextAutocorrectionTypeNo;

// URL field

tf = [alert textFieldAtIndex:1];

tf.clearButtonMode = UITextFieldViewModeWhileEditing;

tf.keyboardType = UIKeyboardTypeURL;

tf.keyboardAppearance = UIKeyboardAppearanceAlert;

tf.autocapitalizationType = UITextAutocapitalizationTypeNone;

tf.autocorrectionType = UITextAutocorrectionTypeNo;

[alert show];

}

Recipe: Presenting Simple MenusWhen it comes to menus, UIActionSheet instances supply the iPhone answer.Theyslide choices, basically a list of buttons representing possible actions, onto the screen andwait for the user to respond.Action sheets are different from pop-ups. Pop-ups standapart from the interface and are better used for demanding attention. Menus slide into aview and better integrate with ongoing application work. Cocoa Touch supplies twoways to present menus:

n showInView. Presenting your sheet in a view is pretty much the ideal way to usemenus and is the method used here.This method slides the menu up from theexact bottom of the view (see Figure 4-5).

n showFromToolBar: and showFromTabBar. When working with toolbars, tabbars, or any other kinds of bars that provide those horizontally grouped buttonsthat you see at the bottom of many applications, these methods align the menuwith the top of the bar and slide it out exactly where it should be.

Recipe 4-4 shows how to initialize and present a simple UIActionSheet instance. Itsinitialization method introduces a concept missing from UIAlertView: the destructivebutton. Colored in red, a destructive button indicates an action from which there is noreturn, such as permanently deleting a file (see Figure 4-5). Its bright red color warns theuser about the choice. Obviously, this option should be used sparingly.

NoteMissing in action sheets, at least as far as the official SDK is concerned, is theUIAlertView’s message. Action sheet instances do not officially support adding a mes-sage in addition to presenting a title. In reality, messages are implemented, even if they’renot especially pretty. Use the undocumented setMessage: call to add a message to yourmenu. When used, messages appear below the sheet title, in slightly larger text.

115Recipe: Presenting Simple Menus

Page 139: I Phone Developer Cookbook

Recipe 4-4 Presenting a Menu

- (void)actionSheet:(UIActionSheet *)actionSheetclickedButtonAtIndex:(NSInteger)buttonIndex

{printf("User Pressed Button %d\n", buttonIndex + 1);

[actionSheet release];

}

- (void) presentSheet

{

UIActionSheet *menu = [[UIActionSheet alloc]

initWithTitle: @"File Management"

delegate:self

cancelButtonTitle:@"Cancel"

destructiveButtonTitle:@"Delete File"

otherButtonTitles:@"Rename File", @"Email File", nil];

[menu showInView:contentView];

}

116 Chapter 4 Alerting Users

Figure 4-5 Use showInView: to create simple menu presentations. Themenu slides in from the bottom of the view. Although the “Delete File”menu button appears gray here, it is red on your iPhone and indicatespermanent actions with possible negative consequences to your users.

Page 140: I Phone Developer Cookbook

“Please Wait”: Showing Progress to Your UserWaiting is an intrinsic part of the computing experience and will remain so for the fore-seeable future. It’s your job as a developer to communicate that fact to your users. CocoaTouch provides several classes that tell your user to wait for a process to complete.Theseprogress indicators come in two forms: as a spinning wheel that persists for the durationof its presentation; and as a bar, which fills from left to right as your process moves for-ward from start to end.The classes that provide these indications are as follows:

n UIActivityIndicatorView. A progress indicator offers a spinning circle thattells your user to wait without providing specific information about its degree ofcompletion.The iPhone activity indicator is small, but its live animation catchesthe user’s eye and is best-suited for quick disruptions in a normal application.

n UIProgressView. This view presents a progress bar.The bar provides concretefeedback as to how much work has been done and how much remains whileoccupying a relatively small onscreen space. It presents as a thin, horizontal rectan-gle that fills itself from left to right as progress takes place.This classic user interfaceelement works best for long delays, where users want to know to what degree thejob has finished.

And from the undocumented features point of view, you have the following:n UIProgressHUD. The undocumented “heads up display” version of the progress

indicator floats over all other views within a window and adds a status message tothe basic progress indicator.The HUD provides a middle ground between the barand the simple indicator.You do not need to quantify your progress, the way youdo with progress bars, but the associated text enables you to narrate your progress.Progress HUDs work especially well when you want to indicate what’s happenedalong a process (for example,“Contacting Server,”“Authenticating,”“RequestingData,” and so forth).

Recipe: Invoking the Basic UndocumentedUIProgressHUDThe UIProgressHUD class overlays your window with a status dialog.This dialog containsa rotating progress indicator and a short message that you specify.As far as programminggoes, all you have to do is instantiate the object, initialize it using your main window,and tell it to go by sending show:YES. Figure 4-6 shows a UIProgressHUD display inaction, and Recipe 4-5 shows the code that created that display.

UIProgressHUD enables you to update its text as needed. Send a new setText:message at any time.When you’ve finished, dismiss it using show:NO.You can adjust thefont size via setFontSize: if needed, but I urge you to keep your messages short andto the point.

117Recipe: Invoking the Basic Undocumented UIProgressHUD

Page 141: I Phone Developer Cookbook

NoteIt’s disappointing that Apple did not include this as a standard SDK class. I’ve presentedthe workaround here. By the time this book publishes, hopefully Apple will have added itback to Cocoa Touch.

118 Chapter 4 Alerting Users

Figure 4-6 The UIProgressHUD’s name means that it is an HUD. It adds a progress overlay above your window. You tell it

when to show, when to hide, and what to say.

When using HUD displays with data intense code, you’ll want to let it run inits own thread so the UI updates do not block.To do this, use NSThread’sdetatchThreadSelector: toTarget: withObject: method. Use separate threadsto start it going, to update the HUD’s text, and to dismiss the HUD when finished.

Recipe 4-5 Creating a UIProgressHUD for Your Window

@interface UIProgressHUD : NSObject

- (void) show: (BOOL) yesOrNo;

- (UIProgressHUD *) initWithWindow: (UIView *) window;

- (void) setText: (NSString *) theText;

@end

- (void) killHUD: (id) aHUD

{

[aHUD show:NO];

[aHUD release];

Page 142: I Phone Developer Cookbook

Recipe 4-5 Continued

}

- (void) presentSheet

{

id HUD = [[UIProgressHUD alloc] initWithWindow:[contentView superview]];

[HUD setText:@"Downloading File. Please wait."];

[HUD show:YES];

[self performSelector:@selector(killHUD:)

withObject:HUD afterDelay:5.0];

}

Recipe: Using UIActivityIndicatorViewUIActivityIndicatorView instances offer lightweight views that display a standardrotating progress wheel.The keyword to keep in mind when working with these viewsis small. The sharpest display for most indicator styles occurs at a view size of just 20 by20 pixels.Any larger, and the indicator starts to blur. Figure 4-7 shows a 40-pixel version.

You need not center the indicator on the screen. Place it wherever it works best for you.As a clear-backed view, the indicator will blend over whatever backdrop view lies behindit.The predominant color of that backdrop helps select which style of indicator to use.

For general use, just add the activity indicator as a subview to the window, view, tool-bar or navigation bar you want to overlay.Allocate the indicator and initialize it with aframe, preferably centered within whatever parent view you’re using.

Start the indicator in action by updating its animating property to YES.To stop, setthe property to NO. Cocoa Touch takes care of the rest, hiding the view when not in use.

The iPhone offers several different styles of the UIActivityIndicatorView class.UIActivityIndicatorViewStyleWhite and UIActivityIndicatorViewStyleGray

are the cleanest.The white version looks best against a black background, and the gray(shown in Figure 4-7) looks best against white. It’s a thin, sharp style.Take care whenchoosing whether to use white or gray.An all-white presentation will not show at allagainst a white backdrop. Unfortunately,UIActivityIndicatorViewStyleWhiteLargeis available only for use on dark backgrounds. It provides the largest, clearest indicator.Recipe 4-6 shows the code used to create these simple UIActivityIndicatorViewinstances.

Recipe 4-6 Adding a UIActivityIndicatorView to Your Program

- (void) performAction

{

if (progressShowing) [activityIndicator stopAnimating];else [activityIndicator startAnimating];

progressShowing = !progressShowing;

}

- (void)loadView

{

119Recipe: Using UIActivityIndicatorView

Page 143: I Phone Developer Cookbook

Recipe 4-6 Continued

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view = contentView;

contentView.backgroundColor = [UIColor whiteColor];

[contentView release];

// Add an action button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Do It"

style:UIBarButtonItemStylePlain

target:self

action:@selector(performAction)]autorelease];

// Add the progress indicator but do not start it

progressShowing = NO;

activityIndicator = [[UIActivityIndicatorView alloc]➥initWithFrame:CGRectMake(0.0f, 0.0f, 32.0f, 32.0f)];

[activityIndicator setCenter:CGPointMake(160.0f, 208.0f)];

[activityIndicator➥setActivityIndicatorViewStyle:UIActivityIndicatorViewStyleGray];

[contentView addSubview:activityIndicator];

[activityIndicator release];

}

120 Chapter 4 Alerting Users

Figure 4-7 The UIActivityIndicatorView

class provides a simple rotating wheel that’s meant to display at relatively small sizes.

Page 144: I Phone Developer Cookbook

Recipe: Building a UIProgressViewProgress views enable your users to follow task progress as it happens rather than justsaying “Please wait.”They present bars that fill from left to right.These bars indicate thedegree to which a task has finished. Progress bars work best for long waits where provid-ing state feedback enables your users to retain the feel of control.

To create a progress view, allocate it and set its frame.To use the bar, issuesetProgress:.This takes one argument, a floating-point number that ranges between0.0 (no progress) and 1.0 (finished). Progress view bars come in two styles: basic white orlight gray.The setStyle: method chooses the kind you prefer.

Unlike the other kinds of progress indicators, it’s completely up to you to show andhide the progress bar’s view.There’s no setVisible: method. I like adding progress barsto action sheets.This simplifies both bringing them onto the screen and dismissing them.Another advantage is that when alert sheets display, the rest of the screen dims.Thisforces a modal presentation as your task progresses. Users cannot interact with the GUIuntil you dismiss the alert. Recipe 4-7 shows a UIActionSheet/UIProgressView sam-ple that produces the display shown in Figure 4-8.

121Recipe: Building a UIProgressView

Figure 4-8 Use UIProgressView instances to trackprogress over an extended delay. Adding them to a

UIActionSheet simplifies their presentation and dismissal.

This recipe uses the undocumented setNumberOfRows: call.To stay within SDKstandards, add a bunch of new lines (\n) to the title.

Page 145: I Phone Developer Cookbook

NoteWhen embedding a progress bar into an action sheet, you need to implement theUIActionSheet delegate protocol. Adding a button click method satisfies the requiredmethods for that protocol, even if the method itself does nothing.

Recipe 4-7 Adding a Progress Bar to an Alert Sheet

@interface UIActionSheet (extended)

- (void) setNumberOfRows: (NSInteger) rows;

@end

@implementation HelloController

// This callback fakes progress via setProgress:

- (void) incrementBar: (id) timer

{

amountDone += 1.0f;

[progbar setProgress: (amountDone / 20.0)];

if (amountDone > 20.0) {[baseSheet dismissWithClickedButtonIndex:0➥animated:YES]; [timer invalidate];}

}

// Dismiss the action sheet backdrop used here

- (void)actionSheet:(UIActionSheet *)actionSheetclickedButtonAtIndex:(NSInteger)buttonIndex

{

[actionSheet release];

}

// Load the progress bar onto an actionsheet backing

- (void) presentSheet

{

if (!baseSheet) {

baseSheet = [[UIActionSheet alloc]

initWithTitle:@"Please Wait"

delegate:self

cancelButtonTitle:nil

destructiveButtonTitle: nil

otherButtonTitles: nil];

[baseSheet setNumberOfRows:5];

[baseSheet setMessage:@"Updating Internal Databases"];

progbar = [[UIProgressView alloc] initWithFrame:CGRectMake(50.0f, 70.0f,➥220.0f, 90.0f)];

[progbar setProgressViewStyle: UIProgressViewStyleDefault];

122 Chapter 4 Alerting Users

Page 146: I Phone Developer Cookbook

Recipe 4-7 Continued

[baseSheet addSubview:progbar];

[progbar release];

}

// Create the demonstration updates

[progbar setProgress:(amountDone = 0.0f)];

[NSTimer scheduledTimerWithTimeInterval: 0.5 target: self selector:➥@selector(incrementBar:) userInfo: nil repeats: YES];

[baseSheet showInView:contentView];

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view = contentView;

contentView.backgroundColor = [UIColor whiteColor];

[contentView release];

// Add an action button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Do It"

style:UIBarButtonItemStylePlain

target:self

action:@selector(presentSheet)]autorelease];

}

Recipe: Adding Custom, Tappable OverlaysWhen communicating to users, you need not limit yourself to standard (even whenundocumented) UIKit objects. Fading an overlay onto a view offers unlimited possibili-ties.You can throw up text, images, animation, and what have you.You can make theoverlay interactive (as shown in this recipe) or allow any taps to fall through to the inter-face below.You can add simple status information (think white, gray, or black text on atransparent background) or you can build an elaborate presentation.The choice is upto you.

Figure 4-9 shows the custom “Please Wait” overlay built in Recipe 4-8.AddingUIView animations, as discussed in Chapter 2,“Views,” smoothes this overlay’s entranceand exit presentations.A tap on the overlay dismisses it, returning you to the interfacebeneath.This overlay is, as you can see, busy and intended to go over a clean, brightbackground.To use this kind of overlay with a complicated interface, you might fill in

123Recipe: Adding Custom, Tappable Overlays

Page 147: I Phone Developer Cookbook

the transparent areas with a translucent white, which would partially hide the interfaceand add a backdrop that allows any message to remain visible.

If you want to add an overlay like this and provide your own UIProgressHUD-likebackdrop, use care that you match Apple’s overall graphic design. Users are particularlysensitive to errors in custom interface elements.

124 Chapter 4 Alerting Users

Figure 4-9 Combine transparency withanimation to create view overlays that pop.

Recipe 4-8 Creating a Custom Animated UIView Overlay

@interface PleaseWaitView : UIImageView

@end

@implementation PleaseWaitView

- (PleaseWaitView *) initWithFrame: (CGRect) rect

{

self = [super initWithFrame:rect];

[self setImage:[UIImage imageNamed:@"PlsWait.png"]];

UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(40.0f,➥300.0f, 60.0f, 60.0f)];

[self addSubview:imgView];

[imgView release];

// load in the animation cells for the butterfly

Page 148: I Phone Developer Cookbook

Recipe 4-8 Continued

NSMutableArray *bflies = [[NSMutableArray alloc] init];

for (int i = 1; i <= 17; i++) {

NSString *cname = [NSString stringWithFormat:@"bf_%d.png", i];

UIImage *img = [UIImage imageNamed:cname];

if (img) [bflies addObject:img];

}

// begin the animation

[imgView setAnimationImages:bflies];

imgView.animationDuration = 0.75f;

[imgView startAnimating];

[bflies release];

return self;

}

- (void)touchesBegan: (NSSet *) touches withEvent:(UIEvent *)event

{

// fade away the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:1.0];

[self setAlpha:0.0f];

// Complete the animation

[UIView commitAnimations];

[pleaseWait setUserInteractionEnabled:YES];

}

@end

@interface HelloController : UIViewController

{

UIView *contentView;

PleaseWaitView *pleaseWait;

}

@end

@implementation HelloController

CGPoint randomPoint() { return CGPointMake(random() % 256, random() % 256); }

- (void) moveButterfly: (NSTimer *) timer

125Recipe: Adding Custom, Tappable Overlays

Page 149: I Phone Developer Cookbook

Recipe 4-8 Continued

{

if ([pleaseWait alpha] == 0.0f) {[timer invalidate]; return;}

// Create an animation block around moving the butterfly to a new origin

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:1.0];

// Move the butterfly

UIImageView *iv = [[pleaseWait subviews] objectAtIndex:0];

CGRect frame = [iv frame];

frame.origin = randomPoint();

[iv setFrame:frame];

// Complete the animation

[UIView commitAnimations];

}

- (void) presentSheet

{

if ([pleaseWait alpha] != 0.0f) return;

// Create an animation block around fading in the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:1.0];

[pleaseWait setAlpha:1.0f];

// Complete the animation

[UIView commitAnimations];

// Give life to the butterfly

[NSTimer scheduledTimerWithTimeInterval: 4.0f target: self selector: ➥@selector(moveButterfly:)

userInfo: nil repeats: YES];

}

- (id) init

{

if (self = [super init]) self.title = @"Hello World";

return self;

126 Chapter 4 Alerting Users

Page 150: I Phone Developer Cookbook

Recipe 4-8 Continued

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view = contentView;

contentView.backgroundColor = [UIColor whiteColor];

[contentView release]; // reduce retain count by one

// Add an action button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Do It"

style:UIBarButtonItemStylePlain

target:self

action:@selector(presentSheet)]autorelease];

// Create and hide the "Please Wait" view

pleaseWait = [[PleaseWaitView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView addSubview:pleaseWait];

[pleaseWait setAlpha:0.0f];

}

-(void) dealloc

{

[pleaseWait release];

[contentView release];

[super dealloc];

}

@end

Recipe: Building a Scroll-Down AlertBuilding off the preceding recipe’s animation block, you can apply the same kind ofapproach to create a more traditional scroll-down alert. Recipe 4-9 animates a view’sframe location to create the illusion that a view has scrolled onscreen.The frame startsoffscreen and animates itself onscreen to present itself as shown in Figure 4-10.Whenthe user clicks OK, the process reverses.You can use this method to build any kind ofalert.

The view in this recipe contains a title, a block of text, and a simple button.Add anyUIView elements or controls as needed to your own implementation.An example inChapter 5,“Basic Tables,” builds on this recipe by adding a user-selectable scrolling tableto the slide-down view.

127Recipe: Building a Scroll-Down Alert

Page 151: I Phone Developer Cookbook

Recipe 4-9 Building a Custom Slide-Down Alert

// Create a color of blue that mimics the official gray highlighting

UIColor *sysBlueColor(float percent) {

float red = percent * 255.0f;

float green = (red + 20.0f) / 255.0f;

float blue = (red + 45.0f) / 255.0f;

if (green > 1.0) green = 1.0f;

if (blue > 1.0f) blue = 1.0f;

return [UIColor colorWithRed:percent green:green blue:blue alpha:1.0f];

}

@interface TopAlert : UIView

{

UILabel *title, *message;

}

- (void) setTitle: (NSString *)titleText;

- (void) setMessage: (NSString *)messageText;

@end

@implementation TopAlert

- (void) setTitle: (NSString *)titleText

{

128 Chapter 4 Alerting Users

Figure 4-10 Use UIViewAnimation tocreate custom scroll-down alert sheets.

Page 152: I Phone Developer Cookbook

Recipe 4-9 Continued

[title setText:titleText];

}

- (void) setMessage: (NSString *)messageText

{

[message setText:messageText];

}

- (TopAlert *) initWithFrame: (CGRect) rect

{

rect.origin.y = 20.0f - rect.size.height; // Place above status bar

self = [super initWithFrame:rect];

[self setAlpha:0.9];

[self setBackgroundColor: sysBlueColor(0.4f)];

// Add button

UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom]➥initWithFrame:CGRectMake(220.0f, 200.0f, 80.0f, 32.0f)];

[button setBackgroundImage:[UIImage imageNamed:@"whiteButton.png"]➥forState:UIControlStateNormal];

[button setTitle:@"Okay" forState: UIControlStateHighlighted];

[button setTitle:@"Okay" forState: UIControlStateNormal];

[button setFont:[UIFont boldSystemFontOfSize:14.0f]];

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

[self addSubview:button];

// Add title

title = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 8.0f, 320.0f, 32.0f)];

title.text = @"Declaration of Independence";

title.textAlignment = UITextAlignmentCenter;

title.textColor = [UIColor whiteColor];

title.backgroundColor = [UIColor clearColor];

// Alternative framed title:

// title.backgroundColor = [UIColor colorWithRed:1.0f green:1.0f blue:1.0f➥alpha:0.25f];

title.font = [UIFont boldSystemFontOfSize:20.0f];

[self addSubview:title];

[title release];

// Add message

message = [[UILabel alloc] initWithFrame:CGRectMake(20.0f, 40.0f, 280.0f,➥200.0f - 48.0f)];

message.text = @"When in the Course of human events, it becomes necessary for➥one people to dissolve the political bands which have connected them with➥another, and to assume among the powers of the earth, the separate and equal

129Recipe: Building a Scroll-Down Alert

Page 153: I Phone Developer Cookbook

Recipe 4-9 Continued

➥which the Laws of Nature and of Nature’s God entitle them, a decent respect➥to the opinions of mankind requires that they should declare the causes➥which impel them to the separation.";

message.textAlignment = UITextAlignmentCenter;

message.numberOfLines = 999;

message.textColor = [UIColor whiteColor];

message.backgroundColor = [UIColor clearColor];

message.lineBreakMode = UILineBreakModeWordWrap;

message.font = [UIFont systemFontOfSize:[UIFont smallSystemFontSize]];

[self addSubview:message];

[message release];

return self;

}

- (void) removeView

{

// Scroll away the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:0.5];

CGRect rect = [self frame];

rect.origin.y = -10.0f - rect.size.height;

[self setFrame:rect];

// Complete the animation

[UIView commitAnimations];

}

- (void) presentView

{

// Scroll in the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:0.5];

CGRect rect = [self frame];

rect.origin.y = 0.0f;

[self setFrame:rect];

// Complete the animation

[UIView commitAnimations];

}

130 Chapter 4 Alerting Users

Page 154: I Phone Developer Cookbook

Recipe 4-9 Continued

-(void) dealloc

{

[title release];

[message release];

[super dealloc];

}

@end

Recipe: Adding Status Bar ImagesThe bar at the top of your iPhone offers a wealth of status information.These applica-tion-triggered status symbols appear to the right of the time display. Familiar iconsinclude pause/play indicators, battery use, and so forth. UIApplication instances canadd existing images to the status bar using undocumented methods, but I’ve been unableto convince the status bar call to use data from the Application package itself.All statusbar images are stored in SpringBoard.app, in /System/Library/CoreServices/SpringBoard.app. So as you can guess, this is clearly marching into App Store–agreementboundary conditions.

This limitation has two profound consequences. First,you can only use this very limitedrange of images (they are just standard PNGs, using transparency) but also, second, it’s apretty bad move security-wise and just overall OS-wise to rely on adding custom imagesto your user’s System folder. It’s bad manners, and it’s not secure, and there are youragreements (and sandboxes!) between you and Apple that stand in the way.

Recipe 4-10 shows how to load and remove these images.To set an icon, calladdStatusBarImageNamed:, using one argument, the trimmed icon name without anextension.As a rule, there are two versions of each image, one for a black status bar (eachname starts with FSO_) and one for a white/gray status bar (names start with Default_).All changes last beyond quitting the program.That, too, indicates why you should usestatus bar updates sparingly.

When passed, for example, @“Airplane” (the airplane mode icon), SpringBoard displays Default_Airplane.png for a black status bar.You do not pass either theextension or the prefix. Remove the icon with removeStatusBarImageNamed:.Figure 4-11 shows the airplane icon, as displayed.

Status bar images persist beyond the life of the application.When you add an airplaneto the status bar and then quit, the airplane remains. If you decide to use status barimages, consider cleaning up the bar before suspending or terminating your program. Bydefinition, these images are meant to indicate behavior that exceeds the span of anyapplication session. Because Apple insists on the “one application at a time” metaphor,use this technology sparely and meaningfully within that limitation.

131Recipe: Adding Status Bar Images

Page 155: I Phone Developer Cookbook

Recipe 4-10 Adding and Removing Status Bar Icons

@interface UIApplication (extended)

- (void) addStatusBarImageNamed:(NSString *)aName;

- (void) removeStatusBarImageNamed:(NSString *)aName;

@end

- (void) performAction

{

isShowing = !isShowing;

if (isShowing)

[[UIApplication sharedApplication] addStatusBarImageNamed:@"Airplane"];

else

[[UIApplication sharedApplication] removeStatusBarImageNamed:@"Airplane"];

}

Adding Application BadgesIf you’ve used the iPhone or iPod touch for any time, you’ve likely seen the small, redbadges that appear over applications on the home screen.These might indicate the num-ber of missed phone calls or unread emails that have accumulated since the user lastopened Phone or Mail.

There are actually three ways to go about badging applications: one, an extremelysimple UIApplication call; the other a slightly more involved tunneling into UIKit;and the last using the announced SDK push features from a Web server.To set anapplication badge from within the program itself, use either the undocumentedsetApplicationBadge: call or set the official applicationIconBadgeNumber prop-erty. Pass an NSString as a general badge argument, limiting the string size to four orfive characters at most or an NSInteger for the badge number property. For example,you could badge an application with the three-letter abbreviation for the current month,as shown by Listing 4-2.

NoteTo hide badges, either set applicationIconBadgeNumber to 0 (the number zero) orsetApplicationBadge: to @"" (the empty string).

132 Chapter 4 Alerting Users

Figure 4-11 Status bar icons allow you to communi-cate mode information to your users.

Page 156: I Phone Developer Cookbook

Listing 4-2 Badging an Application Icon with the Current Month

NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];

NSString *caldate = [[now

dateWithCalendarFormat:@"%b"

timeZone:nil] description];

[[UIApplication sharedApplication] setApplicationBadge:caldate];

To remove an application badge, pass the empty string (for example, @"").Thisremoves any existing badge from the icon. If you want an “empty” badge, pass it a spacecharacter instead, @"".

The problem with the UIApplication approach is that to use it you must placeyour requests directly from the application and then only as a number.You may want tobadge icons from outside the application itself.This is something developers will want toknow about—especially if you do not intend to distribute certain applications throughApp Store. Listing 4-3 demonstrates how to badge applications using dynamic links tothe public UIKit framework.This code does not require that the badging be done bythe application itself. Instead, it calls SpringBoard to perform the dirty work.

This function relies on dynamic linking. Reverse engineering the UIKit frameworkrevealed how UIApplication’s setApplicationBadge worked. It callsSBSetApplicationBadge with a string and an application identifier. Dynamic linkingisn’t an approach I generally endorse for day-to-day programming. Figure 4-12 showsthe result of using this function to badge a few icons.

133Adding Application Badges

Figure 4-12 Application badges appear as small, red containers on the top-right corner of an application’s icon.

Page 157: I Phone Developer Cookbook

Be aware that recent iPhone firmware versions have behaved inconsistently withbadging. Second-party badging continues to work reliably for items in the /Applicationsfolder but not for those applications residing in sandboxes.

NoteIt remains unclear whether SpringBoard calls fall into the undocumented-but-acceptablecategory or the undocumented-and-bad-evil-evil-evil category as far as App Store submis-sion is concerned. Use dynamic linking with care.

Listing 4-3 General Badging Through Reverse Engineering and Dynamic Linking

#include <dlfcn.h>

void badge(char *appid, char *badge)

{

// link to UIKit

void *uikit = dlopen("/System/Library/Frameworks/UIKit.framework/UIKit",

RTLD_LAZY);

// Recover the SpringBoard Port

int (*SBSSpringBoardServerPort)() =

dlsym(uikit, "SBSSpringBoardServerPort");

mach_port_t *p;

p = SBSSpringBoardServerPort();

// Perform the badging

int (*doBadge)(mach_port_t* port, char* x, char *y) =

dlsym(uikit, "SBSetApplicationBadge");

doBadge(p, appid, badge);

dlclose(uikit);

}

Recipe: Simple Audio AlertsAudio alerts “speak” directly to your users.They produce instant feedback—assumingusers are not hearing impaired. Fortunately,Apple built basic sound playback into theCocoa Touch SDK through System Audio services.This works very much like systemaudio on a Macintosh.The alternative, using Audio Queue calls to create audio alerts,comes with a pretty high price.Audio Queue playback is “expensive” to program andinvolves much more complexity than simple alert sounds need. In contrast, you can loadand play system audio with just a few lines of code.

Alert sounds work best when kept short, preferably two seconds or less according toApple.That being said, I’ve tested using sounds up to a minute in length, and they playedback fine with the System Audio services. System Audio plays PCM and IMA audio

134 Chapter 4 Alerting Users

Page 158: I Phone Developer Cookbook

only.That means limiting your sounds to AIFF,WAV, and CAF formats.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 thesound object.That single call does all the work.

You can add an optional system sound completion callback to notify your programwhen a sound finishes playing (call AudioServicesAddSystemSoundCompletion), butyou really don’t need to do that in practice, especially when using short Apple-approvedsounds.

To clean up your sounds, dispose them when you’re through using them. CallAudioServicesDisposeSystemSoundID with the sound object in question.

NoteTo use these system sound services, make sure to include AudioToolbox/AudioServices.h in your code and link to the Audio Toolbox framework.

Recipe 4-11 Playing Simple Audio Alerts

@interface HelloController : UIViewController

{

UIView *contentView;

SystemSoundID pmph;

}

@end

@implementation HelloController

- (void) playSound

{

AudioServicesPlaySystemSound (pmph);

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view = contentView;

contentView.backgroundColor = [UIColor whiteColor];

[contentView release];

// Add an action button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Do It"

style:UIBarButtonItemStylePlain

target:self

135Recipe: Simple Audio Alerts

Page 159: I Phone Developer Cookbook

Recipe 4-11 Continued

action:@selector(playSound)]autorelease];

// Load the sound

id sndpath = [[NSBundle mainBundle] pathForResource:@"pmph1" ofType:@"wav"➥inDirectory:@"/"];

CFURLRef baseURL = (CFURLRef)[[NSURL alloc] initFileURLWithPath:sndpath];

AudioServicesCreateSystemSoundID (baseURL, &pmph);

}

-(void) dealloc

{

if (pmph) AudioServicesDisposeSystemSoundID(pmph);

[contentView release];

[super dealloc];

}

@end

VibrationAs with audio alerts, vibration immediately grabs a user’s attention.What’s more, vibra-tion works for nearly all users, including those who are hearing or visually impaired.Using the same System Audio services you just used in Recipe 4-11, you can vibrate aswell as play a sound.All you need is the following one-line call to accomplish it:

AudioServicesPlaySystemSound (kSystemSoundID_Vibrate);

You cannot vary the vibration parameters. Each call produces a short one- to two-second buzz. On platforms without vibration support (like the iPod touch), this call doesnothing—but will not produce an error.

SummaryThis chapter has introduced ways to interact directly with your user.You’ve seen how tobuild alerts—visual, auditory, and tactile—that grab your user’s attention and can requestimmediate feedback. Use these examples to enhance the interactive appeal of your pro-grams and leverage some unique iPhone-only features. Here are a few thoughts to carryaway from this chapter:

n Many recipes in this chapter rely on undocumented UIKit calls or, worse, dynamiclinking. I’ve included these recipes into a book that is otherwise focused on officialSDK development because their functionality is both useful and passes the test ofbeing needed by a large segment of iPhone developers.All calls in this chapter arelimited to public SDK frameworks and do not, at least theoretically, cross the lineaccording to Apple’s stated policies.

136 Chapter 4 Alerting Users

Page 160: I Phone Developer Cookbook

n Whenever any task will take a noticeable amount of time, be courteous to youruser and display some kind of progress feedback.The iPhone offers many ways todo this, from heads-up displays to status bar indicators and beyond.

n Alerts take users into the moment.They’re designed to elicit responses while com-municating information.And, as you’ve seen in this chapter, they’re almost insanelycustomizable. It’s possible to build entire applications around the simpleUIAlertSheet.

n Badges and status bar icons both allow you to extend application state beyond therunning of the application itself. Use these features sparingly to deliver the greatestinformation punch.

n Audio feedback-like clicks and beeps can enhance your programs and make yourinteraction richer. Using system sound calls means that your sounds play nicelywith iPod functionality and won’t ruin the ongoing listening experience.

137Summary

Page 161: I Phone Developer Cookbook
Page 162: I Phone Developer Cookbook

5Basic Tables

Tables provide an interaction class that works particularly well on a small, crampeddevice. Many if not most apps that ship with the iPhone and iPod touch center ontables, including Settings, iPod,YouTube, Stocks, and Weather.The iPhone’s limitedscreen size makes tables, with their scrolling and individual item selection, an ideal wayto deliver information and content in simple easy-to-manipulate form. In this chapter,you discover how iPhone tables work, what kinds of tables are available to you as adeveloper, and how you can use table features in your own programs.

Introducing UITableView andUITableViewControllerThe standard iPhone table consists of a simple scrolling list of individual cells, providingan interactive data index. Users may scroll or flick their way up and down until they findan item they want to interact with.Then, they can work with that item independently ofother rows. On the iPhone, tables are ubiquitous. Nearly every standard software packageuses them, and they form the core of many third-party applications, too. In this section,you’ll discover how tables function and what elements you need to bring together tocreate your own.

The iPhone SDK supports several kinds of tables, all of which are implemented as flavors of the UITableView class. In addition to the standard scrolling list of cells, whichprovides the most generic table implementation, you can create several specialized tables.These include the kind of tables you see in the preferences application, with their graybackground and rounded cell edges, tables with sections and an index like the ones usedin the Contacts application, and a UITableView subclass with wheeled tables, like thoseused to set appointment dates and alarms. Chapter 6,“Advanced Tables,” introduces thehow-tos for these specialized table styles. No matter what type of table you use, they allwork in the same general way.They contain a column of cells provided from a datasource and respond to user interactions by calling well-defined methods.

UITableViewControllers are a subclass of UIViewController. Like their parentclass, they enable you to build onscreen views with minimal programming and maximum

Page 163: I Phone Developer Cookbook

convenience.As you’ll read in this section, the UITableViewController class greatlysimplifies the process of creating a UITableView, reducing or eliminating the repetitivesteps required for working directly with table instances. UITableViewControllerhandles the fussy details for view layout and provides table-specific convenience byadding a local tableView instance variable and automatic table protocol support fordelegates and data sources.

Creating the TableTo implement tables, you must define three key elements: how the table is laid out, thekind of things that are used to fill the table, and how the table reacts to user interaction.To specify these elements, you add descriptions and methods to your application.Youcreate the visual layout when building your views, you define a data source that feedstable cells on demand, and you implement delegate methods that respond to user inter-actions such as row-selection changes.

Laying Out the ViewUITableViews are, as the name suggests, views.They present interactive tables on theiPhone screen.The UITableView class inherits from the UIScrollView class.Thisinheritance provides the up and down scrolling capabilities used by the table. Like otherviews, UITableView instances define their boundaries through frames, and they can bechildren or parents of other views.To create a table view, you allocate it (alloc), initializeit with a frame just like any other view (initWithFrame:), and then add all the book-keeping details by assigning data source and delegate objects.

UITableViewControllers take care of the layout work for you.TheUITableViewController class creates a standard UIViewController and populates itwith a UITableView, setting its frame to allow for any navigation bars or toolbars.Youmay access that table view via the tableView instance variable.

One important note:When creating a custom UITableViewController subclass,make sure to call [super layoutView] from the child’s layoutView method.Thisproperly sets up the view within the controller and enables you to add customize featuresin the subclass such as navigation item buttons.

Assigning a Data SourceTables do not directly own or copy the cells they use.Table cells exist independently ofeach table. UITableView instances rely on an external source to feed either new orexisting table cells on demand.This external source is called a “data source” and refers tothe object whose responsibility it is to return a cell to a table’s query.

Data sources provide table cells based on an index path. Index paths, members of theNSIndexPath class, describe the path through a data tree to a particular node. It’s thedata source’s job to connect that path to a concrete UITableViewCell instance andreturn that cell on demand.

140 Chapter 5 Basic Tables

Page 164: I Phone Developer Cookbook

The iPhone SDK provides a built-in mechanism for reusing table cells.You can tagcells for reuse and then pop them off a stack as needed.This saves memory and providesa fast, efficient way to feed cells when users scroll quickly through long lists onscreen.

Use setDataSource: to assign an object to a table as its data source.That objectmust implement the UITableViewDataSource protocol. Most typically, theUITableViewController that owns the table view acts as the data source for that view.When working with UITableViewController subclasses, you need not assign the<UITableViewDataSource> protocol.The UITableViewController class implicitlysupports that protocol.

After you’ve assigned a data source, you can go ahead and load your table up with itscells. Call the table’s reloadData method and the table starts querying its data source toload the actual onscreen cells into your table.

Assigning a DelegateLike many other interaction objects, UITableView instances use delegates to respond touser interactions and implement a meaningful response.Your table’s delegate can respondto events like the table scrolling or row selection changes. Delegation tells the table tohand off responsibility for reacting to these interactions to the object you specify, typicallythe UITableViewController object that owns the table view. If you’re working directlywith a UITableView, use the standard setDelegate: method to set your table’s delegate.The delegate must implement the UITableViewDelegate protocol.When workingwith UITableViewController, omit the setDelegate: method and protocol assign-ment.That class automatically handles this.A full set of delegate methods is listed in theApple SDK documentation.

NoteUITableView instances provide notifications in addition to delegate method calls.Notifications enable different threads of your application to communicate with each otherby broadcasting updates. You can subscribe your application to these notifications (usingstandard NSNotificationCenter calls) to find out when the table states change.

What the UITableViewController DoesThe UITableViewController class embeds a UITableView into aUIViewController object that manages its table view. Its view is called tableView,which you can access directly through calls to self.tableView. It automatically sets thedata source and delegate methods for the table view to itself.And it automatically imple-ments editing mode when you add an Edit|Done button to its parent’s navigation barvia setEditing: animated: calls.

141Introducing UITableView and UITableViewController

Page 165: I Phone Developer Cookbook

Recipe: Creating a Simple List TablePretty much any array of strings can be used to set up and populate a table.This recipeleverages the UIFont class’s capability to list available system fonts.A call to [UIFontfamilyNames] returns an NSArray populated with those font names—handy for settingup a table data source. Recipe 5-1 creates a basic table based on those font names. Figure 5-1shows the interface produced by this code, as run on the iPhone simulator. (Running onthe Simulator produces a different set of fonts because they’re based on the available fontsfrom the Macintosh running the SDK rather than the fonts on the iPhone itself.)

142 Chapter 5 Basic Tables

Figure 5-1 It’s easy to fill a UITableView with cells based on any arrayof strings. Here, the font family list from the UIFont class is listed. When

tapped, the font chosen will update the title bar font.

Data Source FunctionsTo display a table, every table data source (UITableViewDelegate) must provide threecore methods.These methods are presented in the following list along with commentsabout their use in a simple list view, as shown in Figure 5-1:

n numberOfSectionsInTableView. Tables must determine whether they displaytheir data in sections or as a single list. For simple tables, always return 1, as in onesingle section for the table.

n tableView: numberOfRowsInSection. Because simple lists contain only onesection, return the number of rows for the entire table here. For more complexlists, you’ll want to provide a way to report back per section.

Page 166: I Phone Developer Cookbook

n tableView: cellForRowAtIndexPath. This is the method that actually returnsa cell. Use the index path’s row to determine which cell to provide.

Reusing CellsOne of the ways the iPhone conserves memory is by reusing cells.You can assign an IDstring to each cell.This specifies what kind of cell it is. Use different IDs for differentkinds of cells. For simple tables, a single identifier does the job. In the case of Recipe 5-1,it is @“any-cell”.The strings are arbitrary. Define them the way you like, but whenusing multiple cell types keep the names meaningful.

Before allocating a new cell, always check whether a reusable cell is available. If yourtable returns NULL from a request to dequeueReusableCellWithIdentifier:, goahead and allocate that new cell. If it returns a cell, customize that cell with the informa-tion that’s meaningful for the current row index.You do not need to add cells to thereuse queue. Cocoa Touch handles all those details for you.

Font Table SampleRecipe 5-1 demonstrates how to build a simple list-based table. It creates a table and fillsthat table with all available font families.When tapped, the view controller assigns thatfont to the label in the blue navigation bar at the top of the screen and prints a list ofavailable fonts for that family out to the debugger console.This behavior is defined inthe tableView: didSelectRowAtIndexPath: delegate method.

Using the UITableViewController as a delegate is a good choice here because thetable’s user interactions affect its views. If you’d rather use the application delegate, youcan call setDelegate: with that object by querying the application:[[UIApplication sharedApplication] delegate] instead.This overrides the standard UITableViewController settings.

NoteTables enable you to set the color for the selected cell by choosing between a blueor gray overlay. Set the selectionStyle property to eitherUITableViewCellSelectionStyleBlue orUITableViewCellSelectionStyleGray. If you’d rather not show a selection,use UITableViewCellSelectionStyleNone. The cell can still be selected, butthe overlay will not display.

Recipe 5-1 Creating a Simple List-Based Table

@interface HelloController : UITableViewController

@end

@implementation HelloController

- (HelloController *) init

143Recipe: Creating a Simple List Table

Page 167: I Phone Developer Cookbook

Recipe 5-1 Continued

{

if (self = [super init]) self.title = @"Fonts";

return self;

}

#pragma mark UITableViewDataSource Methods

// Only one section in this table

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 1;

}

// Return the number of rows in the table

- (NSInteger)tableView:(UITableView *)tableViewnumberOfRowsInSection:(NSInteger)section

{

return [[UIFont familyNames] count];

}

// Return a cell for the specified index path

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

{

UITableViewCell *cell = [tableView ➥dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

// Set up the cell

cell.text = [[UIFont familyNames] objectAtIndex:[indexPath row]];

return cell;

}

#pragma mark UITableViewDelegateMethods

// Respond to user selection

- (void)tableView:(UITableView *)tableView ➥didSelectRowAtIndexPath:(NSIndexPath *)newIndexPath

{

printf("User selected row %d\n", [newIndexPath row] + 1);

NSString *fontName = [[UIFont familyNames] objectAtIndex:[newIndexPath row]];

CFShow([UIFont fontNamesForFamilyName:fontName]);

[(UILabel *)self.navigationItem.titleView setFont:[UIFont fontWithName:➥fontName size:[UIFont systemFontSize]]];

}

#pragma mark Controller's loadView method

144 Chapter 5 Basic Tables

Page 168: I Phone Developer Cookbook

Recipe 5-1 Continued

- (void)loadView

{

[super loadView];

// Add the custom title bar label

self.navigationItem.titleView = [[UILabel alloc]➥initWithFrame:CGRectMake(0.0f, 4.0f, 320.0f, 36.0f)];

[(UILabel *)self.navigationItem.titleView setText:@"Font Families"];

[(UILabel *)self.navigationItem.titleView setBackgroundColor:[UIColor➥clearColor]];

[(UILabel *)self.navigationItem.titleView setTextColor:[UIColor whiteColor]];

[(UILabel *)self.navigationItem.titleView➥setTextAlignment:UITextAlignmentCenter];

[(UILabel *)self.navigationItem.titleView setFont:[UIFont➥boldSystemFontOfSize:[UIFont systemFontSize]]];

}

-(void) dealloc

{

[super dealloc];

}

@end

Recipe: Creating a Table-Based Selection SheetThe preceding recipe showed you how to create an extremely simple table of font fami-lies.This recipe takes that idea to the next level by embedding that table into a view andpresenting it for user selection (see Figure 5-2).Tables provide an excellent way to offeryour users a selection of similar options. Mobile Safari has standardized on aUIPickerTableView presentation, but I think a simple flat scrolling list has a lot to offer.The metallic look used by Picker views can be jarring, and although my design skills arelimited, this UITableView alternative should be able to find a home in many applications.

145Recipe: Creating a Table-Based Selection Sheet

Figure 5-2 Embed tables into other views to createanother way for your users to pick from lists.

Page 169: I Phone Developer Cookbook

Note that this recipe does not use UITableViewController.The solution wrapsaround a custom view with a table and a button to indicate selection.This falls outsidethe province of UIViewControllers and is presented by animating a UIView subclassinto position.

This recipe, shown in Recipe 5-2, creates a custom view, the FontPickerSheet, andslides it onto the screen on demand using a UIView animation block (see Chapter 2,“Views”). User selections are stored in a local property, the selection, which can bequeried from the UIViewController.A value of NULL means that the user failed tomake a selection. Otherwise, it returns an NSString, the name of the selected font.

What I have not included here, which you might want to consider, is a semitranslu-cent UIView sitting between the sheet and the view below it. Such a view would servetwo purposes. First, it would dim your main application view, and second, it would blockinteractions with that view.This forces a modal presentation, the interaction style thatlimits your user to a single view before returning control to the main application.

Recipe 5-2 Embedding a Table into a Sheet and Recovering the Selection

@interface FontPickerSheet : UIView <UITableViewDataSource, UITableViewDelegate>

{

UILabel *title;

UITableView *tableView;

NSString *selection;

}

@property (nonatomic, retain) NSString *selection;

@end

@implementation FontPickerSheet

@synthesize selection;

#pragma mark UITableViewDataSource Methods

// Only one section in this table

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 1;

}

// One row for each family font name

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

{

return [[UIFont familyNames] count];

}

// Return a cell for the ith row

- (UITableViewCell *)tableView:(UITableView *)tView➥cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:@"any-cell"];

146 Chapter 5 Basic Tables

Page 170: I Phone Developer Cookbook

Recipe 5-2 Continued

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

// Set up the cell

cell.text = [[UIFont familyNames] objectAtIndex:[indexPath row]];

return cell;

}

#pragma mark UITableViewDelegateMethods

// Respond to user selection

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)➥newIndexPath

{

selection = [[[UIFont familyNames] objectAtIndex:[newIndexPath row]] retain];

}

#pragma mark FontPickerSheet customization

- (FontPickerSheet *) initWithFrame: (CGRect) rect

{

rect.origin.y = 0.0f - rect.size.height; // Place above status bar

self = [super initWithFrame:rect];

[self setAlpha:0.9];

[self setBackgroundColor:sysBlueColor(0.4f)];

// Add button

UIButton *button = [[UIButton buttonWithType:UIButtonTypeCustom]➥initWithFrame:CGRectMake(220.0f, 200.0f, 80.0f, 32.0f)];

[button setBackgroundImage:[UIImage imageNamed:@"whiteButton.png"]➥forState:UIControlStateNormal];

[button setTitle:@"Okay" forState: UIControlStateHighlighted];

[button setTitle:@"Okay" forState: UIControlStateNormal];

[button setFont:[UIFont boldSystemFontOfSize:14.0f]];

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

[self addSubview:button];

// Add title

title = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 8.0f, 320.0f, 32.0f)];

title.text = @"Please Select a Font";

title.textAlignment = UITextAlignmentCenter;

title.textColor = [UIColor whiteColor];

title.backgroundColor = [UIColor clearColor];

title.font = [UIFont boldSystemFontOfSize:20.0f];

[self addSubview:title];

[title release];

147Recipe: Creating a Table-Based Selection Sheet

Page 171: I Phone Developer Cookbook

Recipe 5-2 Continued

// Add border for the table

CGRect bounds = CGRectMake(20.0f, 40.0f, 280.0f, 200.0f - 48.0f);

UIView *borderView = [[UIView alloc] initWithFrame:bounds];

[borderView setBackgroundColor:sysBlueColor(0.55f)];

[self addSubview:borderView];

[borderView release];

// Add table

tableView = [[UITableView alloc] initWithFrame:CGRectInset(bounds, 4.0f, 4.0f)➥style:UITableViewStylePlain];

tableView.backgroundColor = [UIColor whiteColor];

tableView.delegate = self;

tableView.dataSource = self;

[tableView reloadData];

[self addSubview:tableView];

[tableView release];

return self;

}

- (void) removeView

{

[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] ➥animated:NO];

// Scroll away the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:0.5];

CGRect rect = [self frame];

rect.origin.y = 0.0f - rect.size.height;

[self setFrame:rect];

// Complete the animation

[UIView commitAnimations];

}

- (void) presentView

{

selection = NULL;

// Scroll in the overlay

CGContextRef context = UIGraphicsGetCurrentContext();

[UIView beginAnimations:nil context:context];

[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

[UIView setAnimationDuration:0.5];

CGRect rect = [self frame];

rect.origin.y = 0.0f;

148 Chapter 5 Basic Tables

Page 172: I Phone Developer Cookbook

Recipe 5-2 Continued

[self setFrame:rect];

// Complete the animation

[UIView commitAnimations];

}

-(void) dealloc

{

[title release];

[tableView release];

[super dealloc];

}

@end

Recipe: Loading Images into Table CellsAdd images to tables by assigning them to individual cells.The UITableViewCell classenables you to place a small thumbnail to the left side of any cell without any specialprogramming by setting the cell’s image property to a UIImage.The image does notinterfere with the normal text property of that cell.Any text appears to the right ofthe image, with spacing automatically taken into account.

You must know the approximate size of the images you’ll use and adjust the tableview’s rowHeight property accordingly.This example sets the row height to 64, withplenty of space to accommodate 57-pixel square application icons.

This example scans for application icons on your iPhone. It uses NSFileManager toaccess your /Applications folder and scans each application for icon.png.When found, ituses this icon to label each cell.When not found, it adds the icon for its own applicationinstead.You can expect to see icons for most apps, including Calculator and Maps butnot for the two MobileSlideshow applications (Photos and Camera) because they do notprovide individual icon.png files.

NoteIf you’d like to launch the selected application, call the undocumented UIApplicationmethod launchApplicationWithIdentifier: suspended:. Pass it the applicationID (for example, com.apple.Calculator). Launching applications is, however, strictlyprohibited by App Store agreements.

Recipe 5-3 Adding Image Thumbnails to a Table

// Return a cell for the ith row

- (UITableViewCell *)tableView:(UITableView *)tView➥cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:@"any-cell"];

149Recipe: Loading Images into Table Cells

Page 173: I Phone Developer Cookbook

Recipe 5-3 Continued

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

// Set up the cell

NSString *appname = [[[NSFileManager defaultManager]➥directoryContentsAtPath:@"/Applications"] objectAtIndex:[indexPath row]];

cell.text = appname;

NSString *imagePath = [NSString stringWithFormat:@"/Applications/%@/icon.png",➥appname];

printf("app: %s\n", [imagePath UTF8String]);

if ([[NSFileManager defaultManager] fileExistsAtPath:imagePath])

cell.image = [UIImage imageWithContentsOfFile:imagePath];

else

cell.image = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle]➥pathForResource:@"Icon" ofType:@"png" inDirectory:@"/"]];

return cell;

}

- (void)loadView

{

[super loadView];

[self.tableView setRowHeight:64];

}

150 Chapter 5 Basic Tables

Figure 5-3 UITableViewCells makes it easy to addthumbnail pictures to your tables as well as text labels.

Page 174: I Phone Developer Cookbook

Recipe: Setting a Cell’s Text TraitsEach UITableViewCell provides full text trait support.You can set the cell label’s color,font, size, alignment, and so forth. Recipe 5-4 shows data source methods that create asimple three-item table, assigning unique colors and fonts to each cell.Although this produces a visual mishmash, as shown in Figure 5-4, you can see how you might assignvisual cues to differentiate table rows from their mates.

You might use different text styles to indicate which table elements are “busy” andcannot be accessed or which items have not yet been examined. In one of my earlyiPhone utilities (SendFile, see ericasadun.com), I used color to tell users which file wasbeing shared over Bonjour’s configuration-free networking.To assign text traits, use thestandard text properties such as textColor and font.

Recipe 5-4 assigns these kinds of traits arbitrarily, to produce a proof of concept. Itdemonstrates that each cell can own its own text styling.

151Recipe: Setting a Cell’s Text Traits

Recipe 5-4 Assigning Text Traits to Table Cells

// Return a cell for the ith row

- (UITableViewCell *)tableView:(UITableView *)tView➥cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

Figure 5-4 UITableViewCells gives you directaccess to their label’s text traits.

Page 175: I Phone Developer Cookbook

Recipe 5-4 Continued

// Set up the cell

cell.text = [NSString stringWithFormat:@"Cell Number #%d", [indexPath row]];

cell.font = [UIFont fontWithName:[[NSArray arrayWithObjects:@"Georgia",➥@"Helvetica", @"Courier", nil]

objectAtIndex:[indexPath row]] size:16];

cell.textColor = [[NSArray arrayWithObjects:

[UIColor redColor],

[UIColor lightGrayColor],

[UIColor blueColor], nil] objectAtIndex: [indexPath row]];

return cell;

}

Removing Cell SelectionsIf there’s one thing I hated about the UITableView class (and by associationUITableViewController), it was its historical attitude toward cell reselection.Depending on the iteration of the iPhone SDK beta program, UITableView instanceswouldn’t let you reselect the currently selected cell and trigger a delegate method call—as you can now do in recent SDK releases.There was always at least one way you coulduse to get a table cell to allow itself to be selected twice—and that was by removing thecurrent selection so that the table was ready for the next tap.

When working with delegate methods, you can’t just tell the selected cell to deselectitself.There is no [aCell setSelected:NO]; method. Instead, you must talk to thetable and have it deselect the cell in question, as shown in Listing 5-1. Here, each userselection triggers a delayed deselection (the deselect method) after a half a second.

Now that cells support multiple tapping, instead of deselecting for functionality usedeselection semantically.A table with a selected item may present different options than atable with no selection. For example, in an audio application, you might show only aPlay or Pause button when users select a sound from a list.

Listing 5-1 Removing a Table Cell Selection

- (void) deselect: (id) sender

{

[tableView deselectRowAtIndexPath:[tableView indexPathForSelectedRow] ➥animated:YES];

}

// Respond to user selection

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)➥newIndexPath

{

printf("User selected row %d\n", [newIndexPath row] + 1);

[self performSelector:@selector(deselect:) withObject:nil afterDelay:0.5f];

}

152 Chapter 5 Basic Tables

Page 176: I Phone Developer Cookbook

When moving between views (which often happens as a result of table selection),delay this method until the view that contains this table is fully redrawn. Perform deselection in the viewDidAppear method of the table’s parent UIViewController.

Recipe: Creating Complex CellsTable cells can be more than a text label with an optional image.Table cells are muchmore flexible and complex than that.They are, in the end, views.And as such, you cancustomize them the way you’d customize any other view with arbitrary frame sizes andchild subviews.

Recipe 5-5 demonstrates how to create a custom table cell with children. In thisexample, I modify a standard UITableViewCell.A cell constructor (newCell:url:note:) creates a 320-by-100-pixel cell and populates it with several UITextLabels withdistinct text traits (see Figure 5-5). My design strengths, as you can see from this image,are limited. Developers with better-developed graphic skills will be able to do muchmore than this after mastering the notion that each cell can be a canvas. Chapter 6demonstrates more table cell customization examples.

Please note that this example does not subclass UITableCell. It customizes the stan-dard cell.Also note that unlike standard table cells, this presentation does not invertthe black fonts to white on selection.You might have to play with your cell-selectionbehavior if you don’t like the standard blue-over-black display.This involves catching theselection, restoring the colors for any previously selected cell, and either inverting orchanging the text color for the labels in the newly selected cell.

153Recipe: Creating Complex Cells

Figure 5-5 Create complex cell types by adding subviews to your UITableCells.

Page 177: I Phone Developer Cookbook

Recipe 5-5 Creating a Customized UITableViewCell

- (void) modCell:(UITableViewCell *)aCell withTitle:(NSString *)title

url: (NSString *) url note: (NSString *) comment

{

// Title

CGRect tRect1 = CGRectMake(0.0f, 5.0f, 320.0f, 40.0f);

id title1 = [[UILabel alloc] initWithFrame:tRect1];

[title1 setText:title];

[title1 setTextAlignment:UITextAlignmentCenter];

[title1 setFont: [UIFont fontWithName:@"American Typewriter" size:36.0f]];

[title1 setBackgroundColor:[UIColor clearColor]];

// URL

CGRect tRect2 = CGRectMake(0.0f, 45.0f, 320.0f, 20.0f);

id title2 = [[UILabel alloc] initWithFrame:tRect2];

[title2 setText:url];

[title2 setTextAlignment:UITextAlignmentCenter];

[title2 setFont: [UIFont fontWithName:@"Helvetica" size:18.0f]];

[title2 setBackgroundColor:[UIColor clearColor]];

// Comment

CGRect tRect3 = CGRectMake(0.0f, 70.0f, 320.0f, 20.0f);

id title3 = [[UILabel alloc] initWithFrame:tRect3];

[title3 setText:comment];

[title3 setTextAlignment:UITextAlignmentCenter];

[title3 setFont: [UIFont fontWithName:@"Helvetica" size:18.0f]];

[title3 setBackgroundColor:[UIColor clearColor]];

// Add to cell

[aCell addSubview:title1];

[aCell addSubview:title2];

[aCell addSubview:title3];

[title1 release];

[title2 release];

[title3 release];

}

// Return a cell for the ith row

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

{

UITableViewCell *cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

154 Chapter 5 Basic Tables

Page 178: I Phone Developer Cookbook

Recipe 5-5 Continued

// Set up the cell

int row = [indexPath row];

switch (row)

{

case 0:

[self modCell:cell withTitle:@"Digital Media"

url: @"http://digitalmedia.oreilly.com/"

note: @"Developer Issues"];

break;

case 1:

[self modCell:cell withTitle:@"TUAW"

url: @"http://tuaw.com/"

note: @"Consumer Apple Use"];

break;

case 2:

[self modCell:cell withTitle:@"Erica Sadun"

url: @"http://ericasadun.com"

note: @"Software Updates"];

break;

}

return cell;

}

Recipe: Creating Checked SelectionsAccessory views enable you to expand normal UITableViewCell functionality.Themost common accessories are the Delete buttons and drag bars for reordering, but youcan also add check marks to create interactive one-of-n or n-of-n selections.With thesekinds of selections, you can ask your users to pick what they want to have for dinner orchoose which items they want to update.This kind of radio button/check box behaviorprovides a richness of table interaction. Recipe 5-6 demonstrates how to create this kindof table.

Figure 5-6 shows checks in action: a standard UITableView with accessorized cells.Check marks appear next to selected items.When tapped, the checks toggle on or off.Query your table by iterating through the cells. Checked items use theUITableViewCellAccessoryCheckmark accessory type.

Note very carefully that it’s the cell that’s being checked here, not the logical item asso-ciated with the cell.As you scroll your table, your cells get reused.And as they do, thereused cells remain checked or unchecked. In the next chapter, Recipe 6-6 demonstrateshow to store and update state information that’s tied to data.

155Recipe: Creating Checked Selections

Page 179: I Phone Developer Cookbook

You control the way items are selected.To simply toggle items on and off, keep trackof the current accessoryType setting.To create a one-of-n selection instead, removethe accessory from the old index path, and add it to the cell at the new index path.

Recipe 5-6 Implementing Table Cell Checks

// Respond to user selection

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath➥*)newIndexPath

{

printf("User selected row %d\n", [newIndexPath row] + 1);

if ([[tableView cellForRowAtIndexPath:newIndexPath] accessoryType] ==➥UITableViewCellAccessoryCheckmark)

[[tableView cellForRowAtIndexPath:newIndexPath]➥setAccessoryType:UITableViewCellAccessoryNone];

else

[[tableView cellForRowAtIndexPath:newIndexPath]➥setAccessoryType:UITableViewCellAccessoryCheckmark];

[self performSelector:@selector(deselect) withObject:nil afterDelay:0.5f];

}

156 Chapter 5 Basic Tables

Figure 5-6 Check mark accessories offer a convenientway of making one-of-n or n-of-n selections from a list.

Page 180: I Phone Developer Cookbook

157Recipe: Deleting Cells

Recipe: Deleting CellsThe iPhone deletion accessory makes table management simple and elegant. EveryiPhone user quickly becomes familiar with the small, red circles that enable them todelete cells from tables, and many users pick up on basic swipe-to-delete functionality.Recipe 5-7 shows how to build a table that responds meaningfully to cell deletion.In this sample, users may add new cells by tapping an Add button and may removecells either by swiping or entering edit mode and using the red remove controls(see Figure 5-7).

Remove controls belong to the UIRemoveControl class, which is not a public class.Like the check marks discussed in Recipe 5-6, remove controls are a kind of accessoryview.Accessories enable you to add user-tappable elements to your UITableViewCells.Accessory types include the remove controls seen here, add controls, check marks, anddisclosures.

Creating and Displaying Remove ControlsThere are actually two steps you need to take when adding remove controls to yourcells.When creating the cell, you must specify what kind of accessory view it uses (inthis case, the remove control).You do this by assigning the cell’s editingStyle toUITableViewCellEditingStyleDelete. Implement this in the tableView:cellForRowAtIndexPath: data source routine.

Figure 5-7 Red remove controls enable your users tointeractively delete items from an interactive table.

Page 181: I Phone Developer Cookbook

In addition to creating the control, you must tell the table to enter edit mode.You dothis through two calls. First, animate the table into an edit mode, revealing the edit con-trols next to each cell title and performing the indentation that makes room for thecontrols.To do this, call [tableView setEditing:YES animated:YES]. If for somereason you’d rather not animate the change, you can pass NO instead of YES, but I urgeyou not to.Whenever possible, add animations to your iPhone user interfaces to leadyour users from one state to the next, so they’re prepared for the mode changes thathappen onscreen.

Next, call [tableView beginUpdates].This tells your table to enter the mode thatenables you to change its contents interactively.This means removing cells with removecontrols or adding cells with insert controls. (This latter requires an editingStyle ofUITableVFiewCellEditingStyleInsert.)

In Recipe 5-7, I’ve combined these latter actions into a single methodenterEditMode.This method is called when a user taps the navigation bar’s Edit button.This method also swaps out the title from Edit to Done and updates the target-actionsettings for that button to call leaveEditMode.

Dismissing Remove ControlsWhen you’ve completed your edit mode and want to return to normal table display,proceed in reverse. Conclude your table updates with [tableView endUpdates].Thendismiss the controls ([tableView setEditing:NO animated:YES]).You can reenterediting mode at will without having to update cell attributes or performing any otherwork. Once the editing style has been set for your cell, it can show and hide its removecontrols strictly through UITableView calls.

Handling Delete RequestsOn row deletion, the table communicates with your application by issuing atableView: commitEditingStyle: forRowAtIndexPath: callback.This methodlets you update your data source and respond to the row deletion that the user just performed.

Here is where you actually delete the item from the data structure that supplies thedata source methods (in this recipe, through an NSMutableArray of titles) and handleany real-world action such as deleting files that occur as a consequence. In this samplecode, the cell goes away but there’s no real-world consequence for the deletion.Thesample is not based on a real-life model. Instead, the title list just loses that particularnumbered cell title.

Swiping CellsSwiping provides an elegant method for removing items from your UITableViews.Youdon’t have to do anything to enable swipes other than set a cell’s editingStyle.Thetable takes care of the rest.To swipe, users drag swiftly from the left to the right side ofthe cell.The rectangular delete confirmation appears to the right of the cell, but the cellsdo not display the round remove controls on the left.

158 Chapter 5 Basic Tables

Page 182: I Phone Developer Cookbook

After users swipe and confirm, the tableView: commitEditingStyle:forRowAtIndexPath: method handles data updates just as if the deletion had occurredin edit mode.

Adding CellsRecipe 5-7 introduces an Add button.This button lets users add new table cells.Toaccomplish this, an add method appends a new cell title at the end of the tableTitlesarray and then tells the table to update the data source using reloadData. So ratherthan actually creating new cells, this enables the normal table mechanism to check thedata and re-create the table view using that updated data source.

Recipe 5-7 Deleting Cells On-the-Fly

// Initialize and set up the table title array

- (HelloController *) init

{

if (![self = [super init]]) return self;

self.title = @"Table Edits";

tableTitles = [[NSMutableArray alloc] init];

ithTitle = NCELLS;

for (int i = 1; i <= NCELLS; i++)

[tableTitles addObject:[NSString stringWithFormat:@"Table Cell #%d", i]];

return self;

}

// Add a row to the table

- (void) add

{

[tableTitles addObject:[NSString stringWithFormat:@"Table Cell #%d",➥++ithTitle]];

[self.tableView reloadData];

}

// Finished with edit mode, so restore the buttons and commit changes

-(void)leaveEditMode

{

// Add the edit button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Edit"

style:UIBarButtonItemStylePlain

target:self

action:@selector(enterEditMode)]autorelease];

[self.tableView endUpdates];

[self.tableView setEditing:NO animated:YES];

159Recipe: Deleting Cells

Page 183: I Phone Developer Cookbook

Recipe 5-7 Continued

}

// Handle deletion requests

- (void)tableView:(UITableView *)tableView➥commitEditingStyle:(UITableViewCellEditingStyle)editingStyle

➥forRowAtIndexPath:(NSIndexPath *)indexPath

{

printf("About to delete item %d\n", [indexPath row]);

[tableTitles removeObjectAtIndex:[indexPath row]];

[tableView reloadData];

}

// Enter edit mode by changing "Edit" to "Done"

-(void)enterEditMode

{

// Add the edit button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Done"

style:UIBarButtonItemStylePlain

target:self

action:@selector(leaveEditMode)]autorelease];

[self.tableView setEditing:YES animated:YES];

[self.tableView beginUpdates];

}

// Initialize the view with its buttons

- (void)loadView

{

[super loadView];

// Add an add button

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"New"

style:UIBarButtonItemStylePlain

target:self

action:@selector(add)] autorelease];

// Add the edit button

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Edit"

style:UIBarButtonItemStylePlain

target:self

action:@selector(enterEditMode)]autorelease];

}

160 Chapter 5 Basic Tables

Page 184: I Phone Developer Cookbook

Recipe: Reordering CellsYou empower your users when you enable them to directly reorder the cells of a table.They can apply this interaction to sort to-do items by priority or choose which songsshould go first in a playlist.The iPhone ships with built-in reordering support that’s easyto add to your applications. Recipe 5-8 shows how. Just set showsReorderControl: toYES for your cells.

161Recipe: Reordering Cells

To make your cells reorder properly, you must implement one method in addition tosetting the showsReorderControl property: tableView: moveRowAtIndexPath:toIndexPath:.This method synchronizes your data source with the onscreen changes,much as committing edits worked with cell deletion.This data source method enablesyou to move the object from an original path to an updated one. In the case of thisexample, this means moving the cell title to an updated location in the tableTitlesmutableArray.

Recipe 5-8 Reordering Table Cells

// Perform the reorder

-(void) tableView: (UITableView *) tableView moveRowAtIndexPath: (NSIndexPath *)➥oldPath toIndexPath:(NSIndexPath *) newPath

Figure 5-8 Reorder controls appear at the right of each cell during editmode. They appear as three stacked gray lines.

Page 185: I Phone Developer Cookbook

Recipe 5-8 Continued

{

NSString *title = [tableTitles objectAtIndex:[oldPath row]];

[tableTitles removeObjectAtIndex:[oldPath row]];

[tableTitles insertObject:title atIndex:[newPath row]];

}

Recipe: Working with DisclosuresDisclosures refer to those small, blue or gray, right-facing chevrons found on the right oftable cells. Disclosures enable you to link from a cell to a view that supports that cell. Forexample, in the Contacts list and Calendar applications, these chevrons link to screensthat enable you to customize contact information and appointments. Figure 5-9 showstwo UITable examples where each cell displays a disclosure control.

162 Chapter 5 Basic Tables

Figure 5-9 The right-pointing chevrons indicate disclosure controls,allowing you to link individual table items to another view.

The blue and gray chevrons have two roles.The blueUITableViewCellAccessoryDetailDisclosureButton versions are actual buttons.They respond to touches and are supposed to indicate that the button leads to a fullinteractive detail view.The gray UITableViewCellAccessoryDisclosureIndicatordoes not track touches and should lead your users to a further options view, specificallyoptions about that choice.

Page 186: I Phone Developer Cookbook

You see these two accessories in play in the Settings application. In the Wi-FiNetworks screen, the detail disclosures lead to specific details about each WiFi network:its IP address, subnet mask, router, DNS and so forth.The disclosure indicator for“Other” enables you to add a new network by scrolling up a screen for entering networkinformation.A new network then appears with its own detail disclosure.

You also see disclosure indicators whenever one screen leads to a related submenu.When working with submenus, stick to the simple gray chevron.The rule of thumb isthis: Submenus use gray chevrons, and object customization uses blue ones.

Recipe 5-9 demonstrates how to use disclosures in your applications.This code setsthe accessoryType for each cell toUITableViewCellAccessoryDetailDisclosureButton. Importantly, it also setshidesAccessoryWhenEditing to YES.When your delete or reorder controls appear,your disclosure chevron will hide, enabling your user full control over their edits withoutaccidentally popping over to a new view.

To handle user taps on the disclosure, the tableView:accessoryButtonTappedForRowWithIndexPath: method enables you to check therow that was tapped and implement some appropriate response.This sample merelypushes a new UIViewController that displays the application’s Default.png image. Inreal life, you’d move to a view that explains more about the selected item or that enablesyou to choose from additional options.

Recipe 5-9 Adding Disclosure Views

// Return a cell for the ith row

- (UITableViewCell *)tableView:(UITableView *)tView➥cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

// Set up the cell

cell.text = [tableTitles objectAtIndex:[indexPath row]];

cell.editingStyle = UITableViewCellEditingStyleDelete;

cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;

cell.hidesAccessoryWhenEditing = YES;

return cell;

}

- (void)tableView:(UITableView *)tableView➥accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath

{

[[self navigationController] pushViewController:[[ImageController alloc] init]➥animated:YES];

}

163Recipe: Working with Disclosures

Page 187: I Phone Developer Cookbook

SummaryThis chapter has introduced all the basic iPhone table features: from simple tables to editsto reordering.The skills covered in this chapter should enable you to build a wealth ofbasic table-based applications for the iPhone and iPod touch. Here are some key pointsto take away from this chapter:

n When it comes to understanding tables, make sure you know the differencebetween data source and delegate methods. Data sources fill up your tables withmeaningful cells. Delegate methods respond to user interactions.

n Don’t overlook notifications as a useful part of programming not just tables but allUI elements.With tables, notifications can sometimes move you past some of theSDK’s inherent limitations.

n UITableViewControllers simplify applications built around a centralUITableView. Do not hesitate to use UITableView instances directly, however,if your application requires it. Just make sure to explicitly support theUITableViewDelegate and UITableViewDataSource protocols.

n There’s more than one way to skin a cat or remove a cell. If you’d rather avoidintroducing an “edit/done” mode switch, support swipe-to-delete by setting yourcells’ editingStyle.

164 Chapter 5 Basic Tables

Page 188: I Phone Developer Cookbook

6Advanced Tables

iPhone tables do not begin and end with simple scrolling lists.You can build tables withtitled sections, with multiple scrolling columns, and more.You can add controls such asswitches, create translucent cell backgrounds, and include custom fonts.You can display aquick-access index like the one used in the Address Book or build preferences screenslike the kind you see in Settings.This chapter starts from where Chapter 5,“BasicTables,” left off. It introduces advanced table recipes for you to use in your iPhone pro-grams. Discover what it takes to design and implement more complex tables by exploringthese recipes and discussions.

Recipe: Grouping Table SelectionsOn the iPhone, tables come in two formats: grouped tables and plain table lists.You’vealready seen the latter. Chapter 5 focused on creating them.They use a single sectionwithout intrinsic element grouping. But you’ve surely already seen the former, too.TheSettings application on the iPhone offers grouped lists in action.These lists display on ablue-gray background, and each subsection appears within a slightly rounded rectangle.Figure 6-1 shows the grouped list built by Recipe 6-1.

Grouped lists depend on properly setting a table’s style.This happens during objectinitialization. Unfortunately, you cannot go back later and change a table’s style from onekind to the other, so you have to begin right. Decide which kind of list you’re buildingand add that into your view controller’s init or loadView routine as follows.

With lists, you may be working either with table view controllers (used here) ordirectly with UITableView instances. For table view controllers, use initWithStyle:to set the table’s style to UITableViewStyleGrouped.With UITable instances, useinitWithFrame: style:.At this time, the SDK supports just these two styles men-tioned: the grouped style and the (default) plain style, UITableViewStylePlain.

NoteApple seems to be moving from the “plain” terminology to “indexed,” so these names maychange without notice.

Page 189: I Phone Developer Cookbook

Style initialization is optional. If you’d rather go with the default, just use init orinitWithFrame: to create your table.This produces the same result as initializing itwith the plain style.

Building a Section-Based Data SourceWhen working with groups and sections, think two dimensionally.A section array letsyou store and access the members of each section. Recipe 6-1 implements this by creat-ing an array of arrays.The section array stores one array for each section, which in turncontains the titles and, in this case, colors for each cell.

The way this works in this recipe is through the createSectionList method.Thismethod takes a sorted array of words and iterates through them.As it does, it adds eachname to an array based on the index of the first letter of that word.

To work, this particular implementation relies on two things: first, that the words arealready 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 ornumbers will cause this loop to fail.You can trivially add an “other” section to take careof these cases, which this (simple) sample omits.

Although alphabetic sections are useful and probably the most common grouping,you can use any kind of grouping structure you like. For example, you might group peo-ple by departments, gems by grades, or appointments by date. No matter what kind ofgrouping you choose, an array of arrays provides the table view data source that bestmatches sectioned tables.

166 Chapter 6 Advanced Tables

Figure 6-1 Grouped lists enable you to create blocks of cells that scroll over a blue-gray background.

Page 190: I Phone Developer Cookbook

Grouped tables require two key data source methods in addition to the standardUITable data source routines, as follows:

n numberOfSectionsInTableView. This method specifies how many sectionsappear in your table.This establishes the number of groups to display.When usinga section array, as recommended here, return the number of items in the sectionarray (that is, [mySectionArray count]).

n tableView: numberOfRowsInSection. This method is called with a sectionnumber. Specify how many rows appear in that section.With the recommendeddata structure, just return the count of items at the nth subarray:[[mySectionArray objectAtIndex: sectionNumber] count].

In addition to these data source methods, be sure to use both the row and sectioninformation to find the cell data in question. Recipes in Chapter 5 used a flat array witha row number index. Recipes in this chapter must look up the section array and thenfind the row indexed in that section.

Recipe 6-1 Creating a Grouped Table

@interface HelloController : UITableViewController

{

NSMutableArray *sectionArray;

int fullCount;

}

@end

@implementation HelloController

#define ALPHA @"ABCDEFGHIJKLMNOPQRSTUVWXYZ"

// Initialize the table view controller with the grouped style

- (HelloController *) init

{

if (self = [super initWithStyle:UITableViewStyleGrouped]) self.title =➥@"Crayon Colors";

return self;

}

// One section for each alphabet member

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return [sectionArray count];

}

// Each row array object contains the members for that section

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

167Recipe: Grouping Table Selections

Page 191: I Phone Developer Cookbook

Recipe 6-1 Continued

{

return [[sectionArray objectAtIndex:section] count];

}

// Convert a 6-character hex color to a UIColor object

- (UIColor *) getColor: (NSString *) hexColor

{

unsigned int red, green, blue;

NSRange range;

range.length = 2;

range.location = 0;

[[NSScanner scannerWithString:[hexColor substringWithRange:range]]➥scanHexInt:&red];

range.location = 2;

[[NSScanner scannerWithString:[hexColor substringWithRange:range]]➥scanHexInt:&green];

range.location = 4;

[[NSScanner scannerWithString:[hexColor substringWithRange:range]]➥scanHexInt:&blue];

return [UIColor colorWithRed:(float)(red/255.0f) green:(float)(green/255.0f)➥blue:(float)(blue/255.0f) alpha:1.0f];

}

// Return a cell for the row and section

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

{

NSInteger row = [indexPath row];

NSInteger section = [indexPath section];

// Create a cell if one is not already available

UITableViewCell *cell = [self.tableView➥dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil)

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

// Set up the cell by coloring its text

NSArray *crayon = [[[sectionArray objectAtIndex:section] objectAtIndex:row]➥componentsSeparatedByString:@"#"];

cell.text = [crayon objectAtIndex:0];

cell.textColor = [self getColor:[crayon objectAtIndex:1]];

return cell;

}

168 Chapter 6 Advanced Tables

Page 192: I Phone Developer Cookbook

Recipe 6-1 Continued

// Remove the current table row selection

- (void) deselect

{

[self.tableView deselectRowAtIndexPath:[self.tableView➥indexPathForSelectedRow] animated:YES];

}

// Respond to user selection by coloring the navigation bar

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:➥(NSIndexPath *)newIndexPath

{

// Retrieve named color

int row = [newIndexPath row];

int section = [newIndexPath section];

NSArray *crayon = [[[sectionArray objectAtIndex:section] objectAtIndex:row]➥componentsSeparatedByString:@"#"];

// Update the nav bar color

self.navigationController.navigationBar.tintColor = [self getColor:[crayon➥objectAtIndex:1]];

// Deselect

[self performSelector:@selector(deselect) withObject:NULL afterDelay:0.5];

}

// Build a section/row list from the alphabetically ordered word list

- (void) createSectionList: (id) wordArray

{

// Build an array with 26 sub-array sections

sectionArray = [[NSMutableArray alloc] init];

for (int i = 0; i < 26; i++) [sectionArray addObject:[[NSMutableArray alloc]➥init]];

// Add each word to its alphabetical section

for (NSString *word in wordArray)

{

if ([word length] == 0) continue;

// determine which letter starts the name

NSRange range = [ALPHA rangeOfString:[[word substringToIndex:1]➥uppercaseString]];

// Add the name to the proper array

[[sectionArray objectAtIndex:range.location] addObject:word];

}

}

169Recipe: Grouping Table Selections

Page 193: I Phone Developer Cookbook

Recipe 6-1 Continued

// Prepare the Table View

- (void)loadView

{

[super loadView];

// Retrieve the text and colors from file

NSString *pathname = [[NSBundle mainBundle] pathForResource:@"crayons"➥ofType:@"txt" inDirectory:@"/"];

NSString *wordstring = [NSString stringWithContentsOfFile:pathname];

NSArray *wordArray = [wordstring componentsSeparatedByString:@"\n"];

// Build the sorted section array

[self createSectionList:wordArray];

}

// Clean up

-(void) dealloc

{

[sectionArray release];

[super dealloc];

}

@end

Adding Section HeadersIt takes little work to add section headers to your grouped table. Listing 6-1 shows theoptional tableView: titleForHeaderInSection: method that titles each section. It’spassed an integer. In return, you supply a title.

Listing 6-1 Adding Titles to Sections

#define ALPHA_ARRAY [NSArray arrayWithObjects: @"A", @"B", @"C", @"D", @"E", @"F",@"G", @"H", @"I", @"J", @"K", @"L", @"M", @"N", @"O", @"P", @"Q", @"R", @"S",@"T", @"U", @"V", @"W", @"X", @"Y", @"Z", nil]

// Add a title for each section

- (NSString *)tableView:(UITableView *)tableView➥titleForHeaderInSection:(NSInteger)section

{

return [NSString stringWithFormat:@"Crayon names starting with '%@'",➥[ALPHA_ARRAY objectAtIndex:section]];

}

As Figure 6-2 (left) shows, when you supply this (optional) method, titles automati-cally appear.Title names appear above each group, on the blue-gray background.And asFigure 6-2 (right) shows, these titles display whether you supply section members.You

170 Chapter 6 Advanced Tables

Page 194: I Phone Developer Cookbook

171Recipe: Building a Section Table with an Index

Figure 6-2 (Left) Section titles appear on the gray-blue background aboveeach section. (Right) When sections have no members, the titles stack.

may want to weed through your titles and section data to remove empty sections toavoid the double headings shown here.

Recipe: Building a Section Table with an IndexSection lists need not be grouped. In fact, if you want to use an index, as shown inFigure 6-3 (left), avoid grouped lists.That is to say, it’s completely possible to use anindex with groups. It just looks ugly.

Create your section lists exactly as you would for a grouped table, but use the plain tablestyle when initializing and add a method to supply the index title array.This data sourcemethod is called sectionIndexTitlesForTableView.When used, as in Recipe 6-2, ittells the table to build a new UIControl for the index and add it to your table.

The method returns an array of strings. For Recipe 6-2, it’s a list of single characters,one per letter of the alphabet, but don’t let this limit your design.As Figure 6-3 (right)shows, you can return words as well as characters. It’s ugly, but it might be what youneed for your design.

Recipe 6-2 Building an Indexed Section List

- (HelloController *) init

{

if (self = [super initWithStyle:UITableViewStylePlain]) self.title = @"Crayon➥Colors";

Page 195: I Phone Developer Cookbook

Recipe 6-2 Continued

return self;

}

#define ALPHA_ARRAY [NSArray arrayWithObjects: @"A", @"B", @"C", @"D", @"E", @"F",@"G", @"H", @"I", @"J", @"K", @"L", @"M", @"N", @"O", @"P", @"Q", @"R", @"S",@"T", @"U", @"V", @"W", @"X", @"Y", @"Z", nil]

// Adding a section index here

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView

{

return ALPHA_ARRAY;

}

172 Chapter 6 Advanced Tables

Figure 6-3 (Left) Indexed lists enable you to jump directly to sections bytapping on their titles. (Right) Indices aren’t limited to single characters. If

desired, use entire words to create your index object.

Recipe: Custom Cell BackgroundsThe standard UITableView class contains a list of cells with white backgrounds.You canchange your table’s overall background color by setting its backgroundColor property.Set it to a color, and your cells take on that background color. Set it to clear, and anycontents behind your table leak through.

This is the way Apple has built the classes, and this is the way it intends your tables tolook. Getting around this behavior is not hard, but at the same time it’s nontrivial.Although not especially intuitive or straightforward, you can customize your tables, their

Page 196: I Phone Developer Cookbook

1. Access the index and clear its background.The index control at the right side of the table is a subview of your table view. Itsclass is UITableViewIndex.To customize, you can set its background color indi-vidually from the table. Otherwise, it inherits whatever color the table uses.

To let backgrounds leak through, use [UIColor clearColor]. Fortunately, youneed only do this once. Unfortunately, the index is not placed into the table untilafter the loadView method returns. So, you have to delay modifying it.

In Recipe 6-3, I wait until tableView: cellForRowAtIndexPath: is called dur-ing table creation.Then, I use a Boolean variable, clearedIndex, to make surethis modification is run just once.

To perform the modification, you need to find the actual index control object.After searching through the tableView’s subviews for the index object, I set the

173Recipe: Custom Cell Backgrounds

Figure 6-4 Customize your cells and index for custom backdrops.

cells and any index control by diving into the UIView hierarchy used in the table.Thesteps needed to build this effect in your own application are presented in this section.Figure 6-4 shows the results: a custom UITableView with individual colors for each cell.

As you can see from this image, the method does not affect section headers.You maywant to omit them from your table when making these modifications. (It looks a lot better without the section headers and runs more smoothly on the iPhone, but becauseRecipe 6-3 builds directly off Recipe 6-2, I kept them for continuity.)

There are three basic steps to follow to change the background:

Page 197: I Phone Developer Cookbook

index’s background color to clear. If you want, you can perform other index modi-fications here. For example, you might update its font to another style as desired.

2. Create a custom label view for cells.Standard UITableView cells are composed with a cell background view ontowhich is added a custom UILabel. Unfortunately, even though you can access thisstandard UILabel by browsing through subviews, it does not properly update itstraits when requested.

You can try clearing its background color, but the custom behavior prevents thisaction from completing, leaving you with a fixed color behind the text thatmatches the table’s background color. Instead, you need to add a standard UILabeland use that for your cell labels.This approach is a workaround, but it’s reliable andconsistent.

The workaround involves adding new UILabel labels when a cell is actually creat-ed.This gives them a fresh UILabel without any of the behavior issues associatedwith the UITableViewCell’s version. If you initialize your cells properly, they’llretain these labels when dequeued from the reuse stack.

Recipe 6-3 works like this. During cell creation, it allocates those new labels andadds them to the cell as a subview. It then tags the label so it can be retrieved forsetting text.

This differs slightly from the standard UITableViewCell structure, where labelsare usually children of the background, not a direct child of the cell itself.

3. Tagging the cell background.

The UITableViewCell’s first child is its background view. Recipe 6-3 tags thisview so that it can be recovered and colored on demand.To tint the background,adjust its backgroundColor property.To add custom image, create aUIImageView subview.

These three steps ensure that table elements, namely the index, cell titles, and cellbackgrounds, can be customized.

Recipe 6-3 Creating Custom Cell Backgrounds

// Return a cell on demand

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

{

// if the Index background has not yet been cleared, clear it

if (!clearedIndex) {

for (UIView *view in [tableView subviews])

if ([[[view class] description]➥isEqualToString:@"UITableViewIndex"]) {

[view setBackgroundColor:[UIColor clearColor]];

}

clearedIndex = YES;

174 Chapter 6 Advanced Tables

Page 198: I Phone Developer Cookbook

Recipe 6-3 Continued

}

// Recover section and row info

NSInteger row = [indexPath row];

NSInteger section = [indexPath section];

// Pull the cell

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"any-➥cell"];

UILabel *labelView = NULL;

// If there’s a new cell needed, add a custom label

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

cell.selectionStyle = UITableViewCellSelectionStyleGray;

// Tag the background view

[[[cell subviews] objectAtIndex:0] setTag:111];

// Add and tag the label view

labelView = [[UILabel alloc] initWithFrame: CGRectMake(0, 0, 300, 44)];

[labelView setBackgroundColor:[UIColor clearColor]];

[labelView setShadowColor:[UIColor whiteColor]];

[labelView setFont:[UIFont boldSystemFontOfSize:20]];

[labelView setTag:222];

[cell addSubview:labelView];

[labelView release];

}

// Set up the cell

NSArray *crayon = [[[sectionArray objectAtIndex:section] objectAtIndex:row]➥componentsSeparatedByString:@"#"];

// Recover the label

UILabel *labelView = (UILabel *)[cell viewWithTag:222];

[labelView setText:[crayon objectAtIndex:0]];

// recover the background

UIView *bgView = [cell viewWithTag:111];

[bgView setBackgroundColor:[self getColor:[crayon objectAtIndex:1]]];

return cell;

}

175Recipe: Custom Cell Backgrounds

Page 199: I Phone Developer Cookbook

Listing 6-2 Creating a Floating Semitranslucent Table

- (void)loadView

{

// Add a background

CGRect frame = [[UIScreen mainScreen] applicationFrame];

self.view = [[[UIImageView alloc] initWithImage:[UIImage➥imageNamed:@"bg.png"]] autorelease];

self.view.userInteractionEnabled = YES;

[self.view setFrame:frame];

// Set up the table -- allowing for customizations

frame = CGRectMake(30.0f, 40.0f, 260.0f, 300.0f);

176 Chapter 6 Advanced Tables

Figure 6-5 Tables can use translucency as shown in this highly customized table view.

Customizing the Table ViewFurther customize your tables by using a normal UIViewController and decouplingthe table view. Figure 6-5 shows a partially translucent table set in the middle of aUIImageView. Listing 6-2 shows the table creation. It is built with a small frame, setwith an alpha level of just 0.35, and added to the background as a subview.This kind ofmodification lets you break out of the “my application is the table, my table is the appli-cation” mindset and use scrolling tables more flexibly but again at a computational cost.Make sure you test on the iPhone to catch any performance issues introduced by yourcustomization.

Page 200: I Phone Developer Cookbook

Listing 6-2 Continued

tableView = [[UITableView alloc] initWithFrame:frame style:➥UITableViewStylePlain];

[tableView setDelegate:self];

[tableView setDataSource:self];

[tableView setBackgroundColor:[UIColor colorWithRed:1.0f green:1.0f➥blue:1.0f alpha:0.35f]];

[self.view addSubview:tableView];

[tableView release];

// Retrieve the text and colors from file

NSString *pathname = [[NSBundle mainBundle] pathForResource:@"crayons"➥ofType:@"txt" inDirectory:@"/"];

NSString *wordstring = [NSString stringWithContentsOfFile:pathname];

NSArray *wordArray = [wordstring componentsSeparatedByString:@"\n"];

// Build the sorted section array

[self createSectionList:wordArray];

clearedIndex = NO;

}

Recipe: Creating Alternate Blue and White CellsAlthough blue and white cell alternation is a common and highly requested table fea-ture, Apple did not include that style in its iPhone SDK.The custom cell techniquesshown in the last few recipes enable you to build the alternating white/blue cell struc-ture, as shown in Figure 6-6. Recipe 6-4 provides the cell creation method.

As with earlier recipes, this code uses a custom UILabel to blend text over whateverbackground is used.A simple even/odd check (row % 2) specifies whether to fill thebackground in with blue or white. Because this table uses just one section, it simplifiesthe math considerably. Blue/white alternating cells work best for nongrouped, nonsec-tion tables both visually and programmatically.

Recipe 6-4 Creating Alternating Blue and White Cells

- (UITableViewCell *)tableView:(UITableView *)tView➥cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

NSInteger row = [indexPath row];

// Pull the cell

UITableViewCell *cell = [tView dequeueReusableCellWithIdentifier:@"any-cell"];

UILabel *labelView = NULL;

// If there’s a new cell needed, add a custom label

if (cell == nil) {

177Recipe: Creating Alternate Blue and White Cells

Page 201: I Phone Developer Cookbook

Recipe 6-4 Continued

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

cell.selectionStyle = UITableViewCellSelectionStyleGray;

[[[cell subviews] objectAtIndex:0] setTag:111];

labelView = [[UILabel alloc] initWithFrame: CGRectMake(8, 0, 300, 44)];

[labelView setBackgroundColor:[UIColor clearColor]];

[labelView setTag:222];

[labelView setFont:[UIFont boldSystemFontOfSize:20]];

[cell addSubview:labelView];

[labelView release];

}

UIView *cellView = [cell viewWithTag:111];

if (row % 2)

[cellView setBackgroundColor:[UIColor whiteColor]];

else

[cellView setBackgroundColor:[UIColor colorWithRed:0.90f green:0.95f➥blue:1.0f alpha:1.0f]];

// recover labelView from the cell

[(UILabel *)[cell viewWithTag:222] setText:[[[sectionArray objectAtIndex:row]➥componentsSeparatedByString:@"#"] objectAtIndex:0]];

return cell;

}

178 Chapter 6 Advanced Tables

Figure 6-6 Use custom backgrounds to create alternating blue and white cells.

Page 202: I Phone Developer Cookbook

Recipe: Framing TablesFramed tables represent another frequently requested table feature not supplied withinthe SDK. Unlike the rounded sections used for group tables, a framed table enables usersto scroll cells within a rounded frame. Figure 6-7 shows a framed table. Here, the roundedborder stays still.All cell movement happens within and behind that border.

To make framing work, your table must be an independent view.This limitsyou to adding a UITableView to a UIViewController instead of using theUITableViewController class directly.The framing in turn is nothing but UIImage artadded to a UIImageView (see Figure 6-7, right).This view is added directly on top ofthe UITableView and set to be noninteractive with userInteractionEnabled = NO.That means that all user touches redirect to the table below.The overlay provides artwithout interfering with interaction.

Recipe 6-5 shows how to set up the table and add its frame using the art shown inFigure 6-7.The table is sized to match the frame opening, and the art is layered on topof the table.

179Recipe: Framing Tables

Figure 6-7 (Left) Framing tables add an iPhone style component toselections that aren’t meant to occupy the entire screen. (Right) This is the

art used to overlay the table to produce the desired framing.

Recipe 6-5 Framing a List View

- (void)loadView

{

// Add a background for the main content view

UIImageView *bg = [[[UIImageView alloc] initWithImage:[UIImage➥imageNamed:@"BG.png"]] autorelease];

Page 203: I Phone Developer Cookbook

Recipe 6-5 Continued

bg.userInteractionEnabled = YES;

self.view = bg;

[bg release];

// Build the table frame and add the table as a subview

CGRect frame = CGRectMake(10.0f, 20.0f, 300.0f, 440.0f);

tableView = [[UITableView alloc] initWithFrame:frame style:➥UITableViewStylePlain];

[tableView setDelegate:self];

[tableView setDataSource:self];

[bg addSubview:tableView];

[tableView release];

// Load the frame art, add it as a subview and set it

// to be non-interactive

UIImageView *fg = [[[UIImageView alloc] initWithImage:[UIImage➥imageNamed:@"cutbg.png"]] autorelease];

fg.userInteractionEnabled = NO;

[bg addSubview:fg];

[fg release];

}

Recipe: Adding Coupled Cell ControlsYou can increase a cell’s interactivity by adding UIControl items. Figure 6-8 shows atable with on/off switches.Adding this kind of cell extra depends on two things. First,you need to carefully manage your cell reuse. Second, you must expand your data sourceimplementation to include addressable state information for the controls.

As with custom labels used in previous recipes, Recipe 6-6 uses a cell queue. It addscontrols as new cells are created and recovers them from dequeued ones.This limits thenumber of actual control items used to about ten per program at most.

As for the state information, it’s held in a simple mutable dictionary (boolDict) andindexed by the cell name.The cells are added to the dictionary when used so that thedictionary remains sparse and controls are assumed to be “off ” until specified otherwise.Between the reusable cell queue and the sparse dictionary, this recipe produces a lot ofvisual bang for little memory buck.

Recipe 6-6 Building Table Cells with Custom Controls

// respond to user switches by updating the Boolean dictionary

- (void) switchEm: (UIControl *) sender

{

UITableViewCell *cell = (UITableViewCell *)[sender superview];

NSString *whichCell = [cell text];

180 Chapter 6 Advanced Tables

Page 204: I Phone Developer Cookbook

Recipe 6-6 Continued

NSNumber *value = [boolDict objectForKey:whichCell];

if (!value) // no value assigned, false

{

[boolDict setObject:[NSNumber numberWithBool:YES] forKey:whichCell]; // initialize

} else {

value = [NSNumber numberWithBool:(![value boolValue])]; // toggle

[boolDict setObject:value forKey:whichCell];

}

}

// Return a cell on demand

- (UITableViewCell *)tableView:(UITableView *)tableViewcellForRowAtIndexPath:(NSIndexPath *)indexPath

{

// Recover section and row info

NSInteger row = [indexPath row];

NSInteger section = [indexPath section];

// Pull the cell

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"any-➥cell"];

UISwitch *switchView = NULL;

// If there’s a new cell needed, add a custom switch

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

cell.selectionStyle = UITableViewCellSelectionStyleGray;

cell.indentationLevel = 5;

switchView = [[UISwitch alloc] initWithFrame: CGRectMake(4.0f, 16.0f,➥100.0f, 28.0f)];

[switchView setTag:997];

[cell addSubview:switchView];

[switchView release];

}

// recover switchView from the cell

switchView = [cell viewWithTag:997];

[switchView addTarget:self action:@selector(switchEm:)➥forControlEvents:UIControlEventValueChanged];

// Set up the cell

NSArray *crayon = [[[sectionArray objectAtIndex:section] objectAtIndex:row]➥componentsSeparatedByString:@"#"];

[cell setText:[crayon objectAtIndex:0]];

181Recipe: Adding Coupled Cell Controls

Page 205: I Phone Developer Cookbook

Recipe 6-6 Continued

// Retrieve the switch state and apply it

NSNumber *value = [boolDict objectForKey:[crayon objectAtIndex:0]];

if (!value) value = [NSNumber numberWithBool:NO];

[switchView setOn:[value boolValue] animated:NO];

return cell;

}

182 Chapter 6 Advanced Tables

Figure 6-8 Use cell reuse to create memory-efficient tables with custom controls.

Recipe: Building a Multiwheel TableSometimes you’d like your users to pick from several lists at once.That’s whereUIPickerViews really excel. UIPickerViews produce tables offering individually scrolling “wheels,” as shown in Figure 6-9. Users interact with each wheel to build their selection.

These tables, although superficially similar to standard UITableViews, use distinct dataand delegate protocols.To be specific, at the time of writing Apple has not specified adata source protocol at all.All picker view support is handled through theUIPickerViewDelegate protocol including the calls normally associated with datasources. Here are some points to keep in mind about the ways picker views differ fromtables:

Page 206: I Phone Developer Cookbook

n There are no UIPickerViewControllers.

UIPickerView instances act as subviews to other views.They are not intended tobe the central focus of an application view.

n Picker views use numbers not objects.

Components, that is to say the wheels, are indexed by numbers, not byNSIndexPath instances. It’s altogether a much more informal class than theUITableView.

n The view height for pickers is static.

You can’t resize pickers the way you would a standard UITableView just bymanipulating its frame.All pickers are 216 pixels high, a number you can test bylining the view up with the bottom of the screen.Any other frame height looksdistorted or clipped.

Creating the UIPickerViewYou can use any frame size for your UIPickerView as long as your height is 216 pixelsand your width is 320 pixels.That being said, you can float the table wherever you needit on the screen.And float you’ll have to.The picker does not respond well to iPhonereorientation, so you need to center it and keep it onscreen should the user rotate his orher unit.

183Recipe: Building a Multiwheel Table

Figure 6-9 UIPickerView instances enable users to select from independently scrolling wheels.

Page 207: I Phone Developer Cookbook

When creating the picker, remember two key points. First, you want to enable theselection indicator.That is the blue bar that floats over the selected items. So setshowsSelectionIndicator to YES. Second, don’t forget to assign the delegate.Without delegate support, you cannot add data to the view or define its features.

Implement three key data source methods for your UIPickerView to make it func-tion properly.These methods are as follows. Let your application control the number ofcolumns and the rows that form those columns:

n numberOfComponentsInPickerView. Return an integer, the number ofcolumns.

n pickerView: numberOfRowsInComponent. Return an integer, the maximumnumber of rows per wheel.These numbers do not need to be identical.You canhave one wheel with many rows and another with very few.

n pickerView: titleForRow: forComponent. This method specifies the textused to label a row on a given component. Return an NSString.

In addition to these data source methods, you want to supply one further delegatemethod.This method responds to user interactions via wheel selection:

n pickerView: didSelectRow: inComponent. Add any application-specificbehavior to this method. If needed, you can query the pickerView to return theselectedRowInComponent: for any of the wheels in your view.

NoteThe undocumented setSoundsEnabled: controls whether your users hear the clickingeffects associated with the wheels.

Recipe 6-7 Using a UIPickerView for Multicolumn Selection

@interface HelloController : UIViewController <UIPickerViewDelegate>

{

UIPickerView *pickerView;

UILabel *comboView;

}

@end

@implementation HelloController

- (HelloController *) init

{

if (self = [super init]) self.title = @"Combo Picker";

return self;

}

184 Chapter 6 Advanced Tables

Page 208: I Phone Developer Cookbook

Recipe 6-7 Continued

// Number of wheels

- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView { return 3; }

// Number of rows per wheel

- (NSInteger)pickerView: (UIPickerView *)pView numberOfRowsInComponent:➥(NSInteger) component { return 30; }

// Return the title of each cell by row and component

- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row➥ forComponent:(NSInteger)component

{

return [NSString stringWithFormat:@"%@%d", (component != 1) ? @"Left" :➥@"Right", row];

}

// Respond to user selection

- (void)pickerView:(UIPickerView *)aPickerView didSelectRow:(NSInteger)row➥inComponent:(NSInteger)component

{

printf("User selected row %d for component %d\n", row, component);

int first = [pickerView selectedRowInComponent:0];

int second = [pickerView selectedRowInComponent:1];

int third = [pickerView selectedRowInComponent:2];

NSString *results = [NSString stringWithFormat:@"Left-%@%d Right-%@%d ➥Left-%@%d",

(first < 10) ? @"0" : @"",

first,

(second < 10) ? @"0" : @"",

second,

(third < 10) ? @"0" : @"",

third];

[comboView setText:results];

}

- (void)loadView

{

self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

self.view.backgroundColor = [UIColor lightGrayColor];

// Add the picker

float height = 216.0f;

185Recipe: Building a Multiwheel Table

Page 209: I Phone Developer Cookbook

Recipe 6-7 Continued

pickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0.0f, ➥416.0f - height, 320.0f, height)];

pickerView.delegate = self;

pickerView.showsSelectionIndicator = YES;

[self.view addSubview:pickerView];

[pickerView release];

// Add a results label

comboView = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 170.0f, 320.0f,➥32.0f)];

[comboView setBackgroundColor:[UIColor clearColor]];

[comboView setTextAlignment:UITextAlignmentCenter];

[comboView setText:@"Left-00 Right-00 Left-00"];

[self.view addSubview:comboView];

[comboView release];

}

-(void) dealloc

{

[pickerView release];

[comboView release];

[super dealloc];

}

@end

Recipe: Using the UIDatePickerWhen you want to ask your user to enter date information,Apple supplies a tidy subclassof UIPickerView to handle several kinds of time entry. Figure 6-10 shows the fourbuilt-in styles of UIDatePickers that you can choose from.These range from selecting atime, to selecting a date, to selecting a combination of the two.

Creating the Date PickerLay out a date picker exactly as you would a UIPickerView. Create a frame that’s320 pixels wide by 216 pixels high.After that, things get much, much easier.You neednot set a delegate or define data source methods. Instead, you assign a date pickermode. (Choose from UIDatePickerModeTime, UIDatePickerModeDate,UIDatePickerModeDateAndTime, and UIDatePickerModeCountDownTimer), andthen add a target for when the selection changes (UIControlEventValueChanged.)

All that remains is to create the callback method you triggered with the value-changed event.After that you’re done. Implementing UIDatePicker objects is almostinsanely simple, as Recipe 6-8 demonstrates.

186 Chapter 6 Advanced Tables

Page 210: I Phone Developer Cookbook

187Recipe: Using the UIDatePicker

Figure 6-10 The iPhone offers four stock date picker models.Use the datePickerMode property to select the picker you want

to use in your application.

Page 211: I Phone Developer Cookbook

Here are a few properties you’ll want to take advantage of in the UIDatePicker class:n date

Set the date property to initialize the picker.n maximumDate and minimumDate

These properties set the bounds for date and time picking.Assign each one a stan-dard NSDate.With these, you can constrain your user to pick a date from next yearrather than just enter a date and then check whether it falls within an acceptedtime frame.

n minuteInterval

Sometimes you want to use 5-, 10-, 15-, or 30-minute intervals on your selections.Use the minuteInterval property to specify that value.Whatever number youpass, it has to be evenly divisible into 60.

n countDownDuration

Use this property to set the maximum available value for a countdown timer.Youcan go as high as 23 hours and 59 minutes (that is, 86,399 seconds).

Recipe 6-8 Using the UIDatePicker to Select Dates and Times

// Respond to a user when the date has changed

- (void) changedDate: (UIDatePicker *) picker

{

NSString *caldate = [[[picker date] dateWithCalendarFormat:@"%B %d, %Y"➥timeZone:nil] description];

[resultsView setText:caldate];

}

- (void)loadView

{

self.view = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥ applicationFrame]];

self.view.backgroundColor = [UIColor lightGrayColor];

// Add the picker

float height = 216.0f;

pickerView = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0f, 416.0f -➥height, 320.0f, height)];

pickerView.datePickerMode = UIDatePickerModeDate;

[pickerView addTarget:self action:@selector(changedDate:)➥forControlEvents:UIControlEventValueChanged];

[self.view addSubview:pickerView];

[pickerView release];

// Add a results label

resultsView = [[UILabel alloc] initWithFrame:CGRectMake(0.0f, 170.0f, 320.0f,➥32.0f)];

188 Chapter 6 Advanced Tables

Page 212: I Phone Developer Cookbook

Recipe 6-8 Continued

[resultsView setBackgroundColor:[UIColor clearColor]];

[resultsView setTextAlignment:UITextAlignmentCenter];

[self.view addSubview:resultsView];

[resultsView release];

}

Recipe: Creating Fully Customized Group TablesIf alphabetic section list tables are the M. C. Eschers of the iPhone table world, witheach section block precisely fitting into the negative spaces provided by other sections inthe list, then freeform group tables are the Marc Chagalls. Every bit is drawn as afreeform handcrafted work of art.

It’s relatively easy to create all the tables you’ve seen so far in this chapter once you’vemastered the knack. Perfecting the group table (usually called “preferences table” byiPhone devotees because that’s the kind of table used in the Settings application) remainsan illusion. Building group tables is all about the collage.They’re all about handcrafting alook, piece by piece.

Tools like Interface Builder are meant to place layout directly in the hands of thedeveloper instead of trying to approach things programmatically. Unfortunately, there’s aproblem: Interface Builder does not, at least at this time, create preferences tables. So, it’sup to you to programmatically put all that material together after all.There are still standardized methods and approaches you can take to build these tables.These all involvemonstrous data source methods with lots and lots of switch calls.

When you’ve got the basics under control, the preferences table becomes a projectyou can mold and shape. Figure 6-11 shows a simple preferences table that consists oftwo groups: a four-item checklist and a block with text and a text entry field. Recipe 6-9demonstrates all the work that goes into providing even such a little creation.

Unfortunately, adding new items or updating old ones requires a lot of fine detailwork.That work isn’t centralized in any way.You must review each of the data sourcemethods and update with your new or refined items.The next section introduces whatthose data source methods are and what they do.

Creating Grouped Preferences TablesThere’s nothing special involved in terms of laying out a new UITableViewControllerfor a preferences table.You allocate it.You initialize it with the grouped table style.That’spretty much the end of it.All done and dusted. It’s the data source and delegate methodsthat provide the challenge. Here are the methods you’ll need to define:

189Recipe: Creating Fully Customized Group Tables

Page 213: I Phone Developer Cookbook

n numberOfSectionsInTableView:

All preferences tables contain groups of items. Each group is contained in one ofthose lovely rounded rectangles. Return the number of groups you’ll be definingas an integer.

n tableView: titleForHeaderInSection:

Add the titles for each section into this method. Return an NSString with therequested section name.

n tableView: numberOfRowsInSection:

Each section may contain any number of cells. Have this method return an integerindicating the number of rows (that is, cells) for that group.

n tableView: heightForRowAtIndexPath:

Tables that use flexible row heights cost more in terms of computational intensity.If you need to use variable heights (Recipe 6-9 does so), implement this optionalmethod to specify what those heights will be.

n tableView: cellForRowAtIndexPath:

This is the standard cell-for-row method you’ve seen throughout this chapter.What sets apart is its implementation. Instead of using one kind of cell, Recipe 6-9builds different kinds of reusable cells (with different reuse tags) for each cell type.As this recipe shows, things become much more complicated when using severalcell types. Make sure you manage your reuse queue carefully.

190 Chapter 6 Advanced Tables

Figure 6-11 Preferences tables must be laid out by hand, with each rowand group specified through your data source methods.

Page 214: I Phone Developer Cookbook

n tableView: didSelectRowAtIndexPath:

You provide case-by-case reactions to cell selection in this delegate methoddepending on the cell type selected.

Recipe 6-9 A Simple Grouped Preferences-Style Table

// Number of groups

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 2;

}

// Section Titles

- (NSString *)tableView:(UITableView *)tableView➥titleForHeaderInSection:(NSInteger)section

{

switch (section)

{

case 0:

return @"Civics";

case 1:

return @"Checked Items";

default:

return @"";

}

}

// Number of rows per section

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

{

switch (section)

{

case 0:

return 2;

case 1:

return 4;

default:

return 0;

}

}

// Heights per row

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndex➥Path *)indexPath

{

int section = [indexPath section];

191Recipe: Creating Fully Customized Group Tables

Page 215: I Phone Developer Cookbook

Recipe 6-9 Continued

int row = [indexPath row];

switch (section)

{

case 0:

if (row == 0) return 80.0f;

return 350.0f;

case 1:

return 44.0f;

default:

return 44.0f;

}

}

// Produce cells

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

{

NSInteger row = [indexPath row];

NSInteger section = [indexPath section];

UITableViewCell *cell;

switch (section)

{

case 0:

if (row == 0) {

// Add a text field to the cell

cell = [tableView dequeueReusableCellWithIdentifier:@"textCell"];

if (!cell) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"textCell"] autorelease];

UILabel *label = [[UILabel alloc]➥initWithFrame:CGRectMake(20.0f, 10.0f, 100.0f, 20.0f)];

[label setText:@"Name:"];

[cell addSubview:label];

[label release];

[cell addSubview:[[UITextField alloc]➥initWithFrame:CGRectMake(20.0f, 40.0f, 280.0f, 30.0f)]];

}

UITextField *tf = [[cell subviews] lastObject];

tf.placeholder = @"Enter Your Name Here";

tf.delegate = self;

tf.borderStyle = UITextBorderStyleBezel;

return cell;

}

if (row == 1) {

// Create a big word-wrapped UILabel

192 Chapter 6 Advanced Tables

Page 216: I Phone Developer Cookbook

Recipe 6-9 Continued

cell = [tableView➥dequeueReusableCellWithIdentifier:@"libertyCell"];

if (!cell) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"libertyCell"] autorelease];

[cell addSubview:[[UILabel alloc]➥initWithFrame:CGRectMake(20.0f, 10.0f, 280.0f, 330.0f)]];

}

UILabel *sv = [[cell subviews] lastObject];

sv.text = @"When in the Course of human events, it becomes necessary for one people to dissolve the political bands which have connected themwith another, and to assume among the powers of the earth, the separate and equalstation to which the Laws of Nature and of Nature’s God entitle them, a decentrespect to the opinions of mankind requires that they should declare the causeswhich impel them to the separation.";

sv.textAlignment = UITextAlignmentCenter;

sv.lineBreakMode = UILineBreakModeWordWrap;

sv.numberOfLines = 9999;

return cell;

}

break;

case 1:

// Create cells with accessory checking

cell = [tableView dequeueReusableCellWithIdentifier:@"checkCell"];

if (!cell) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"checkCell"] autorelease];

cell.accessoryType = UITableViewCellAccessoryNone;

}

cell.text = [NSString stringWithFormat:@"This is option #%d", ➥row + 1];

return cell;

break;

default:

break;

}

// Return a generic cell if all else fails

cell = [tableView dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

return cell;

}

- (void) notify: (NSString *) aMessage

{

193Recipe: Creating Fully Customized Group Tables

Page 217: I Phone Developer Cookbook

Recipe 6-9 Continued

UIAlertView *baseAlert = [[UIAlertView alloc]

initWithTitle:@"Alert" message:aMessage

delegate:self cancelButtonTitle:nil

otherButtonTitles:@"Okay", nil];

[baseAlert show];

}

// TextField delegate handles return events and dismisses keyboard

- (BOOL)textFieldShouldReturn:(UITextField *)textField

{

[textField resignFirstResponder];

[self notify:[NSString stringWithFormat:@"Hello %@", [textField text]]];

return YES;

}

// Respond to user selection based on the cell type

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)➥newIndexPath

{

int section = [newIndexPath section];

int row = [newIndexPath row];

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:newIndexPath];

switch (section)

{

case 0:

if (row == 0)

{

[self notify:[NSString stringWithFormat:@"Hello %@", ➥[[[cell subviews] lastObject] text]]];

}

case 1:

if (cell.accessoryType == UITableViewCellAccessoryNone)

cell.accessoryType = UITableViewCellAccessoryCheckmark;

else

cell.accessoryType = UITableViewCellAccessoryNone;

break;

break;

default:

break;

}

[self performSelector:@selector(deselect) withObject:NULL afterDelay:0.5];

}

194 Chapter 6 Advanced Tables

Page 218: I Phone Developer Cookbook

SummaryThis chapter has introduced advanced iPhone tables: from multirowed picker elements toindexed alphabetic listings to preferences tables.The skills covered in this chapter shouldenable you to build a wealth of table-based applications for the iPhone and iPod touch.Here are some key points to take away from this chapter:

n Apple has designed UITableViews to work in certain ways. Defy Apple. Don’tconstrain yourself to those design paths if they don’t take you where you need togo.This chapter covered many alternative ways to gain access to cell and table subviews and manage those views as needed.

n Index controls provide a great way to navigate quickly through large ordered lists.Take advantage of their power when working with tables that would otherwisebecome unnavigable.

n Date pickers are highly specialized and very good at what they do: soliciting yourusers for dates and times. Picker views provide a less-specialized solution butrequire more work on your end to bring them to life.

n As complicated and annoying as preferences tables are to program, they are the sin-gle most requested feature when it comes to iPhone programming.You see themalmost ubiquitously in third-party software.They allow you to combine manykinds of interactive input on a good-looking and easy-to-use scrolling page.Whenyou’ve conquered all the fussy aspects, they become a powerful tool in your pro-gramming arsenal.

195Summary

Page 219: I Phone Developer Cookbook
Page 220: I Phone Developer Cookbook

7Media

As you’d expect, the iPhone can load and display media from a wide variety of for-mats. It does music; it does movies. It handles images and Web pages.You can presentPDF documents and photo albums and more. In this chapter, you’re about to discoverway after way that you can import or download data into your program and display thatdata using the iPhone’s multitouch interface.This chapter introduces recipes that let youpresent a rich variety of media types to your users.You’ll see how to build viewers forthese data types and how to load them with content both from the iPhone itself andfrom remote locations.

Recipe: Browsing the Documents Folder by File TypeThe Documents folder does exactly what the name suggests.You store documents to andaccess them from this directory.Apple suggests you store file data created by or browsedfrom your program.

The Documents folder resides in the application sandbox. It is one of three directo-ries automatically created in the sandbox in addition to your application bundle.Theother two are Library and tmp. Library stores user defaults and other state informationfor your program.The tmp folder provides a place to create transient files on-the-fly.Files in Documents and Library are not transient. iTunes backs up all Documents andLibrary files whenever the iPhone syncs.When the iPhone reboots, it discards any tmpfiles.

The one standard folder that’s missing from this mix is Application Support. On theMac, the Application Support folder has a specific role. It holds files, plug-ins, and sup-porting data that don’t belong in your application bundle, which support the applicationas it runs, but which are not vital for the proper execution of your application. On theiPhone, any materials like this should be stored either in the Library folder (preferablefor more static material) or Documents folder (for mutable data like SQL data bases thatthe user will change and update).

Page 221: I Phone Developer Cookbook

Often, when working with media, you’ll want to browse your documents folder tofind files that your user has created or downloaded. Browsing this folder allows you toproduce a user-selectable list of available files.

Recipe 7-1 demonstrates how to create a table and populate it with filenames fromthe Documents folder.These names are selected by path extension.This code collects allfiles that match into an array of path extensions. Figure 7-1 shows this in action, listingdocument files ending with txt, c, h, or m.

198 Chapter 7 Media

Figure 7-1 Browsing by extension type in the Documents folderenables you to present a list of matching filenames to your users.

Locating DocumentsYou’re free to use both standard and nonstandard file locations on the Macintosh.TheiPhone with its sandbox is far more structured—rigidly so by Apple’s dictates. Its filesappear in better-defined locations. On the Macintosh, locating the documents folderusually means searching the user domain.This is the standard way to locate documentsfolders:

NSArray *paths =NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,

NSUserDomainMask, YES); return [paths lastObject];

The iPhone is more constrained.You can reliably locate the top sandbox folder by call-ing the home directory function.This lets you navigate down one level to the Documentsfolder as a matter of course with full assurance of reaching the proper destination:

Page 222: I Phone Developer Cookbook

#define DOCUMENTS_FOLDER [NSHomeDirectory()stringByAppendingPathComponent:@"Documents"]

With the Documents folder located, pull in an array of files by callingNSFileManager’s directoryContentsAtPath:. NSArray’spathsMatchingExtensions: method retrieves just those file types you want to workwith. Recipe 7-1 uses these methods to create a file list of text files that feed a table asits data source.

Recipe 7-1 Building a User-Selectable Table of Documents

#define DOCUMENTS_FOLDER [NSHomeDirectory()stringByAppendingPathComponent:@"Documents"]

- (HelloController *) init

{

if (self = [super init]) self.title = @"Text Files";

// initialize the file list with any text documents

fileList = (NSMutableArray *) [[[[NSFileManager defaultManager]➥directoryContentsAtPath:DOCUMENTS_FOLDER] pathsMatchingExtensions:[NSArray➥arrayWithObjects:@"txt", @"c", @"h", @"m", nil]] retain];

return self;

}

- (void) loadView

{

[super loadView];

// Reveal the documents folder — so you can pre-fill it for the simulator

printf("Documents folder is %s\n", [DOCUMENTS_FOLDER UTF8String]);

self.tableView.autoresizesSubviews = YES;

self.tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView

{

return 1;

}

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

{

return [fileList count];

}

199Recipe: Browsing the Documents Folder by File Type

Page 223: I Phone Developer Cookbook

Recipe 7-1 Continued

// Return a cell for the ith row

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

{

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"any-➥cell"];

if (cell == nil) {

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

}

cell.text = [fileList objectAtIndex:[indexPath row]];

return cell;

}

Loading and Viewing ImagesWhen working with images, no single solution fits all problems. In the next few sections,you’ll discover recipes that enable you to work with various kinds of images and imagesources.These recipes range from local display to images downloaded live from theWorld Wide Web. Each recipe offers a different spin on what you might otherwiseassume was a simple issue.

Image display is all about memory.Treat small and large image display as two separateproblems.With small images, say less than half a megabyte in size, load them directly toUIImageView and throw them onscreen.You can even layer them onto a scroller (seeRecipe 7-2) to enable user-directed resizing and placement.

With big images, a class such as UIWebView easily handles memory-intense data (asshown in Recipe 7-3). WebView objects offer a less satisfactory interface because they areoptimized for PDF files and Web pages, but they won’t choke when it comes to big files.You get the resizing and display power you need, but the initial presentation tends to beextremely compact.At the time of writing,Apple does not provide a “scale to screen”option to start the UIWebView image presentation at the best possible scale.

When viewing and selecting images from onboard albums, look no further than theUIImagePickerController class. It simplifies browsing through both your camera rolland your synced albums as well as picking images, cropping them, and snapping newpictures. Recipes 7-4 through 7-6 demonstrate its range of functionality.

As you’ll see, each of these recipes introduces a different way to display and interactwith your iPhone images.

NoteThe iPhone supports the following image types: PNG, JPG, THM, JPEG, TIF, TIFF, GIF, BMP,BMPF, ICO, CUR, XBM, and PDF.

200 Chapter 7 Media

Page 224: I Phone Developer Cookbook

Recipe: Displaying Small ImagesIt’s trivial to display small images (under about a half megabyte in size).This holds especially true when loading them from the application bundle. Use the UIImageimageNamed: method to load UIImageView with the contents of an application image.Results returned by imageNamed: are cached, so file retrieval is extremely efficient:

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];[contentView setImage:[UIImage imageNamed:@"helloworld.png"]];

You won’t always store images in the application bundle.To load pictures from otherlocations, use imageWithContentsOfFile: and pass it a path to your image. UIImagehandles the rest, loading the image from a file:

NSString *path = [DOCUMENTS_FOLDERstringByAppendingString:@"myImage.png"];[contentView setImage:[UIImage imageWithContentsOfFile:path]];

You can also load image data from a live host instead of grabbing it from a file.Recipe 7-2 demonstrates how to initialize image data from the contents of a Web-basedURL. In this case, the image data is an up-to-date weather map fromwww.weather.com.This code loads that data to a UIImageView object and layers thatimage view onto a UIScrollView instance.This produces the fully interactive interfaceshown in Figure 7-2. Users can scroll through the map and zoom in and out.

In this recipe, the scroll view matches its content size to the reported image size

201Recipe: Displaying Small Images

Figure 7-2 This live weather map is downloaded from a World WideWeb URL and layered onto a scroll view that enables users to scale

and pan through the image.

Page 225: I Phone Developer Cookbook

([img size]), and the maximum and minimum zooms range between two times actualto half of actual size.These settings allow full user interaction with the image while limiting that interaction to a reasonable scope that won’t unduly burden the iPhone’smemory.

NoteThe UIImageView class can display a single image or a moving slideshow. Recipe 4-8’sbutterfly shows how to animate an image display.

Recipe 7-2 Downloading Image Data to Display on a UIScrollView

@interface HelloController : UIViewController <UIScrollViewDelegate>

{

UIScrollView *contentView;

UIImageView *imgView;

}

@end

@implementation HelloController

- (id)init

{

if (self = [super init]) self.title = @"Weather Map";

return self;

}

// Return the UIImageView instance that gets zoomed by the scroller

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView

{

return imgView;

}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view➥atScale:(float)scale

{

// perform any post-scaling tasks here like re-centering

}

- (void)loadView

{

// Download the 600x405 weather map and load it into the image view

NSURL *url = [NSURL URLWithString:@"http://image.weather.com/images/maps/➥current/curwx_600x405.jpg"];

UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

imgView = [[UIImageView alloc] initWithImage:img];

202 Chapter 7 Media

Page 226: I Phone Developer Cookbook

Recipe 7-2 Continued

[imgView setUserInteractionEnabled:NO];

// Set up the main scroller

contentView = [[UIScrollView alloc] initWithFrame: [[UIScreen mainScreen]➥applicationFrame]];

[contentView setScrollEnabled:YES];

[contentView setContentSize:[img size]];

[contentView addSubview:imgView];

[imgView release] ;

[contentView setMaximumZoomScale: 2.0f];

[contentView setMinimumZoomScale: 0.5f];

[contentView setDelegate:self];

self.view = contentView;

[contentView release]; // reduce retain count by one

// Provide support for auto-rotation and resizing

contentView.autoresizesSubviews = YES;

contentView.autoresizingMask = (UIViewAutoresizingFlexibleWidth |➥UIViewAutoresizingFlexibleHeight);

}

// Allow the view to respond to iPhone Orientation changes

-(BOOL)shouldAutorotateToInterfaceOrientation:➥(UIInterfaceOrientation)interfaceOrientation

{

return YES;

}

-(void) dealloc

{

[imgView release];

[contentView release];

[super dealloc] ;

}

@end

Recipe: Using a UIWebView to Display ImagesAs image size grows, you can shift away from the scroll view/image view approach anduse the UIWebView class instead.The UIWebView class is a marvel. Recipe 7-3 demon-strates how to load and display images using this class. It uses the document browserfrom Recipe 7-1, customized to enable selection of PNG, JPEG,TIFF, and PDF images.No further programming is necessary to support these file types.They’re all built in,

203Recipe: Using a UIWebView to Display Images

Page 227: I Phone Developer Cookbook

including the PDF paging support shown in Figure 7-3 (left) and, as you’d suppose,HTML/CSS support.

NoteLoading large PDF data files into UIWebView instances may triggerapplicationDidReceiveMemoryWarning: callbacks to your application delegate. Youcan generally ignore these warnings as the Apple-supplied UIWebView class properlyreduces its memory load. Use Instruments to profile your application if you have furtherconcerns.

The drawback is, as already mentioned, that for images you don’t get any immediatecontrol over image size. Images display at an alarmingly high pixels per inc (ppi), asFigure 7-3 (right) shows.Work around this by generating HTML source to set the imagewidth and height properties and produce the exact presentation desired.

204 Chapter 7 Media

Figure 7-3 (Left) UIWebView objects ship with built-in PDF paging support as well as full compatibility with all iPhone-supported image

types. (Right) When working with UIWebView, the default image display size is far from ideal.

Recipe 7-3 Displaying Images with the UIWebView Class

#define DOCUMENTS_FOLDER [NSHomeDirectory()➥stringByAppendingPathComponent:@"Documents"]

Page 228: I Phone Developer Cookbook

Recipe 7-3 Continued

@interface WebController : UIViewController

{

UIWebView *webView;

}

@end

@implementation WebController

- (id)initWithFile: filename

{

// Initialize a URL pointing to the image file to display

NSString *path = [DOCUMENTS_FOLDER stringByAppendingPathComponent:filename];

NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:path];

NSURLRequest *req = [NSURLRequest requestWithURL:fileURL];

// Initialize the Web view

webView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

// allow user interaction

[webView setScalesPageToFit:YES];

// Load the Web view with the contents of the image file URL

[webView loadRequest:req];

return self;

}

- (void)loadView

{

self.view = webView;

[webView release];

}

-(void) dealloc

{

[webView release];

[super dealloc];

}

@end

Displaying Web Pages with UIWebViewIt takes a few small changes to modify Recipe 7-3 to display Web pages rather thanimages.These changes are as follows:

205Recipe: Using a UIWebView to Display Images

Page 229: I Phone Developer Cookbook

1. Switch the extensions to search for HTML files rather than images in your maininterface.

2. Replace the WebController‘s initWithFile: with the contents of Listing 7-1.

This simple two-step swap-out provides you with a rich-featured Web browser forlocal HTML source.You’ll be able to connect to Web-based assets and navigate by tapping on links. UIWebView offers a full-featured Safari-esque browser in a simple-to-use class.

Listing 7-1 Loading HTML Source into UIWebView Objects

- (id)initWithFile: filename

{

// Read in the HTML source

NSString *fileText = [NSString stringWithContentsOfFile:filename➥encoding:NSUTF8StringEncoding error:NULL];

// Initialize the Web view

webView = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

// Load it with the HTML source

if (!fileText) [webView loadHTMLString: [error localizedDescription]➥baseURL:fileURL]; else [webView loadHTMLString:fileText baseURL:fileURL];

// Allow direct user interaction

[webView setScalesPageToFit:YES];

return self;

}

Recipe: Browsing Your Image LibraryThe UIImagePickerController class is an odd one. It’s highly specialized with rela-tively few public methods and some moderate quirks. For example, it has its own naviga-tion controller built in. If you push it onto an existing navigation controller–based viewscheme, it adds a second navigation bar below the first.That means although you can useit with a tab bar and although you can use it as an independent view system, that youcan’t really push it onto an existing navigation stack and have it look right. Instead, it’smeant to be used as a modal controller.You’ll see this in the next recipe, while thisrecipe introduces a more direct method of working with its built-in navigation.

The UIImagePickerController’s navigation controller is integrated directly intothe picker object.You don’t pop back to the root view the way you do with normalview stacks (that is, [self.navigationController popToRootViewAnimated:YES]).Instead, pop the controller directly (that is, [selfpopToRootViewControllerAnimated:YES]).This moves control back to the albums

206 Chapter 7 Media

Page 230: I Phone Developer Cookbook

screen.Why would you want to do this? Popping after each selection lets you set up aloop like the one used in Recipe 7-4, letting your user pick photos one after anotheruntil leaving the program.

Recipe 7-4 shows the picker in its simplest mode, pick-from-album mode, as seen inFigure 7-4. Set the picker to use any of the legal source types.The three kinds of sourcesare as follows:

n UIImagePickerControllerSourceTypePhotoLibrary, all images synced to theiPhone plus any camera roll (used here)

n UIImagePickerControllerSourceTypeSavedPhotosAlbum, aka just the camera roll

n UIImagePickerControllerSourceTypeCamera, which enables you to shootpictures with the built-in iPhone camera

207Recipe: Browsing Your Image Library

This recipe follows a basic work path. Select an album, select an image, and repeat.This simple flow works because there’s no image editing involved.That’s because theallowsImageEditing property defaults to NO. So, any selection (basically any imagetap) redirects control to the UIImagePickerControllerDelegate object to the fin-ished picking image method.When image editing is enabled, the callback flow changes,and you’re much better off using the kind of modal interaction style introduced inRecipe 7-5.

Figure 7-4 Apple supplies several prebuilt albums, including thisGraduation Day one, for in-Simulator testing.

Page 231: I Phone Developer Cookbook

NoteRecipe 7-4 saves a copy of the selected image to the Documents folder. It grabs the PNGrepresentation of the image (this returns NSData *) and writes that to file.

Recipe 7-4 Selecting an Image and Writing It to the Documents Folder

@interface HelloController : UIImagePickerController<UIImagePickerControllerDelegate>

@end

@implementation HelloController

#define SOURCETYPE UIImagePickerControllerSourceTypePhotoLibrary

#define DOCSFOLDER [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]

- (id) init

{

if (self = [super init]) self.title = @"Hello World";

if ([UIImagePickerController isSourceTypeAvailable:SOURCETYPE])

self.sourceType = SOURCETYPE;

self.delegate = self;

return self;

}

- (void)imagePickerController:(UIImagePickerController *)picker➥didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo

{

// Find a unique path name so files are not overwritten

int i = 0;

NSString *uniquePath = [DOCSFOLDER➥stringByAppendingPathComponent:@"selectedImage.png"];

while ([[NSFileManager defaultManager] fileExistsAtPath:uniquePath])

uniquePath = [NSString stringWithFormat:@"%@/%@-%d.%@", DOCSFOLDER,➥@"selectedImage", ++i, @"png"];

printf("Writing selected image to Documents folder\n");

[UIImagePNGRepresentation(image) writeToFile: uniquePath atomically:YES];

[self popToRootViewControllerAnimated:YES];

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker

{

printf("User cancelled\n");

[self popToRootViewControllerAnimated:YES];

}

@end

208 Chapter 7 Media

Page 232: I Phone Developer Cookbook

Recipe: Selecting and Customizing Images fromthe Camera RollThis recipe extends image picker interaction to add image edits.Working with editsmore or less mandates modal interaction. Modal presentations limit the user’s interactionoptions to a single view, in this case one presented by the UIImagePickerController,for some period of time. Chapter 4,“Alerting Users,” introduced modal interaction foralert view and action sheet objects. Recipe 7-5 uses modality with image pickers.

Modal presentations are built in to every UIViewController.They work like this.When a parent controller calls presentModalViewController: animated:, a childview controller slides onscreen from the bottom of the window.The user interacts withthe modal controller, finishes his or her work, and then you calldismissModalViewControllerAnimated:.This slides the controller back away. Modalpresentations let you temporarily redirect control away from your main view structureand return when the subtask completes.

To enable image editing in a UIImagePickerController, set theallowsImageEditing property to YES.This enables users to scale and position imagesafter selection, or in the case of camera shots, after snapping a photo. On the iPhone, thisbehavior appears in the Set Wallpaper feature of Settings. Figure 7-5 shows the post-selection editor.

209Recipe: Selecting and Customizing Images from the Camera Roll

Figure 7-5 Use the interactive image editor to move, scale,and choose your final presentation.

Page 233: I Phone Developer Cookbook

This window prompts users to move and scale the image as desired.When the usertaps Choose, control moves to the picker delegate. On Cancel, control bounces back tothe album view.

The delegate method imagePickerController: didFinishPickingImage:editingInfo: returns an edited version of the image as its second argument.Thisimage reflects the scaling and translation specified by the user.The UIImage returned issmall, sized to fit the iPhone screen.

The third argument, the editingInfo dictionary, now becomes important. It containsa copy of the original image and a rectangle that represents the image cropping.Theseitems are set as objects of the dictionary and can be recovered for use when needed.

NoteTo populate the camera roll on the iPhone simulator, locate the user file system in~/Library/Application Support/iPhone Simulator/User. Copy a 100APPLE folder from a realiPhone to the Media/DCIM folder. Make sure to copy both the JPG images and the smallTHM thumbnail files.

Recipe 7-5 Adding User Editing to Image Selection

@interface HelloController : UIImagePickerController➥<UIImagePickerControllerDelegate>

@end

@implementation HelloController

#define SOURCETYPE UIImagePickerControllerSourceTypeSavedPhotosAlbum

// #define SOURCETYPE UIImagePickerControllerSourceTypePhotoLibrary

#define DOCSFOLDER [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]

- (id) init

{

if (!(self = [super init])) return self;

// Set up to use the photo roll aka "Saved Photos"

if ([UIImagePickerController isSourceTypeAvailable:SOURCETYPE])➥ self.sourceType = SOURCETYPE;

self.allowsImageEditing = YES;

self.delegate = self;

return self;

}

- (void)imagePickerController:(UIImagePickerController *)picker➥didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo

{

210 Chapter 7 Media

Page 234: I Phone Developer Cookbook

Recipe 7-5 Continued

// Find a unique path name so files are not overwritten

int i = 0;

NSString *uniquePath = [DOCSFOLDER➥stringByAppendingPathComponent:@"selectedImage.png"];

while ([[NSFileManager defaultManager] fileExistsAtPath:uniquePath])

uniquePath = [NSString stringWithFormat:@"%@/%@-%d.%@", DOCSFOLDER,➥@"selectedImage", ++i, @"png"];

printf("Writing selected image to Documents folder\n");

[UIImagePNGRepresentation(image) writeToFile: uniquePath atomically:YES];

// Show the editing changes

printf("Dictionary: %s\n", [[editingInfo description] UTF8String]);

// Pop after saving the edited file

[[self parentViewController] dismissModalViewControllerAnimated:YES];

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker

{

[[self parentViewController] dismissModalViewControllerAnimated:YES];

}

-(void) dealloc

{

[super dealloc];

}

@end

@interface PrimaryController : UIViewController

@end

@implementation PrimaryController

- (void) snap

{

[self presentModalViewController:[[HelloController alloc] init] animated:YES];

}

- (void) loadView

{

[super loadView];

UIView *contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

contentView.backgroundColor = [UIColor whiteColor];

self.view = contentView;

[contentView release];

211Recipe: Selecting and Customizing Images from the Camera Roll

Page 235: I Phone Developer Cookbook

Recipe 7-5 Continued

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Select"

style:UIBarButtonItemStylePlain

target:self

action:@selector(snap)] autorelease];

}

@end

Recipe: Snapping Pictures with the iPhone CameraRecipes 7-4 and 7-5 introduced two of the three UIImagePickerController modes:picking from the entire photo collection and picking just from the camera roll.Recipe 7-6 presents the third mode, snapping photos with the iPhone’s built-in camera.Figure 7-6 shows this interface in action.As you might suppose, this feature does notwork in the Simulator.You must run it from the iPhone for it to work.

As with the other modes, you can allow or disallow image editing as part of thephoto-capture process. One feature the camera interaction brings that has no parallel isthe Preview screen, which displays after the user taps the green camera “snap” button.The preview screen enables you to retake the photo or use the photo as is. On tappingUse Photo, control passes to the next phase. If you’ve enabled image editing, that takesplace next. If not, control moves to the standard “did finish picking” method.

As with edits, use a modal presentation for shooting photos. If you don’t, you’ll haveto hack your way into returning control back to your main program. Using the camerainterface as a modal view controller makes it simple to both present and respond to thecamera.

NotePress and hold the Sleep/Wake button and tap the Home key to take screenshots, like theone shown in Figure 7-6. The screen flashes white to let you know that the shot has beenadded to your DCIM folder. Alternatively, use the iPhone Organizer in Xcode (Control-Command-O) to snap live pictures of your screen.

212 Chapter 7 Media

Page 236: I Phone Developer Cookbook

Recipe 7-6 Accessing the iPhone Camera

@implementation CameraController

#define SOURCETYPE UIImagePickerControllerSourceTypeCamera

#define DOCSFOLDER [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"]

- (id) init

{

if (!(self = [super init])) return self;

// Set up as the camera source

if ([UIImagePickerController isSourceTypeAvailable:SOURCETYPE])➥self.sourceType = SOURCETYPE;

// Just snap here. Although you can easily add image editing if you want

self.allowsImageEditing = NO;

self.delegate = self;

return self;

}

- (void)imagePickerController:(UIImagePickerController *)picker➥didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo

{

// Find a unique path name so files are not overwritten

int i = 0;

NSString *uniquePath = [DOCSFOLDER➥stringByAppendingPathComponent:@"selectedImage.png"];

while ([[NSFileManager defaultManager] fileExistsAtPath:uniquePath])

uniquePath = [NSString stringWithFormat:@"%@/%@-%d.%@", DOCSFOLDER,➥@"selectedImage", ++i, @"png"];

printf("Writing selected image to Documents folder\n");

[UIImagePNGRepresentation(image) writeToFile: uniquePath atomically:YES];

// Thread of control ends here

[[self parentViewController] dismissModalViewControllerAnimated:YES];

[picker release];

}

- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker

{

[[self parentViewController] dismissModalViewControllerAnimated:YES];

[picker release];

}

@end

213Recipe: Snapping Pictures with the iPhone Camera

Page 237: I Phone Developer Cookbook

Working with iPhone AudioAs you’d expect, the iPhone with its iPod-based roots is a powerful audio-capabledevice. Using iPhone audio in your programs is neither simple nor straightforward.Although Apple once had a public QuickTime-like class called Celestial, in Firmware2.0 it moved this class into its PrivateFrameworks folder.That cut off developers frommany beautiful, elegant, and easy-to-use classes and routines that made iPhone audioplayback and recording a thing of beauty. Instead, with the iPhone SDK, developers areofficially limited to two approaches, System Audio and Audio Queues:

n System Audio services enables you to play back short sounds, providing bothalerts and vibration. Recipe 4-11 introduced these calls and showed you how touse them in your programs.

n Audio Queue services offers a C-based interface to the iPhone’s Audio Toolboxframework.The iPhone SDK officially provides no simple and beautifulObjective-C classes for general audio playback.The programming samplesprovided by Apple are complicated and a bit buggy.

Fortunately, two elegant and general playback solutions are available if you’re willingto dig enough.These solutions hide within the Media Player framework. Recipes 7-7and 7-8 introduce each approach.The first gains behind-the-scenes access to the

214 Chapter 7 Media

Figure 7-6 The iPhone camera interface enables you to snap photos,resize and scale them, and reshoot if necessary.

Page 238: I Phone Developer Cookbook

Celestial framework for audio playback.The second uses the MPMoviePlayer class toplay back audio and movies.

NoteThe iPhone sandbox provides no access to your onboard iTunes Music Library. You cannotview or play back any file inside the Library or even get a listing or request playback froman “authorized” program. You can play back only those files you have access to, typicallylimited to those inside the application bundle or the Documents folder.

Recipe: Playing Audio with CelestialOfficially, the Celestial media framework is off limits. Unofficially, the Media Playerframework offers several built-in Celestial features for audio playback.That’s because theiPhone’s media player is based on Celestial and exposes several of its classes.You canaccess these classes through undocumented API calls.The classes that enable you to playaudio of nearly any type are as follows:

n MPItem. In Celestial, AVItem objects feed data from movies and audio files on theiPhone. MPItem offers a media player version of the standard Celestial AVItem.Initialize MPItem instances with a path that leads to the media in question.

n MPAVController. This class provides a centralized audio-visual controller to handle item playback and queue management. One shared instance ofMPAVController remains available for you to use at any time.

n MPArrayQueueFeeder. This audio queue stores a list of pending media that’s duefor playback, like in a playlist.You initialize it with an array of paths that point toeach item that you want played back.

n AVController. This class is the standard Celestial AV controller, which formspart of the MPAVController object.You can access this Celestial-based controllerthrough the shared Media Player controller object and ask it to play back MPItemdata.

Each of these classes is included in the Media Player framework and linkable.Yousupply the header to use them in your applications. Listing 7-2 provides that bare-bonesheader file (Playback.h) that exposes these classes and their most basic interface.

NoteSee Chapter 1, “Introducing the iPhone SDK,” for further discussion about using undocu-mented calls and features in your programs.

215Recipe: Playing Audio with Celestial

Page 239: I Phone Developer Cookbook

Listing 7-2 Playback.h

#import <MediaPlayer/MediaPlayer.h>

// Celestial’s AV Item

@interface AVItem : NSObject

@end

// Media Player’s version of AVItem

@interface MPItem : AVItem

- (id)initWithPath:(NSString *)path error:(id *)anError;

@end

// Media Player’s primary playback controller

@interface MPAVController : NSObject

+ (id) sharedInstance;

- (id) avController;

@end

// Celestial’s AVController

@interface AVController : NSObject

- (void) play: (id *) whatever;

- (void) pause;

- (void) setQueueFeeder:(id)fp8;

@end

// Celestial’s AV Queue Feeder

@interface AVQueueFeeder : NSObject

@end

// Media Player’s version of Celestial’s feeder

@interface MPQueueFeeder : AVQueueFeeder

@end

// The Media Player array-based feeder

@interface MPArrayQueueFeeder : MPQueueFeeder

- (id) initWithPaths:(id)fp8;

@end

Recipe 7-7 presents an extremely no-frills player that loads a copy of Martin LutherKing’s “I have a dream” audio from the application bundle and plays it back.Thismethod allocates a new MPItem and initializes it with the path to the audio file.Theshared audio controller loads that MPItem and starts playback. In about five calls, you getfunctionality equaling page after page of AudioQueue programming.

The advantages to using Celestial are many.You never have to deal with complexAudioQueue calls. Celestial’s AVController class handles nearly any audio format youcan throw at it.You get a lot of performance for little programming.

216 Chapter 7 Media

Page 240: I Phone Developer Cookbook

There are drawbacks, too. Celestial playback interferes with any ongoing iPod play-back. Start playing your audio and you cut into whatever song was playing. In contrast,System Audio services playback does not interrupt. It overlays any iPod audio withwhatever short alert you need to play.

To stop playback, call [avc pause]. Unfortunately, you cannot “unpause” to continueplayback after that.

AVController broadcasts several notifications you’ll want to keep track of.The twomost important are AVController_ItemReadyToPlay and AVController_

ItemPlaybackDidEnd.The former tells your program when the queue has finishedloading the data, which may take a second or two to do.The latter lets you know whenplayback has naturally ended, as opposed to when a user taps a Pause or Stop button.AVController also sends notifications about the queue selection changing and more.You can subscribe to all notifications (see Chapter 1 for details) to listen in toAVController’s state broadcasts.

Recipe 7-7 Playing Back Audio with Celestial’s AVController

- (void) startPlayback : (id) sender

{

// Recover the AV Controller

avc = [[MPAVController sharedInstance] avController];

// Set up a queue to feed the controller

id feeder = [[MPArrayQueueFeeder alloc] initWithPaths:[NSArray➥arrayWithObject:path]];

[avc setQueueFeeder:feeder];

[feeder release];

// Start playback

[avc play:nil];

}

Recipe: Using the Media Player for Audio andVideo PlaybackAs Figure 7-7 shows, the full-screen media player interface offers scrubbing, play/pausecontrol, and more.These features are available for both audio and video playback.Thedifference lies in whether you supply a file URL that points to an audio file or a videofile. Swap out a video URL for the audio one used in this recipe, and you end up with afull-screen movie.

Like other examples in this chapter, the MPMoviePlayerController plays by its ownrules.You do not push it onto a navigation stack.You do not invoke it modally. Instead,you create it and tell it to play. It takes control of the screen, offering the playback con-trols shown in Figure 7-7.To regain control, subscribe your application to the

217Recipe: Using the Media Player for Audio and Video Playback

Page 241: I Phone Developer Cookbook

MPMoviePlayerPlaybackDidFinishNotificationnotification.This notification issent under two circumstances: when playback naturally ends, or when the user taps Done.

Recipe 7-8 is made up of two methods. One starts playback, the other cleans up whenplayback ends.This exact code can be used to play back audio or to play back video.Allyou need to provide is a movie file rather than an audio one. Supported file types includeMOV, MP4, MPV, M4V, and 3GP, as well as MP3,AIFF, M4A, and so forth.

218 Chapter 7 Media

Figure 7-7 The full-screen media player interface offers extensive user control over audio playback.

Recipe 7-8 Using MPMoviePlayerController to Play Back Audio or Video

-(void)myMovieFinishedCallback:(NSNotification*)aNotification

{

// Remove and release the observer

MPMoviePlayerController* theMovie=[aNotification object];

[[NSNotificationCenter defaultCenter] removeObserver:self➥name:MPMoviePlayerPlaybackDidFinishNotification object:theMovie];

[theMovie release];

// restore the status bar to portrait orientation

[[UIApplication sharedApplication]➥setStatusBarOrientation:UIInterfaceOrientationPortrait animated:YES];

}

- (void) startPlayback : (id) sender

{

// create a new player and initialize it with the path

NSString *path = [[NSBundle mainBundle] pathForResource:@"dream"➥ofType:@"m4a"];

MPMoviePlayerController* theMovie=[[MPMoviePlayerController alloc]➥initWithContentURL:[NSURL fileURLWithPath:path]];

theMovie.scalingMode=MPMovieScalingModeAspectFill;

Page 242: I Phone Developer Cookbook

Recipe 7-8 Continued

// Add an observer to catch when playback ends

[[NSNotificationCenter defaultCenter] addObserver:self➥selector:@selector(myMovieFinishedCallback:)➥name:MPMoviePlayerPlaybackDidFinishNotification object:theMovie];

// Start playback

[theMovie play];

}

Recipe: Recording AudioUnfortunately, Celestial’s insanely easy-to-use audio recorder object did not seem tomake the transition from 1.1.4 to 2.0.That leaves developers with Audio Queue record-ing. Recipe 7-9 offers a bare-bones recording wrapped in an Objective-C class. Notehow the class delays ending a recording for a second to fully clear out pending buffers.This sample is based entirely on Apple sample code and is indebted to the trailsinthe-sand.com Web site’s excellent tutorials.

Recipe 7-9 Basic Audio Queue Recording

#import <UIKit/UIKit.h>

#import <AudioToolbox/AudioQueue.h>

#import <AudioToolbox/AudioFile.h>

#define NUM_BUFFERS 3

#define kAudioConverterPropertyMaximumOutputPacketSize 'xops'

#define DOCUMENTS_FOLDER [NSHomeDirectory()➥stringByAppendingPathComponent:@"Documents"]

// Custom recording state storage

typedef struct

{

AudioFileID audioFile;

AudioStreamBasicDescription dataFormat;

AudioQueueRef queue;

AudioQueueBufferRef buffers[NUM_BUFFERS];

UInt32 bufferByteSize;

SInt64 currentPacket;

BOOL recording;

} RecordState;

// Objective-C wrapper for the Audio Queue functionality

@interface Recorder : NSObject {

RecordState recordState;

CFURLRef fileURL;

219Recipe: Recording Audio

Page 243: I Phone Developer Cookbook

Recipe 7-9 Continued

}

- (BOOL) isRecording;

// Automatically generate a dated file in Documents

- (void) toggleRecording;

// Manual recording

- (void) startRecording: (NSString *) filePath;

- (void) stopRecording;

@end

// Derive the Buffer Size. I punt with the max buffer size.

void DeriveBufferSize (

AudioQueueRef audioQueue,

AudioStreamBasicDescription ASBDescription,

Float64 seconds,

UInt32 *outBufferSize

) {

static const int maxBufferSize = 0x50000;

int maxPacketSize = ASBDescription.mBytesPerPacket;

if (maxPacketSize == 0) {

UInt32 maxVBRPacketSize = sizeof(maxPacketSize);

AudioQueueGetProperty (

audioQueue,

kAudioConverterPropertyMaximumOutputPacketSize,

&maxPacketSize,

&maxVBRPacketSize

);

}

Float64 numBytesForTime =

ASBDescription.mSampleRate * maxPacketSize * seconds;

*outBufferSize = (UInt32) ((numBytesForTime < maxBufferSize) ?numBytesForTime : maxBufferSize);

}

// Forward Declaration to Input Buffer Handler

static void HandleInputBuffer (

void *aqData,

AudioQueueRef inAQ,

AudioQueueBufferRef inBuffer,

220 Chapter 7 Media

Page 244: I Phone Developer Cookbook

Recipe 7-9 Continued

const AudioTimeStamp *inStartTime,

UInt32 inNumPackets,

const AudioStreamPacketDescription *inPacketDesc

);

// Assign the file metadata

OSStatus SetMagicCookieForFile (

AudioQueueRef inQueue,

AudioFileID inFile

) {

OSStatus result = noErr;

UInt32 cookieSize;

if (

AudioQueueGetPropertySize (

inQueue,

kAudioQueueProperty_MagicCookie,

&cookieSize

) == noErr

) {

char* magicCookie =

(char *) malloc (cookieSize);

if (

AudioQueueGetProperty (

inQueue,

kAudioQueueProperty_MagicCookie,

magicCookie,

&cookieSize

) == noErr

)

result = AudioFileSetProperty (

inFile,

kAudioFilePropertyMagicCookieData,

cookieSize,

magicCookie

);

free (magicCookie);

}

return result;

}

// Write out current packets

static void HandleInputBuffer (

void *aqData,

AudioQueueRef inAQ,

221Recipe: Recording Audio

Page 245: I Phone Developer Cookbook

Recipe 7-9 Continued

AudioQueueBufferRef inBuffer,

const AudioTimeStamp *inStartTime,

UInt32 inNumPackets,

const AudioStreamPacketDescription *inPacketDesc

) {

RecordState *pAqData = (RecordState *) aqData;

if (inNumPackets == 0 &&

pAqData->dataFormat.mBytesPerPacket != 0)

inNumPackets =

inBuffer->mAudioDataByteSize / pAqData->dataFormat.mBytesPerPacket;

if (AudioFileWritePackets (

pAqData->audioFile,

NO,

inBuffer->mAudioDataByteSize,

inPacketDesc,

pAqData->currentPacket,

&inNumPackets,

inBuffer->mAudioData

) == noErr) {

pAqData->currentPacket += inNumPackets;

if (pAqData->recording == 0)

return;

AudioQueueEnqueueBuffer (

pAqData->queue,

inBuffer,

0,

NULL

);

}

}

// Write the buffers out

void AudioInputCallback(

void *inUserData,

AudioQueueRef inAQ,

AudioQueueBufferRef inBuffer,

const AudioTimeStamp *inStartTime,

UInt32 inNumberPacketDescriptions,

const AudioStreamPacketDescription *inPacketDescs)

{

RecordState* recordState = (RecordState*)inUserData;

222 Chapter 7 Media

Page 246: I Phone Developer Cookbook

Recipe 7-9 Continued

if(!recordState->recording) { printf("Not recording, returning\n"); return;}

printf("Writing buffer %d\n", recordState->currentPacket);

OSStatus status = AudioFileWritePackets(recordState->audioFile,

NO,

inBuffer->mAudioDataByteSize,

inPacketDescs,

recordState->currentPacket,

&inNumberPacketDescriptions,

inBuffer->mAudioData);

if(status == 0) // success

{

recordState->currentPacket += inNumberPacketDescriptions;

}

AudioQueueEnqueueBuffer(recordState->queue, inBuffer, 0, NULL);

}

// For use with the file naming, which is automatic

@interface NSDate (extended)

-(NSDate *) dateWithCalendarFormat:(NSString *)format timeZone: (NSTimeZone *)➥timeZone;

@end

@implementation Recorder

// Initialize the recorder

- (id) init

{

self = [super init];

recordState.recording = NO;

return self;

}

// Set up the recording format as low-quality mono AIFF

- (void)setupAudioFormat:(AudioStreamBasicDescription*)format

{

format->mSampleRate = 8000.0;

format->mFormatID = kAudioFormatLinearPCM;

format->mFormatFlags = kLinearPCMFormatFlagIsBigEndian |➥kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked;

format->mChannelsPerFrame = 1; // mono

format->mBitsPerChannel = 16;

format->mFramesPerPacket = 1;

format->mBytesPerPacket = 2;

223Recipe: Recording Audio

Page 247: I Phone Developer Cookbook

Recipe 7-9 Continued

format->mBytesPerFrame = 2; // not used, apparently required

format->mReserved = 0;

}

// Begin recording

- (void) startRecording: (NSString *) filePath

{

[self setupAudioFormat:&recordState.dataFormat];

fileURL = CFURLCreateFromFileSystemRepresentation (NULL, (const UInt8 *)➥[filePath UTF8String], [filePath length], NO);

recordState.currentPacket = 0;

OSStatus status;

status = AudioQueueNewInput(&recordState.dataFormat,

HandleInputBuffer,

&recordState,

CFRunLoopGetCurrent(),

kCFRunLoopCommonModes,

0,

&recordState.queue);

if (status != 0) {

printf("Could not establish new queue\n");

return;

}

status = AudioFileCreateWithURL(fileURL,

kAudioFileAIFFType,

&recordState.dataFormat,

kAudioFileFlags_EraseFile,

&recordState.audioFile);

if (status != 0)

{

printf("Could not create file to record audio\n");

return;

}

DeriveBufferSize (

recordState.queue,

recordState.dataFormat,

0.5,

&recordState.bufferByteSize

);

// printf("0Buffer size: %d\n", recordState.bufferByteSize);

224 Chapter 7 Media

Page 248: I Phone Developer Cookbook

Recipe 7-9 Continued

for(int i = 0; i < NUM_BUFFERS; i++)

{

status = AudioQueueAllocateBuffer(recordState.queue,

recordState.bufferByteSize,

&recordState.buffers[i]);

if (status) {

printf("Error allocating buffer %d\n", i);

return;

}

status = AudioQueueEnqueueBuffer(recordState.queue,➥recordState.buffers[i], 0, NULL);

if (status) {

printf("Error enqueuing buffer %d\n", i);

return;

}

}

status = SetMagicCookieForFile(recordState.queue, recordState.audioFile);

if (status != 0)

{

printf("Magic cookie failed\n");

return;

}

status = AudioQueueStart(recordState.queue, NULL);

if (status != 0)

{

printf("Could not start Audio Queue\n");

return;

}

recordState.currentPacket = 0;

recordState.recording = YES;

return;

}

// There’s generally about a one-second delay before the buffers fully empty

- (void) reallyStopRecording

{

AudioQueueFlush(recordState.queue);

AudioQueueStop(recordState.queue, NO);

recordState.recording = NO;

SetMagicCookieForFile(recordState.queue, recordState.audioFile);

for(int i = 0; i < NUM_BUFFERS; i++)

{

225Recipe: Recording Audio

Page 249: I Phone Developer Cookbook

Recipe 7-9 Continued

AudioQueueFreeBuffer(recordState.queue,

recordState.buffers[i]);

}

AudioQueueDispose(recordState.queue, YES);

AudioFileClose(recordState.audioFile);

}

// Stop the recording after waiting just a second

- (void) stopRecording

{

[self performSelector:@selector(reallyStopRecording) withObject:NULL➥afterDelay:1.0f];

}

// Automatically create a file in Documents and start/stop recording to it

- (void) toggleRecording

{

// Create a new dated file

NSDate *now = [NSDate dateWithTimeIntervalSinceNow:0];

NSString *caldate = [[now dateWithCalendarFormat:@"%b_%d_%H_%M_%S"➥timeZone:nil] description]; // no :’s.

NSString *filePath = [NSString stringWithFormat:@"%@/%@.aiff",➥DOCUMENTS_FOLDER, caldate];

if(!recordState.recording)

{

printf("Starting recording\n");

[self startRecording: filePath];

}

else

{

printf("Stopping recording\n");

[self stopRecording];

}

}

- (BOOL) isRecording

{

return recordState.recording;

}

@end

226 Chapter 7 Media

Page 250: I Phone Developer Cookbook

Reading in Text DataOf all iPhone media, text data proves (unsurprisingly) the simplest to read in and writeout.The NSString class easily loads text data from an iPhone file:

NSString *path = [[NSBundle defaultBundle] pathForResource:@"myText"➥ofType:@"txt"];NSString *fileText = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

After loading a string from the contents of a file, you might add it into a text view(textView.text = fileText), split it into strings for, for example, a table ([fileTextcomponentsSeparatedByString:@"\n"]), or maybe use it as the body for a text alert(see Chapter 4).

To output text, ask the string to write itself out to a file:

NSError *error;[myString writeToFile:path atomically:YES encoding:NSUTF8StringEncoding➥error:&error];

Displaying Property ListsProperty lists, those files used by Apple to store defaults and other kinds of system data,aren’t exactly “media.” On the other hand, they are something you may want to readfrom disk and display onscreen in your program. In that rather generous spirit, here’show you can read in a property list (in this case, the SpringBoard preferences, which isreadable despite sandbox restrictions) and produce a string description suitable for displayin your favorite UITextView or UILabel.This method works for files with thestrings extension as well as the plist:

#define PLISTPATH @"/private/var/mobile/Library/Preferences/➥com.apple.springboard.plist"

NSDictionary *plist = [NSMutableDictionarydictionaryWithContentsOfFile:PLISTPATH];

if (plist) textView.text = [plist description];else textView.text = @"Could not read property list from file";

Most complex structures, including sets, arrays, dictionaries, and so forth, return anNSString description on demand. It’s a great way to convert a property list into aviewable format.To go the other way, converting an NSString XML property list repre-sentation to a property list, use the propertyList method (e.g., id plist =[myXMLString propertyList]).

NoteProperty lists and dictionaries can also be serialized into an XML string. Chapter 10,“Connecting to Services,” shows how to do this and how to recover data from its serializedformat.

227Reading in Text Data

Page 251: I Phone Developer Cookbook

Recovering Media from Backup FilesWhen iTunes backs up an iPhone, it stores data from each application’s Documents andLibrary folders.These files are encoded and checksummed, and added to a central back-up manifest. On the Macintosh, a backup folder for each synced iPhone appears in~/Library/Application Support/MobileSync/Backup; on Windows, in C:\Documentsand Settings\UserName\Application Data\Apple Computer\SyncServices\Local. Insideeach folder appears one Info.plist, one Manifest.plist, and many mdbackup files:

n The Info.plist file describes the device, its name, its serial number, and its iTunespreferences, along with other device-specific information.

n The Manifest.plist file stores a table of contents of all the backed-up files, alongwith their modification time and data hash.This does two things. It ensures thatthe data remains consistent with the time it was backed up, and it prevents third-party developers from loading custom data back to the iPhone on restore.The for-mer is a good thing, the latter not so much of one.

n Mdbackup files contain serialized versions of each file backed up to your computer.My Macintosh-based mdhelper command-line utility (http://ericasadun.com/ftp/Macintosh) scans through these mobile device backup files and recovers thebacked-up data from them.

Serialized data appears in an encoded block. It’s simple to access this block and storeit to disk in its original form. It takes just three steps:

1. Read in the mdbackup file to a dictionary:NSDictionary *mddict = [NSDictionarydictionaryWithContentsOfFile:backupPath];

2. Extract the encoded data block:NSData *data = [mddict objectForKey:@"Data"];

3. Store the data out to disk:[data writeToFile:testPath atomically:YES];

These three steps enable you to convert those otherwise impenetrable data blocksinto the original files, whether for the manifest or for individually backed-up data.Recipe 7-10 shows the mdhelper routine that extracts manifests from the backup folders.

Recipe 7-10 Recovering Manifest Data from iTunes Backups

// Recover all manifests from available foldersvoid getManifests()

{

for (NSString *path in backupFolders())

{

NSString *fullPath = [backupDirPath()➥stringByAppendingPathComponent:path];

// Get the path to the Manifest.plist file

228 Chapter 7 Media

Page 252: I Phone Developer Cookbook

Recipe 7-10 Continued

NSString *manifestPath = [fullPath➥stringByAppendingPathComponent:@"Manifest.plist"];

// Get the path to the Info.plist file

NSString *infoPath = [fullPath➥stringByAppendingPathComponent:@"Info.plist"];

// Read in the manifest and info dictionaries

NSDictionary *manifestDict = [NSDictionary➥dictionaryWithContentsOfFile:manifestPath];

NSDictionary *infoDict = [NSDictionary➥dictionaryWithContentsOfFile:infoPath];

// Read in the device name from Info.plist

NSString *deviceName = [infoDict objectForKey:@"Device Name"];

// Read in the manifests’s data glob

NSData *data = [manifestDict objectForKey:@"Data"];

// Output the manifest’s data glob to disk

NSString *outfile = [[recoveryFolderPath()➥stringByAppendingPathComponent:deviceName] stringByAppendingString:@"-➥Manifest.plist"];

if (data) printf("Extracting manifest for device %s\n", [deviceName➥UTF8String]);

[data writeToFile:outfile atomically:YES];

[data release];

[outfile release];

[deviceName release];

[infoPath release];

[manifestPath release];

[fullPath release];

}

}

SummaryThis chapter introduced many ways to handle media, including loading, storing, select-ing, and extracting files.You’ve seen recipes that worked with audio, video, image, andtext data. Before moving on from this chapter, here are some thoughts about the recipesyou’ve seen here:

n Whether working with images or audio, choose a presentation method that bestmatches the characteristics of the file you’re working with.With the iPhone, there’srarely a one-size-fits-all solution for media.

229Summary

Page 253: I Phone Developer Cookbook

n Although Apple has not made the Celestial AVController class public, it’s avail-able in public frameworks.You bring the headers. Let Apple bring the functionalityyou need.

n Audio Queue provides powerful low-level audio routines but they’re not for thefaint of heart or for anyone who just wants a quick solution. If you need the kindof fine-grained audio control that Audio Queues bring,Apple supplies extensivedocumentation on achieving your goals.

n Many classes deliver more than advertised: Use UIWebView objects to displayHTML, PDF files, and images. Use MPMoviePlayerController to play backboth movies and audio files.

230 Chapter 7 Media

Page 254: I Phone Developer Cookbook

8Controls

The UIControl class provides the basis for many iPhone interactive elements, includingbuttons, text fields, sliders, and switches.These controls have more in common than theirancestor class. Controls all use similar layout and target-action approaches.This chapterintroduces controls and their use.You discover how to build and customize controls in avariety of ways.You read about well-documented SDK calls and about less-documentedones.You see how disassembling the UIControl view hierarchy enables you to accessand modify control elements. From the prosaic to the obscure, this chapter introduces awide range of control recipes you can reuse in your programs.

Recipe: Building Simple ButtonsThe iPhone supports several kinds of buttons.The two most commonly used buttons arethe freestanding UIButton instances built from scratch and the UIBarButtonItem onesthat live exclusively on bars, including navigation bars, tab bars, and toolbars.

UIBarButtonItems provide an extremely simple way to add push-button functionalityto your applications with a minimum of programming. Listing 8-1 shows a typical callthat you’d find in a UIViewController loadView method.You create the button witha title, a style, a target, and an action.The parent view, whether a navigation bar, tab bar,or toolbar, handles placement and sizing issues for you.

Listing 8-1 Adding a UIBarButtonItem to a Navigation Bar

// Add a randomize button

UIBarButtonItem *randomButton = [[[UIBarButtonItem alloc]

initWithTitle:@"Randomize"

style:UIBarButtonItemStylePlain

target:self

action:@selector(randomize)] autorelease];

self.navigationItem.rightBarButtonItem = randomButton;

Page 255: I Phone Developer Cookbook

UIBarButtonItems use target-action. Like all controls, they enable you to add at leastone target and callback action for when users interact with them. Bar button items donot specify the kind of event involved; the only event of interest is a button tap.

The drawback to working with bar button items is that they cannot be placed arbi-trarily onscreen.They are not views. So, you cannot set their frame and add them toother views.The bars that own them both build and manage their presentation.To placebuttons into arbitrary views, you need to work with the UIButton class instead.

NoteOnce built by its parent, you can access a bar button item’s actual view. Call the undocu-mented UIButton view method. You cannot remove that view from its parent and treat itas an independent object without encountering serious complications. Instead, use theview to add subviews like the (undocumented) UIToolbarButtonBadge class, as shownlater in this chapter.

The UIButton classUIButton instances add interactive buttons to your iPhone applications.They offer moretraditional controls, complete with frames and event specification for target-action calls.When tapped, they send messages to a target you specify.The iPhone offers two ways tobuild UIButtons.You can use a precooked button type or build a custom button fromscratch.

The current iPhone SDK offers the following precooked button types.As you cansee, the button types are not very general.They’ve been added to the SDK primarily forApple’s convenience, not yours. Nonetheless, you can use these in your programs asneeded:

n Detail Disclosure. This is the same round, blue circle with the chevron you seewhen you add a detail disclosure accessory to table cells.

n Info Light and Info Dark. These two buttons offer a small circled i like you seeon a Macintosh’s Dashboard widget.These are used in the Weather and Stocksapplication to flip the view from one side to the other.

n Contact Add. This round, blue circle has a white + in its center and can be seenin the Contacts application for adding new people to your Address Book.

n Rounded Rectangle. This button provides a simple onscreen rounded rectanglethat surrounds the button text. It’s not an especially attractive button (that is, it’s notvery “Apple” looking), but it is simple to program and use in your applications.

To use a precooked button, allocate it, set its frame, and add a target. Don’t worryabout adding custom art or creating the overall look of the button.The SDK takes careof all that. Listing 8-2 illustrates how to build a simple, rounded, rectangle button.Tobuild one of the other standard button types, omit the title line. Rounded rectangles arethe only precooked button type that use a title.

232 Chapter 8 Controls

Page 256: I Phone Developer Cookbook

NoteUse the UIControlEventTouchUpInside event to catch user taps on buttons.

Listing 8-2 Building a “Precooked” Rounded Rectangle Button

UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];

[button setFrame:CGRectMake(0.0f, 0.0f, 80.0f, 30.0f)];

[button setCenter:CGPointMake(160.0f, 208.0f)];

[button setTitle:@"Beep" forState:UIControlStateNormal];

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

[contentView addSubview:button];

Building Custom ButtonsWhen using the UIButtonTypeCustom style, you supply all button art.The number ofimages depends on how you want the button to work. For a simple push button, youmight add a single background image and vary the label color to highlight when thebutton is pushed. For a toggle-style button, you might use four images: for the “off ” statein a normal presentation, the “off ” state when highlighted (that is, pressed), and twomore for the “on” state.You choose and design the interaction details.

233Recipe: Building Simple Buttons

Figure 8-1 Use UIImage stretching to resize art for arbitrary buttonwidths. Set the left cap width to specify where the stretching can take place.

Page 257: I Phone Developer Cookbook

Recipe 8-1 builds a button that toggles on and off, demonstrating the detail that goesinto building custom buttons.When tapped, the button switches its art from green tored, or red to green.This allows your (noncolorblind) users to instantly identify a currentstate. Figure 8-1 (left) shows the button created by this recipe.

The UIImage stretchable image calls in this recipe play an important role in buttoncreation. Stretchable images enable you to create buttons of arbitrary width, turning circu-lar art into lozenge-shaped buttons.You specify the caps at either end (that is, the art thatshould not be stretched). In this case, the cap is 110 pixels wide. If you were to changethe button width from the 220 pixels used in this recipe to 300, the button stretches fromthe middle at the point where the cap ends, as shown in Figure 8-1 (right).

Recipe 8-1 Building a UIButton That Toggles On and Off

@interface HelloController : UIViewController

{

UIImageView *contentView;

BOOL isOn;

}

@end

@implementation HelloController

- (UIButton *) buildButton: (NSString *) aTitle

{

// Create a button sized to our art

UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0.0f, 0.0f,220.0f, 233.0f)];

// The cap width indicates the location where the stretch occurs

[button setBackgroundImage:[[UIImage imageNamed:@"green.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateNormal];

[button setBackgroundImage:[[UIImage imageNamed:@"green2.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateHighlighted];

// Set up the button aligment properties

button.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

button.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;

// Set the title, font and color. The color switches from

// white to gray for highlights

[button setTitle:aTitle forState:UIControlStateNormal];

[button setTitle:aTitle forState:UIControlStateHighlighted];

[button setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];

[button setTitleColor:[UIColor lightGrayColor]➥forState:UIControlStateHighlighted];

[button setFont:[UIFont boldSystemFontOfSize:24.0f]];

isOn = NO;

234 Chapter 8 Controls

Page 258: I Phone Developer Cookbook

Recipe 8-1 Continued

return [button autorelease];

}

- (void) toggleButton: (UIButton *) button

{

// Swap the art when the state changes

if (isOn = !isOn)

{

[button setTitle:@"On" forState:UIControlStateNormal];

[button setTitle:@"On" forState:UIControlStateHighlighted];

[button setBackgroundImage:[[UIImage imageNamed:@"green.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateNormal];

[button setBackgroundImage:[[UIImage imageNamed:@"green2.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateHighlighted];

}

else

{

[button setTitle:@"Off" forState:UIControlStateNormal];

[button setTitle:@"Off" forState:UIControlStateHighlighted];

[button setBackgroundImage:[[UIImage imageNamed:@"red.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateNormal];

[button setBackgroundImage:[[UIImage imageNamed:@"red2.png"]➥stretchableImageWithLeftCapWidth:110.0 topCapHeight:0.0]➥forState:UIControlStateHighlighted];

}

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

[contentView setUserInteractionEnabled:YES];

self.view = contentView;

[contentView release];

// Add the button

UIButton *button = [self buildButton:@"On"];

[button setCenter:CGPointMake(160.0f, 200.0f)];

[contentView addSubview: button];

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

isOn = YES;

}

-(void) dealloc

{

235Recipe: Building Simple Buttons

Page 259: I Phone Developer Cookbook

Recipe 8-1 Continued

[contentView release];

[super dealloc];

}

@end

Glass ButtonsDuring the SDK beta period,Apple dropped official support for its simpleUIGlassButton class.This class offered a much better-looking alternative to the roundedrectangle and supported control tinting, too.At the time of writing, the UIGlassButtonclass still exists in the simulator’s UIKit framework, but Apple does not fully implementit in the iPhone’s UIKit framework.

Recipe: Adding Animated Elements to ButtonsYou cannot add subviews to buttons, but you can certainly creatively layer art in frontof or behind them. Use the standard UIView hierarchy to do this, making sure todisable user interaction for any view that might otherwise obscure your button(setUserInteractionEnabled:NO). Figure 8-2 shows what happens when you combine semitranslucent button art with an animated UIImageView behind it.Theimage view contents “leak” through to the viewer, enabling you to add live animationelements to the button.

Recipe 8-2 Adding Animated Elements Behind a UIButton

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

[contentView setUserInteractionEnabled:YES];

self.view = contentView;

[contentView release];

// load in the animation cells for the butterfly

NSMutableArray *bflies = [[NSMutableArray alloc] init];

for (int i = 1; i <= 17; i++) {

NSString *cname = [NSString stringWithFormat:@"bf_%d.png", i];

UIImage *img = [UIImage imageNamed:cname];

if (img) [bflies addObject:img];

}

// create the image view and add it

236 Chapter 8 Controls

Page 260: I Phone Developer Cookbook

UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f,➥0.0f, 80.0f, 80.0f)];

[imageView setAnimationImages:bflies];

[imageView setAnimationDuration:1.2f];

[imageView startAnimating];

[bflies release];

[contentView addSubview:imageView];

[imageView release];

[imageView setCenter:CGPointMake(160.0f, 200.0f)];

[imageView setUserInteractionEnabled:NO];

// Add the button and set it to play the sound

UIButton *button = [self buildButton:@"PushMe"];

[button setCenter:CGPointMake(160.0f, 200.0f)];

[contentView addSubview: button];

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

}

237Recipe: Adding Animated Elements to Buttons

Figure 8-2 Combine semitranslucent button art with animatedUIImageViews to build eye-catching UI elements. In this recipe, the

butterfly flaps “within” the button.

Page 261: I Phone Developer Cookbook

Recipe: Animating Button ResponsesThere’s more to UIControls than frames and target-action.All controls inherit from theUIView class.This means you can use UIView animation blocks when working withcontrols just as you would with standard views. Recipe 8-3 builds a toggle switch thatflips around using UIViewAnimationTransitionFlipFromLeft to spin the buttonwhile changing states.

Unlike Recipe 8-1, this code doesn’t switch art. Instead, it switches buttons.There aretwo: an on button and an off button, both of which rest on a clear UIView. Giving thetwo buttons a see-through parent enables you to apply the flip to just those buttonswithout involving the rest of the user interface. Skip the clear background, and you endup spinning the entire window—not a good UI choice.

As this recipe uses the same semitranslucent art as the previous recipes, it’s importantthat only one button appears onscreen at any time.To make this happen, the current button removes itself from that clear superview while in the animation block.The buttonwith the opposite state takes its place. Figure 8-3 shows the flipping button in midflip.

238 Chapter 8 Controls

Figure 8-3 Use UIView animation blocks to flip between control states.Here, a button twirls around to move between Off and On.

Recipe 8-3 Adding UIView Animation Blocks to Controls

- (void) toggleButton: (UIButton *) button

{

[UIView beginAnimations:nil context:NULL];

Page 262: I Phone Developer Cookbook

Recipe 8-3 Continued

[UIView setAnimationDuration:1.0f];

[UIView setAnimationTransition:

UIViewAnimationTransitionFlipFromLeft forView:clearView cache:YES];

// swap out the button views during the flip

[button removeFromSuperview];

if (isOn = !isOn)[clearView addSubview:onButton];

else [clearView addSubview:offButton];

[UIView commitAnimations];

}

Recipe: Customizing SwitchesNontrivial UIControl objects are usually built from a series of subviews. By navigating acontrol’s subview tree, you can expose and customize objects that normally would not beaccessible from the standard SDK.Take switches, for example.The SDK offers just onetype of switch: an On/Off button. It takes just a few lines of code to edit that button totransform it to the Yes/No button shown in Figure 8-4 (left).

239Recipe: Customizing Switches

Figure 8-4 (Left) A bit of object dumpster diving allows you to exposeand customize underlying UISwitch views. (Right) Color UIAlertView

buttons by accessing the alert’s subviews.

Page 263: I Phone Developer Cookbook

This kind of customization relies on understanding a control’s view tree. Listing 8-3shows how you might explore that hierarchy. Send a control object to explode: level:

with an initial level of 0.The method recursively descends the tree, presenting each sub-view in turn.

Listing 8-3 Exploring UIControl View Hierarchy

#define outstring(anObject) [[anObject description] UTF8String]

- (void) explode: (id) aView level: (int) aLevel

{

// indent to show the current level

for (int i = 0; i < aLevel; i++) printf("—", aLevel);

// show the class and superclass for the current object

printf("%s:%s\n", outstring([aView class]), outstring([aView superclass]));

// recurse for all subviews

for (UIView *subview in [aView subviews])

[self explode:subview level:(aLevel + 1)];

}

When sent a switch, explode creates the following results in the Xcode console.Asyou can see, a switch has a single subview, a custom UISwitchSlider object.This hasfour subviews.Three images create the backgrounds and the switch art. One view ownsthe two labels used on the switch. Because these labels are standard UILabel instances,you can tell them to use whatever text you supply:

UISwitch:UIControl

--_UISwitchSlider:UISlider

----UIImageView:UIView

----UIImageView:UIView

----UIView:UIResponder

------UILabel:UIView

------UILabel:UIView

----UIImageView:UIView

Knowing how the views are set up enables you to build a UISwitch subclass thataccesses these items. Recipe 8-4 shows the UICustomSwitch class implementation thatadds label text customization.

NoteThe extended UISwitch class definition in Recipe 8-4 also exposes the undocumentedsetAlternateColors: call that enables you to use an orange background rather than ablue one. See Chapter 1, “Introducing the iPhone SDK,” for a discussion about usingundocumented features in your applications.

240 Chapter 8 Controls

Page 264: I Phone Developer Cookbook

Recipe 8-4 Adding Text Customization to iPhone Switches

// Add the undocumented alternate colors

@interface UISwitch (extended)

- (void) setAlternateColors:(BOOL) boolean;

@end

// Expose the _UISwitchSlider class

@interface _UISwitchSlider : UIView

@end

// UICustomSwitch allows you to set the left and right label text

@interface UICustomSwitch : UISwitch

- (void) setLeftLabelText: (NSString *) labelText;

- (void) setRightLabelText: (NSString *) labelText;

@end

@implementation UICustomSwitch

- (_UISwitchSlider *) slider {

return [[self subviews] lastObject];

}

- (UIView *) textHolder {

return [[[self slider] subviews] objectAtIndex:2];

}

- (UILabel *) leftLabel {

return [[[self textHolder] subviews] objectAtIndex:0];

}

- (UILabel *) rightLabel {

return [[[self textHolder] subviews] objectAtIndex:1];

}

- (void) setLeftLabelText: (NSString *) labelText {

[[self leftLabel] setText:labelText];

}

- (void) setRightLabelText: (NSString *) labelText {

[[self rightLabel] setText:labelText];

}

@end

Customizing UIAlertView ButtonsAccessing subviews enables you to customize to any number of classes. Figure 8-4 (right)shows a UIAlertView with a customized button. Colored red, it indicates the same kindof danger that’s found in UIActionSheet destructive buttons.This is a hack.

The coloring is not consistent. Because UIAlertView is an undocumented buttonclass, it does not respond to the standard tintColor calls. Instead, you must set thebackground color for the button.This means the color bleeds past the button edges.Fortunately, the bleeding is slight and only pendants need avoid this method.The lesson

241Recipe: Customizing Switches

Page 265: I Phone Developer Cookbook

here is caution when working with nonstandard SDK classes. Many of them presentusing drawRect: calls and do not lend themselves perfectly to customization.

To work, Listing 8-4 uses the willPresentAlertView: delegate method.Thisapproach enables the alert to finish building itself before customizing the button.Alertviews and action sheets (along with most of the bar classes) don’t completely build theircomponent views until just before they appear onscreen.Waiting lets the subviews estab-lish themselves before you modify them and before they appear onscreen to your user.

Listing 8-4 Coloring UIActionSheet Buttons

- (void)willPresentAlertView:(UIAlertView *)alertView

{

[[[alertView subviews] objectAtIndex:2] setBackgroundColor:[UIColor➥colorWithRed:0.5 green:0.0f blue:0.0f alpha:1.0f]];

}

Recipe: Adding Custom Slider ThumbsKeeping with the modifying control elements theme, be aware that many controls offerpublic customization.You don’t have to hack your way to the prize.The UISlider class,for example, enables you to change the look of its thumb component without searchingthrough a subview hierarchy. Set this thumb to whatever image you like by callingsetThumbImage: forState:.This next recipe changes the focus away from searchingfor the right view to dynamic customization. It builds the thumb art on-the-fly.

242 Chapter 8 Controls

Figure 8-5 Core Graphics calls enable this slider’s thumb image to dimor brighten based on the current slider value.

Page 266: I Phone Developer Cookbook

Recipe 8-5 builds a slider whose thumb image dynamically varies. In this case, thebrightness of the thumb image correlates directly with the current slider value.AcreateImage routine builds the thumbs at runtime, as the user manipulates the slider.Core Graphics calls build the target-like image shown in Figure 8-5.The inner circlebrightness is set to the slider value.

This kind of feedback could be based on any kind of data.You might grab valuesfrom onboard sensors or make calls out to the Internet. No matter what live updatescheme 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 require-ments for the thumb-sized images are minimal. Recipe 8-5 uses Apple sample code tocreate customizable bitmap contexts. It then fills that context with target-style art basedon a floating-point value (percentage) passed to the createImage function.This functionreturns a new thumb image whenever the user interacts with the slider.

Recipe 8-5 Building Dynamic Slider Thumbs

// MyCreateBitmapContext: Source based on Apple Sample Code

CGContextRef MyCreateBitmapContext (int pixelsWide,

int pixelsHigh)

{

CGContextRef context = NULL;

CGColorSpaceRef colorSpace;

void * bitmapData;

int bitmapByteCount;

int bitmapBytesPerRow;

bitmapBytesPerRow = (pixelsWide * 4);

bitmapByteCount = (bitmapBytesPerRow * pixelsHigh);

colorSpace = CGColorSpaceCreateDeviceRGB();

bitmapData = malloc( bitmapByteCount );

if (bitmapData == NULL)

{

fprintf (stderr, "Memory not allocated!");

return NULL;

}

context = CGBitmapContextCreate (bitmapData,

pixelsWide,

pixelsHigh,

8,

bitmapBytesPerRow,

colorSpace,

kCGImageAlphaPremultipliedLast);

if (context== NULL)

{

free (bitmapData);

243Recipe: Adding Custom Slider Thumbs

Page 267: I Phone Developer Cookbook

Recipe 8-5 Continued

fprintf (stderr, "Context not created!");

return NULL;

}

CGColorSpaceRelease( colorSpace );

return context;

}

// Build a thumb image based on the grayscale percentage

id createImage(float percentage)

{

CGRect aRect = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f);

CGContextRef context = MyCreateBitmapContext(48, 48);

CGContextClearRect(context, aRect);

// Outer gray circle

CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]);

CGContextFillEllipseInRect(context, aRect);

// Inner circle with feedback levels

CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:percentage➥green:0.0f blue:0.0f alpha:1.0f] CGColor]);

CGContextFillEllipseInRect(context, CGRectInset(aRect, 4.0f, 4.0f));

// Inner gray circle

CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]);

CGContextFillEllipseInRect(context, CGRectInset(aRect, 16.0f, 16.0f));

CGImageRef myRef = CGBitmapContextCreateImage (context);

free(CGBitmapContextGetData(context));

CGContextRelease(context);

return [UIImage imageWithCGImage:myRef];

}

@interface HelloController : UIViewController

{

UIImageView *contentView;

UILabel *valueLabel;

UIImage *knob;

}

@end

@implementation HelloController

- (void) updateValue: (UISlider *) slider

{

244 Chapter 8 Controls

Page 268: I Phone Developer Cookbook

Recipe 8-5 Continued

[valueLabel setText:[NSString stringWithFormat:@"%3.1f", [slider value]]];

CGImageRef oldknobref = [knob CGImage];

UIImage *knob = createImage([slider value] / 100.0f);

[slider setThumbImage:knob forState:UIControlStateNormal];

[slider setThumbImage:knob forState:UIControlStateHighlighted];

CFRelease(oldknobref);

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

[contentView setUserInteractionEnabled:YES];

self.view = contentView;

[contentView release];

// Add a text label to show the current slider scale

valueLabel = [[UILabel alloc] initWithFrame:CGRectMake(50.0f, 100.0f, 220.0f,➥40.0f)];

valueLabel.textAlignment = UITextAlignmentCenter;

valueLabel.text = @"50.0";

valueLabel.backgroundColor = [UIColor clearColor];

[contentView addSubview:valueLabel];

[valueLabel release];

// Build the slider

UISlider *slider = [[UISlider alloc] initWithFrame:CGRectMake(50.0f, 160.0f,➥220.0f, 40.0f)];

slider.backgroundColor = [UIColor clearColor];

slider.minimumValue = 0.0f;

slider.maximumValue = 100.0f;

slider.continuous = YES;

slider.value = 50.0f;

// Add the custom knob

knob = createImage(0.5);

[slider setThumbImage:knob forState:UIControlStateNormal];

[slider setThumbImage:knob forState:UIControlStateHighlighted];

[slider addTarget:self action:@selector(updateValue:)➥forControlEvents:UIControlEventValueChanged];

[contentView addSubview: slider];

[slider release];

}

@end

245Recipe: Adding Custom Slider Thumbs

Page 269: I Phone Developer Cookbook

Adding Text to the SliderUnfortunately, text is computationally expensive.You can add text to the slider, as inListing 8-5, but at a huge cost to normal interaction.This listing demonstrates how toput labels into a core graphics context.You’ll be better off precomputing these thumbimages and loading them from those precomputed images rather than generating themon-the-fly.Although this routine simulates well on a Macintosh, the iPhone’s chip is toounderpowered to handle the extra burden that text processing adds to live image creation.In contrast, precomputed images are cheap, both computationally and in terms of memory storage.

Keep in mind that the Quartz coordinate system starts at the bottom left, not the topleft.Adding text to a point in the image uses the Quartz system.

Listing 8-5 Adding Thumb Text

id createImage(float percentage)

{

CGRect aRect = CGRectMake(0.0f, 0.0f, 48.0f, 48.0f);

CGContextRef context = MyCreateBitmapContext(48, 48);

CGContextClearRect(context, aRect);

CGContextSetFillColorWithColor(context, [[UIColor lightGrayColor] CGColor]);

CGContextFillEllipseInRect(context, aRect);

CGContextSetFillColorWithColor(context, [[UIColor colorWithRed:percentage➥green:0.0f blue:0.0f alpha:1.0f] CGColor]);

CGContextFillEllipseInRect(context, CGRectInset(aRect, 4.0f, 4.0f));

CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);

CGContextSelectFont(context, "Georgia", 14.0f, kCGEncodingMacRoman);

CGContextSetTextDrawingMode(context, kCGTextFill);

CGContextSetShouldAntialias(context, true);

NSString *outString = [NSString stringWithFormat:@"%4.2f", percentage];

CGContextShowTextAtPoint(context, 10, 20, [outString UTF8String], [outString➥length]);

CGImageRef myRef = CGBitmapContextCreateImage (context);

free(CGBitmapContextGetData(context));

CGContextRelease(context);

return [UIImage imageWithCGImage:myRef];

}

Recipe: Dismissing a UITextField KeyboardThe most commonly asked question about the UITextField control is,“How do I dismiss the keyboard?”There’s no built-in way to automatically detect this.When users finish editing the contents of a UITextField, the keyboard should go away.

246 Chapter 8 Controls

Page 270: I Phone Developer Cookbook

Fortunately, it takes little work to respond to the end of edits. By watching for theReturn key, you can resign first-responder status.This moves the keyboard out of sight, asRecipe 8-6 shows. Here are a few key points about doing this:

n Set the Return key type to UIReturnKeyDone. Using a “Done”-style Return keytells the user how to finish editing. Figure 8-6 shows a keyboard using the Donekey style.

n Implement textFieldShouldReturn:.This method catches all return key presses—no matter how they are named. Use the method to resign first responder.This hides the keyboard until the user touches another text field or text view.

n Make sure your view controller implements the UITextFieldDelegate protocoland that you set the field delegate to point to your controller.

Your code needs to handle each of these points to create a smooth interaction processfor your UITextField instances. Figure 8-6 shows UITextField controls in action,with the keyboard in place during a field edit.

247Recipe: Dismissing a UITextField Keyboard

Figure 8-6 Catch Return key presses to know when your userhas finished editing a text field so you can dismiss the keyboard.

Recipe 8-6 Using the Done Key to Dismiss a Text Field Keyboard

// This method catches the Return action

- (BOOL)textFieldShouldReturn:(UITextField *)textField

{

Page 271: I Phone Developer Cookbook

Recipe 8-6 Continued

[textField resignFirstResponder];

return YES;

}

- (void)loadView

{

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

contentView.backgroundColor = [UIColor whiteColor];

self.view = contentView;

[contentView release];

// Create a field with a Done return key

namefield = [[[UITextField alloc] initWithFrame:CGRectMake(120.0f, 40.0f,➥150.0f, 30.0f)] retain];

[namefield setBorderStyle:UITextBorderStyleRoundedRect];

namefield.placeholder = @"name";

namefield.returnKeyType = UIReturnKeyDone;

namefield.clearButtonMode = UITextFieldViewModeWhileEditing;

namefield.delegate = self;

[contentView addSubview:namefield];

[namefield release];

}

Recipe: Dismissing UITextView KeyboardsWhen dismissing keyboards, UITextView instances require a slightly different approachthan UITextField ones. Users should be able to tap Return in the text view, addingcarriage returns without dismissing the keyboard. Instead, add a Done button to the general interface when the text view becomes active, as Figure 8-7 shows. Use this keyto resign first-responder status when the user finishes his or her edits.

To sense text view activity, your view controller must implement theUITextViewDelegate protocol, and it must be set as the text view’s delegate.ThetextViewDidBeginEditing: delegate method triggers whenever a user taps the view.Detecting this enables you to either add or enable the Done button. Users can then tapon Done after they’ve finished editing.The Done button offers an obvious way to finishediting and dismiss the keyboard.

Recipe 8-7 demonstrates how to add the navigation item button in the delegatemethod call and how to remove it when the user is done editing. Reveal the Done button when the view becomes active. Hide it when resigning the view’s first-responder status.

248 Chapter 8 Controls

Page 272: I Phone Developer Cookbook

Recipe 8-7 Adding a Done Button to Active UITextView Sessions

@interface HelloController : UIViewController <UITextViewDelegate>

{

UITextView *contentView;

}

@end

@implementation HelloController

// Reveal a done button when editing starts

- (void) textViewDidBeginEditing: (UITextView *) textView

{

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Done"

style:UIBarButtonItemStyleDone

target:self

action:@selector(doneEditing:)]autorelease];

}

// The done button forces the text view to resign its first-responder status

- (void) doneEditing: (id) sender

249Recipe: Dismissing UITextView Keyboards

Figure 8-7 Add a Done key to the navigation bar when users start interacting with a text view. This offers users an obvious way to finish

editing and dismiss the keyboard.

Page 273: I Phone Developer Cookbook

Recipe 8-7 Continued

{

[contentView resignFirstResponder];

self.navigationItem.rightBarButtonItem = NULL;

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setDelegate:self];

self.view = contentView;

[contentView release];

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

Recipe: Adding an Undo Button to Text ViewsRecipe 8-7 showed how to catch user interactions within a text view.TheUITextViewDelegate protocol can also sense user-generated changes to the displayedtext.The textView: shouldChangeTextInRange: replacementText:method triggerswhenever the user types or deletes text within the view. By returning YES and revealingan undo button, you create the basic structure that supports reverting to the mostrecently saved state.

Recipe 8-8 adds persistent data to the basic structure built in Recipe 8-7.The appli-cation now saves a copy of the text to a file when users tap Done to dismiss the keyboard.During edits, if the user taps Undo, the program replaces the text view contents with thecontents of that file and hides the Undo button again.

These changes ensure that the Undo button appears only when the visual text haschanged from the stored text. Figure 8-8 shows the interface built by this recipe whenthe Undo button has been triggered by user text changes.

250 Chapter 8 Controls

Page 274: I Phone Developer Cookbook

Recipe 8-8 Adding Undo Support to Text Views

@interface HelloController : UIViewController <UITextViewDelegate>

{

UITextView *contentView;

BOOL readyToUndo;

}

@end

@implementation HelloController

#define FILEPATH [NSHomeDirectory()➥stringByAppendingPathComponent:@"Documents/MyText.txt"]

// Reveal a Done button when starting to edit

- (void) textViewDidBeginEditing: (UITextView *) textView

{

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Done"

style:UIBarButtonItemStyleDone

target:self

action:@selector(doneEditing:)]autorelease];

readyToUndo = YES;

251Recipe: Adding an Undo Button to Text Views

Figure 8-8 The Undo button at the top-left corner of the screen appears onlyafter the user has changed text in the text view from the most recently storedstate. Tapping Done saves any changes to the file (and hides Undo). Tapping

Undo reverts the view contents to the most recently stored version.

Page 275: I Phone Developer Cookbook

Recipe 8-8 Continued

}

// Only reveal the undo after text has actually been typed or deleted

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range➥replacementText:(NSString *)text

{

if (readyToUndo)

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Undo"

style:UIBarButtonItemStylePlain

target:self

action:@selector(undoEditing:)]autorelease];

readyToUndo = NO;

return YES;

}

// The Done button forces the text view to resign its first-responder status and➥saves changes

- (void) doneEditing: (id) sender

{

NSError *error;

[contentView resignFirstResponder];

self.navigationItem.rightBarButtonItem = NULL;

self.navigationItem.leftBarButtonItem = NULL;

[[contentView text] writeToFile:FILEPATH atomically: YES➥encoding:NSUTF8StringEncoding error: &error];

}

// Undo means revert to last saved state

- (void) undoEditing: (id) sender

{

if ([[NSFileManager defaultManager] fileExistsAtPath:FILEPATH])

[contentView setText:[NSString stringWithContentsOfFile:FILEPATH]];

else

[contentView setText:@""];

readyToUndo = YES;

self.navigationItem.leftBarButtonItem = NULL;

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setDelegate:self];

self.view = contentView;

252 Chapter 8 Controls

Page 276: I Phone Developer Cookbook

Recipe 8-8 Continued

[contentView release];

// Preload with text if any has been saved

if ([[NSFileManager defaultManager] fileExistsAtPath:FILEPATH])

[contentView setText:[NSString stringWithContentsOfFile:FILEPATH]];

}

@end

Recipe: Creating a Text View–Based HTML EditorAlthough Apple officially removed HTML support from the UITextView class, itremains hidden in the UIKit framework.Access this undocumented feature when youwant to add simple rich text extensions. Obviously,Apple intends you to use UIWebViewrather than UITextView for HTML display, but UITextView offers an appealing alternativefor low-rent markup.

To access this HTML display, declare setContentToHTMLString: as shown inRecipe 8-9.This undocumented UITextView method tells the text view to interpret astring as HTML source. Using UITextView offers a couple of small benefits:

n First, UITextViews can be edited.You can initialize the view with HTML textand then allow your user to edit the results.Text picks up the properties of the surrounding elements. If you add text to a bold headline, for example, the new textremains bolded.

n Second, UITextViews can easily be reloaded. If you allow your user to edit text intext-based source mode, for example, you can then reload those changes into thesame view in HTML mode.

Recipe 8-9 shows how to build a simple HTML editor. Figure 8-9 shows both thetext source and HTML presentation in the same UITextView.This is not a common(or, really, practical) use of the technology, but it highlights a feature that you may have areason to use some day. More often, you’ll just want to initialize text with a few rich textfeatures (like a bolded headline) and then allow users to edit that text directly.

NoteUse NSLocalizedString to internationalize text. Pass it any key from your languagefolder’s Localizable.strings files (for example, from French.lproj or English.lproj).NSLocalizedString(@“Title”, nil) looks for the Title key in a strings file andreturns the localized string that matches that key. The iPhone knows which language touse, thus which lproj folder to look in, via General, International Settings. To internationalizean application name, add an InfoPlist.strings file to each lproj folder. Set yourCFBundleDisplayName key in those files. Consult Apple documents for up-to-datedetails after the SDK is released.

253Recipe: Creating a Text View–Based HTML Editor

Page 277: I Phone Developer Cookbook

Recipe 8-9 Using Undocumented UITextView HTML Features

- (void) presentText: (id) sender

{

// dismiss keyboard if it exists

[contentView resignFirstResponder];

// Store text only if previous editor was text-based

if ([contentView tag]) self.sourceText = [contentView text];

// Text-based Editor

[contentView setText:self.sourceText];

[contentView setEditable:YES];

[contentView setTag:YES];

}

- (void) presentHTML: (id) sender

{

// dismiss keyboard if it exists

[contentView resignFirstResponder];

// Store text only if previous editor was text-based

if ([contentView tag]) self.sourceText = [contentView text];

// HTML-based Presentation-only

254 Chapter 8 Controls

Figure 8-9 This UITextView-based HTML editor allows users to editsource and see the results in the same view.

Page 278: I Phone Developer Cookbook

Recipe 8-9 Continued

[contentView setContentToHTMLString:self.sourceText];

[contentView setEditable:NO];

[contentView setTag:NO];

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setAutocorrectionType:UITextAutocorrectionTypeNo];

[contentView setTag:NO];

[contentView setEditable:NO];

self.view = contentView;

[contentView release];

// Load the HTML source

self.sourceText = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]➥pathForResource:@"decl" ofType:@"html"]];

[contentView setContentToHTMLString:self.sourceText];

// Add Text and HTML buttons

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Text"

style:UIBarButtonItemStylePlain

target:self

action:@selector(presentText:)]autorelease];

self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"HTML"

style:UIBarButtonItemStylePlain

target:self

action:@selector(presentHTML:)]autorelease];

}

Recipe: Building an Interactive Search BarThe UISearchBar class offers a simple search-themed text field that’s well suited for display in an application’s navigation bar. Figure 8-10 shows a search bar in action, huntingamong crayon names for colors that match the search term.The search is live.As userstap in text, the table automatically updates.

255Recipe: Building an Interactive Search Bar

Page 279: I Phone Developer Cookbook

Recipe 8-10 uses searchBar: textDidChange: to catch search bar text changes.This method rebuilds the array that feeds the onscreen table as the user taps new characters.It matches the text in the search field to items in the array.

Notice the Return key swap-out used in Figure 8-10. Normally, search bars delaysearches until the user finishes typing in a term.That’s why they display Search for theirdefault Return key. Recipe 8-10’s live interaction doesn’t use that search model. Instead,the Done key lets the user examine the results of the live search he or she has alreadyperformed upon dismissing the keyboard.

To replace the Return key, access the UISearchBar instance’s search field subviewand set the text traits for that field.At this time, the text view is the frontmost (that is,last) view. Should this change in future SDK releases, adjust the recipe code accordingly.

Recipe 8-10 Using a Search Bar with Live Feedback

// create an array by matching to the search string

- (void) buildSearchArrayFrom: (NSString *) matchString

{

NSString *upString = [[matchString uppercaseString] autorelease];

if (searchArray) [searchArray release];

searchArray = [[NSMutableArray alloc] init];

for (NSString *word in colorArray)

{

256 Chapter 8 Controls

Figure 8-10 Search bars offer excellent interactive controls forlocating items in long lists.

Page 280: I Phone Developer Cookbook

Recipe 8-10 Continued

if ([matchString length] == 0)

{

[searchArray addObject:word];

continue;

}

NSRange range = [[[[word componentsSeparatedByString:@" #"]➥objectAtIndex:0] uppercaseString] rangeOfString:upString];

if (range.location != NSNotFound) [searchArray addObject:word];

}

[self.tableView reloadData];

}

// When the search text changes, update the array

- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText

{

[self buildSearchArrayFrom:searchText];

if ([searchText length] == 0) [searchBar resignFirstResponder];

}

// When the search ("done") button is clicked, hide the keyboard

- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar

{

[searchBar resignFirstResponder];

}

// Prepare the Table View

- (void)loadView

{

[super loadView];

// Retrieve the text and colors from file

NSString *pathname = [[NSBundle mainBundle] pathForResource:@"crayons"➥ofType:@"txt" inDirectory:@"/"];

NSString *wordstring = [NSString stringWithContentsOfFile:pathname];

colorArray = [[wordstring componentsSeparatedByString:@"\n"] retain];

[self buildSearchArrayFrom:@""];

search = [[UISearchBar alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 280.0f,➥44.0f)];

search.delegate = self;

search.placeholder = @"color match text";

search.autocorrectionType = UITextAutocorrectionTypeNo;

search.autocapitalizationType = UITextAutocapitalizationTypeNone;

self.navigationItem.titleView = search;

[search release];

// "Search" is the wrong key usage here. Replacing it with "Done"

257Recipe: Building an Interactive Search Bar

Page 281: I Phone Developer Cookbook

Recipe 8-10 Continued

UITextField *searchField = [[search subviews] lastObject];

[searchField setReturnKeyType:UIReturnKeyDone];

}

Recipe: Adding Callout ViewsIf you’ve used Google Maps on the iPhone, you’ve likely seen UICalloutView instancesin action.They are, despite their name, a kind of UIControl instance.They are undocu-mented but available in the UIKit framework.

Callout views point to something onscreen.They can display a temporary messagebefore moving to another message with an attached disclosure button. Figure 8-11 shows ascreen with several callout views displayed,some showing their original (temporary) message,others showing the final one. Recipe 8-11 provides the code that built this screen.

258 Chapter 8 Controls

Figure 8-11 UICalloutView instances point to things. They can display an initial message (here, “You Tapped Here!”) and a final message(“More Info ... ” in this case) with an attached disclosure button. Callouts

are used in Apple’s Google Maps application.

Because the class is undocumented, you need to define headers. Listing 8-6 shows aUICalloutView class definition. It’s incomplete, but it offers enough functionality thatyou can effectively use the class in your programs.

Page 282: I Phone Developer Cookbook

Listing 8-6 Defining the UICalloutView Interface

@class UIImageView, UILabel, UIPushButton;

@interface UICalloutView : UIControl

- (UICalloutView *)initWithFrame:(struct CGRect)aFrame;

- (void)fadeOutWithDuration:(float)duration;

- (void)setTemporaryTitle:(NSString *)fp8;

- (NSString *)temporaryTitle;

- (void)setTitle:(NSString *)fp8;

- (NSString *)title;

- (void)addTarget:(id)target action:(SEL)selector;

- (void)removeTarget:(id)target;

- (void)setAnchorPoint:(struct CGPoint)aPoint boundaryRect:(struct CGRect)aRect➥animate:(BOOL)yorn;

@end

To create a callout, pass an anchor point—that’s the coordinates that the callout pointsto—plus a generalized boundary rectangle.These bounds are, as far as I can tell,completely ignored.

The callout uses two titles: a temporary one and a normal one.When you add a temporary title, this title displays for a second or so before fading to the normal title, theone with the disclosure.You can also add subtitles to the callout, but they look horribleand I don’t recommend using them.

Set the target for the disclosure by calling addTarget: action:.The selector youpass gets called when the disclosure is tapped. User taps on the callout don’t count; theyare ignored by the iPhone. So if you want to get rid of a callout when a new oneappears, it’s up to you to dismiss it. Use fadeOutWithDuration: and specify the fadeout time in seconds.

Recipe 8-11 Using UICalloutViews

@interface TapView : UIImageView

@end

@implementation TapView

- (void) hideDisclosure: (UIPushButton *) calloutButton

{

UICalloutView *callout = (UICalloutView *)[calloutButton superview];

[callout fadeOutWithDuration:1.0f];

}

- (UICalloutView *) buildDisclosure

{

UICalloutView *callout = [[[UICalloutView alloc] initWithFrame:CGRectZero]➥autorelease];

callout.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

259Recipe: Adding Callout Views

Page 283: I Phone Developer Cookbook

Recipe 8-11 Continued

callout.contentHorizontalAlignment =UIControlContentHorizontalAlignmentCenter;

[callout setTemporaryTitle:@"You Tapped Here!"];

[callout setTitle:@"More Info..."];

[callout addTarget:self action:@selector(hideDisclosure:)];

return callout;

}

- (void) showDisclosureAt:(CGPoint) aPoint

{

UICalloutView *callout = [self buildDisclosure];

[self addSubview:callout];

[callout setAnchorPoint:aPoint boundaryRect:CGRectMake(0.0f, 0.0f, 320.0f,➥100.0f) animate:YES];

}

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

[self showDisclosureAt:[[touches anyObject] locationInView:self]];

}

@end

Adding a Page Indicator ControlSadly, the UIPageControl class does not live up to expectations. It does not belong tothe same class as the excellent version seen in SpringBoard.They look the same; theyperform differently.The UIPageControl class available in the official SDK is awkwardto handle, hard to tap, and will generally annoy your users. So when using it, make sureyou add alternative navigation options (such as the swiping covered in Chapter 2,“Views”) so that the page control acts more as an indicator and less as a control.

Figure 8-12 shows a page control in action.Taps to the left or right of the bright-colored current page indicator trigger UIControlEventValueChanged events, launch-ing whatever method you set as the control’s action.You can query the control for itsnew value by calling currentPage and set the available page count by adjusting thenumberOfPages property.

Recipe 8-12 displays five unique pages of images, but in fact, it uses only two imageviews.At any given time, one view is displayed, and the other view is ready to load withthe next image—regardless of whether that image is numerically before or after the currentone.When the user moves to a new page, the view controller loads the new image intothe unused image view and calls a UIViewAnimation block to swap between the two

260 Chapter 8 Controls

Page 284: I Phone Developer Cookbook

image views.The numeric relation between the two “pages” determine whether theswap pushes the images from right to left, or left to right.

261Adding a Page Indicator Control

Figure 8-12 The UIPageControl class offers an interactive indicatorfor multipage presentations. Taps to the left or right of the active dot

enable users to select new pages. At least they do in theory. The pagecontrol is hard to tap and offers poor response performance.

Recipe 8-12 Using the UIPageControl Indicator

@interface HelloController : UIViewController

{

SwipeView *contentView;

int currentPage;

}

@end

@implementation HelloController

- (id)init

{

if (!(self = [super init])) return self;

self.title = @"Page Control";

return self;

}

// Build and return an animation with the given direction

Page 285: I Phone Developer Cookbook

Recipe 8-12 Continued

- (CATransition *) getAnimation:(NSString *) direction

{

CATransition *animation = [CATransition animation];

[animation setDelegate:self];

[animation setType:kCATransitionPush];

[animation setSubtype:direction];

[animation setDuration:1.0f];

[animation setTimingFunction:[CAMediaTimingFunction➥functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];

return animation;

}

// Perform the page turn animation, in this case a push

- (void) pageTurn: (UIPageControl *) pageControl

{

CATransition *transition;

int secondPage = [pageControl currentPage];

if ((secondPage - currentPage) > 0)

transition = [self getAnimation:@"fromRight"];

else

transition = [self getAnimation:@"fromLeft"];

UIImageView *newView = (UIImageView *)[[contentView subviews]➥objectAtIndex:0];

[newView setImage:[UIImage imageNamed:[NSString stringWithFormat:@"f%d.png",➥secondPage + 1]]];

[contentView exchangeSubviewAtIndex:0 withSubviewAtIndex:1];

[[contentView layer] addAnimation:transition➥forKey:@"transitionViewAnimation"];

currentPage = [pageControl currentPage];

}

- (void)loadView

{

UIView *baseView = [[[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]] autorelease];

// Add the content view with its two images

contentView = [[[SwipeView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]] autorelease];

[contentView setHost:self];

[contentView addSubview:[[UIImageView alloc] initWithFrame:[[UIScreen➥mainScreen] applicationFrame]]];

[contentView addSubview:[[UIImageView alloc] initWithFrame:[[UIScreen➥mainScreen] applicationFrame]]];

262 Chapter 8 Controls

Page 286: I Phone Developer Cookbook

Recipe 8-12 Continued

for (UIView *view in [contentView subviews]) [view➥setUserInteractionEnabled:NO];

// initialize with "page" 3

[[[contentView subviews] lastObject] setImage:[UIImage imageNamed:@"f3.png"]];

[baseView addSubview:contentView];

// Add the page control

UIPageControl *pageControl = [[UIPageControl alloc]➥initWithFrame:CGRectMake(0.0f, 10.0f, 320.0f, 20.0f)];

[pageControl setNumberOfPages:5];

[pageControl setCurrentPage:(currentPage = 2)];

[pageControl addTarget:self action:@selector(pageTurn:)➥forControlEvents:UIControlEventValueChanged];

[baseView addSubview:pageControl];

self.view = baseView;

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

Recipe: Customizing ToolbarsThe UITabBar class officially supports customization.The UIToolbar class does not.Youcan drag view controller buttons into and out of a tab bar from its More screen.Withthe toolbar, you cannot.At least you cannot do so officially.

Unofficially, the UIToolbar offers a similar (but undocumented) interaction style.Although you can’t add new items or switch items out, you can reorder the toolbar. It’snot an especially flexible interaction style, but you might be able to use it to enable usersto customize how they want your toolbar to appear or let them rearrange Scrabble letters.The use is up to you.As with all undocumented calls in public frameworks, you are freeto use them in your program, but they can change without notice. Figure 8-13 showsthe toolbar customization screen in action.

All objects in the toolbar appear in the customization screen.That means if youinclude fixed or flexible spaces, they’ll show up there as well as your icons.You must addan external way to finish editing the toolbar because once you enter the customizationmode, triggered actions stop working from the toolbar buttons.That’s the role of theDone button at the top right of the screen.

263Recipe: Customizing Toolbars

Page 287: I Phone Developer Cookbook

Recipe 8-13 shows how to add toolbar customization. It extends the UIToolbar classto reveal the undocumented begin and end customizing methods.

264 Chapter 8 Controls

Figure 8-13 The undocumented UIToolbar customization screenallows users to reorder toolbar items.

Recipe 8-13 Customizing Toolbars

// Expose the customizing calls

@interface UIToolbar (extended)

- (void)beginCustomizingItems:(id)fp8;

- (BOOL)endCustomizingAnimated:(BOOL)fp8;

@end;

@interface HelloController : UIViewController

{

UIImageView *contentView;

UIToolbar *toolbar;

NSMutableArray *allitems;

}

@end

@implementation HelloController

// When finished customizing, remove the Done button

- (void) endEditing

Page 288: I Phone Developer Cookbook

Recipe 8-13 Continued

{

[toolbar endCustomizingAnimated:YES];

self.navigationItem.rightBarButtonItem = NULL;

}

// Reveal the Done button and begin customizing

- (void) doAction: (id) sender

{

[toolbar beginCustomizingItems:allitems];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Done"

style:UIBarButtonItemStylePlain

target:self

action:@selector(endEditing)]autorelease];

}

- (void)loadView

{

// Load an application image and set it as the primary view

contentView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

[contentView setUserInteractionEnabled:YES];

self.view = contentView;

[contentView release];

toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0.0f, 416.0f - 44.0f,➥320.0f, 44.0f)];

// Add a whole bunch of arbitrary system items with icons

allitems = [[NSMutableArray alloc] init];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@➥selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemBookmarks target:self➥action:@selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCamera target:self action:@➥selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemCompose target:self➥action:@selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemOrganize target:self➥action:@selector(doAction:)] autorelease]];

265Recipe: Customizing Toolbars

Page 289: I Phone Developer Cookbook

Recipe 8-13 Continued

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemRefresh target:self➥action:@selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemReply target:self action:@➥selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@➥selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemStop target:self action:@➥selector(doAction:)] autorelease]];

[allitems addObject:[[[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemTrash target:self action:@➥selector(doAction:)] autorelease]];

[toolbar setItems:allitems];

[contentView addSubview:toolbar];

[toolbar release];

}

-(void) dealloc

{

[toolbar release];

[allitems release];

[contentView release];

[super dealloc];

}

@end

Toolbar TipsWhen working with toolbars, here are a few tricks of the trade that might come inhandy:

n Fixed spaces can have widths.

Of all UIBarButtonItems, only UIBarButtonSystemItemFixedSpace itemscan be assigned a width. So create the spacer item, set its width, and only then addit to your items array.

UIBarButtonItem *spacer = [[[UIBarButtonItem alloc]

initWithBarButtonSystemItem:UIBarButtonSystemItemFixedSpace

target:NULL action:NULL] autorelease];

spacer.width = 20;

[items addObject:spacer];

266 Chapter 8 Controls

Page 290: I Phone Developer Cookbook

n Use a single flexible space for left or right alignment.

Adding a single UIBarButtonSystemItemFlexibleSpace at the start of an itemslist right-aligns all the remaining items.Adding one to the end, left-aligns. Usetwo, one at the start and one at the end to create center alignments.

n Take missing items into account.

When hiding bar button items due to context, don’t just use flexible spacing to getrid of the item. Instead, replace the item with a fixed-width space that matches theitem’s original size.That preserves the layout and leaves all the other icons in thesame position both before and after the item disappears.

n Bar button items aren’t views.

If you ever need to add a subview to one, give it a proper parent and let that parent (whether it’s a toolbar, tab bar, or navigation bar) build it onscreen.You canthen use the undocumented view call to recover the bar button item’s view.

SummaryThis chapter introduced many nonstandard (and in some cases nonofficial and undocu-mented) 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:

n Don’t be afraid to peek into controls and discover how they’re put together. Onceyou have exposed standard SDK classes like labels or buttons, customize them toyour specifications and desires.

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

n Core Graphics lets you build visual elements as needed. Combine the comfort ofthe SDK classes with a little real-time wow to add punch to your presentation.

n As with the search bar keyboard example, don’t let Apple’s preconceived notionsabout how a control should be used stand in the way of using it the way you thinkyou should. If “search” doesn’t describe what users need to do at the end of aninteraction, switch the Return button to a better phrase that does.

n Callout views are just one of many undocumented classes that ship with UIKitand add richness to your applications.

n This chapter covered a lot of undocumented classes and calls.As with all undocu-mented and unofficial material, use with due care in your production software.

267Summary

Page 291: I Phone Developer Cookbook
Page 292: I Phone Developer Cookbook

9People, Places, and Things

In addition to standard user interface controls and media components that you’d see onany computer, the iPhone SDK provides a number of tightly focused developer solutionsspecific to iPhone and iPod touch delivery.These classes and routines access narrowiPhone core functionality.The most useful of these include Address Book access (“people”), Core Location (“places”), and sensors (“things”). Each of these personalizesthe iPhone: where you are, who you know, and how you hold the phone.This chapterpresents recipes that show you how to use these technologies in your own developmentwork.

Address Book FrameworksThe iPhone SDK provides two address book frameworks:AddressBook.framework andAddressBookUI.framework.As their names suggest, they occupy two distinct niches inthe iPhone SDK.Address Book contains low-level C-based structures and routines foraccessing contact information from the iPhone’s onboard SQLite databases.AddressBook UI provides high-level Objective-C based UIViewController browser objects topresent to users. Both frameworks are small.They provide just a few classes and datatypes.

On the iPhone, contact data resides in the home Library folder. On the Macintoshsimulator, you can freely access these files in ~/Library/Application Support/iPhoneSimulator/User/Library.The two files,AddressBook/AddressBook.sqlitedb andAddressBook/AddressBookImages.sqlitedb use standard SQLite3 to store contact infor-mation and optional contact images. On the iPhone, you cannot access these directly.The files live in /var/mobile/Library/AddressBook.You must either use the two AddressBook frameworks or break out of the sandbox.The latter may invalidate any application’sApp Store eligibility.

Address Book UIThe Address Book UI framework provides a few precooked views that interact with theonboard contacts database.These include a general people picker, a one-contact display,

Page 293: I Phone Developer Cookbook

and a contact editor.You set a delegate and then push these controllers onto your navigation stack or display them modally, as shown in the recipes in this chapter.

Like the Image Picker and Camera controllers you saw in Chapter 7,“Media,” theAddress Book UI controllers are not very flexible.Apple intends you to use them as pro-vided, with little or no customization from the developer.What’s more, they require acertain degree of low-level programming prowess.As you’ll see, these classes interactwith the underlying Address Book in circuitous ways.

Address BookIn the C-based Address Book framework, the ABPerson type provides the core contactstructure.This record stores all information for each contact, including name, e-mail,phone numbers, and so forth. Because each person may have multiple e-mails, phonenumbers, and important dates associated with his or her contact, ABPerson uses a con-voluted “multivalue” structure to store that information. In normal programming terms,this means an array. In Address Book terms, this involves a little extra programming.

Handling People Picker CallsBecause dates, strings, and numbers all use different typing,Address Book provides func-tions to help you store and recover mixed type data to and from the contact data files. Ifyou’re looking for clean Objective-C or Core Foundation calls, look again.

When responding to GUI interactions via the AddressBookUI people picker, yougenerally recover untyped multivalued properties.That is, you copy a value from theABPerson record without knowing exactly what it is you have copied.You must requestthe property type and then provide separate handling for each type, extracting the actualvalue from its array. For example

id theProperty = [(id)ABRecordCopyValue(person, property) autorelease];

int propertyType = ABPersonGetTypeOfProperty(property);

if (propertyType == kABMultiStringPropertyType)

NSString *result = [(NSString *)[(NSArray➥*)ABMultiValueCopyArrayOfAllValues(theProperty) objectAtIndex:identifier]➥autorelease];

Here, the copy value function extracts a property, which is handed off to get a property type.Testing this property type against all known kinds lets you identify a classfor the extracted property and provide a typed result object.

Fortunately, this kind of complex untyped handling is limited to interactively selectingproperties like a phone number, e-mail address, or street address. It’s much easier to letusers select an entire contact at once and recover general information from that contact.

Querying the Address Book and Its RecordsDirect database searches are definitely simpler than responding to the GUI.To request alist of all available contacts (businesses as well as individuals), call the “all people” func-tion.This returns an array of all available ABPersons.The badly named create function

270 Chapter 9 People, Places, and Things

Page 294: I Phone Developer Cookbook

seen here returns an Address Book reference populated with data from your entireContacts database.You’re not actually creating a new Address Book.You’re just supplyingthe copy array function with a link to the existing one:

peopleArray = (NSMutableArray➥*)ABAddressBookCopyArrayOfAllPeople(ABAddressBookCreate());

Query ABPerson records to pull out any and all properties.There are quite a numberof them, about 17 if I counted correctly.They range from birthdays and middle names toe-mail addresses and job titles. Some of these return a string or an integer, others a dic-tionary or a date. Some return a single value (people rarely have multiple first names),others return arrays (people often have many e-mail addresses).

Here are ways to pull out a contact’s first name, last name, and organization from anABPerson record. Each of these returns a string. Notice how the ABRecordCopyValuefunction returns an untyped value. Know these property types in advance or callABPersonGetTypeOfProperty() to request the type:

NSString *firstName = [(NSString *)ABRecordCopyValue(person,➥kABPersonFirstNameProperty) autorelease];

NSString *lastName = [(NSString *)ABRecordCopyValue(person,➥kABPersonLastNameProperty) autorelease];

NSString *biz = [(NSString *)ABRecordCopyValue(person,➥kABPersonOrganizationProperty) autorelease];

Recipe: Accessing Address Book Image DataSome Address Book contacts may use photo data. Others may not.Associated imagesoffer an elegant way to match people to faces and enhance the iPhone experience.Whenused, pictures are stored in the AddressBookImages.sqlitedb database.To check whether aphoto is available, call ABPersonHasImageData(person), where person is anABPerson record.This returns YES or NO, indicating whether you can request imagedata.

When YES, call ABPersonCopyImageData(person) to provide the NSDatareference that you can use to fill a UIImage object. Here’s a typical call to add imagedata to a UITableCell:

if (ABPersonHasImageData(person)) {

UIImage *img = [UIImage imageWithData:(NSData➥*)ABPersonCopyImageData(person)];

[cell setImage:img];

} else { [cell setImage:NULL]; }

Unfortunately, photos do not conform to any native sizing. One image may bethumbnail sized, whereas another is full screen. Recipe 9-1 demonstrates how to accessand resize contact images to fit a standard thumbnail size that you specify, in this case60 by 60 pixels.This recipe returns table cells populated with address information and

271Recipe: Accessing Address Book Image Data

Page 295: I Phone Developer Cookbook

(where possible) a 60x60 contact image.To do this, it creates a custom bitmap contextand fills that context with the scaled image.

NoteThis code calls the MyCreateBitmapContext function, which can be found in Recipe 8-5.

Recipe 9-1 Accessing and Resizing Contact Images

#define IMG_SIZE 60

// Draw the image into a 60x60 bitmap and use that bitmap to

// create a new UIImage

id createImage(CGImageRef image)

{

// Set the size of the output image

CGRect aRect = CGRectMake(0.0f, 0.0f, IMG_SIZE, IMG_SIZE);

// Create a bitmap context to store the new thumbnail

CGContextRef context = MyCreateBitmapContext(IMG_SIZE, IMG_SIZE);

// Clear the context and draw the image into the rectangle

CGContextClearRect(context, aRect);

CGContextDrawImage(context, aRect, image);

// Return a UIImage populated with the new thumbnail

CGImageRef myRef = CGBitmapContextCreateImage (context);

free(CGBitmapContextGetData(context));

CGContextRelease(context);

return [UIImage imageWithCGImage:myRef];

}

// Return the string that best represents the contact name

// or organization

- (NSString *) getName: (ABRecordRef) person

{

NSString *firstName = [(NSString *)ABRecordCopyValue(person,➥kABPersonFirstNameProperty) autorelease];

NSString *lastName = [(NSString *)ABRecordCopyValue(person,➥kABPersonLastNameProperty) autorelease];

NSString *biz = [(NSString *)ABRecordCopyValue(person,➥kABPersonOrganizationProperty) autorelease];

if ((!firstName) && !(lastName))

{

if (biz) return biz;

272 Chapter 9 People, Places, and Things

Page 296: I Phone Developer Cookbook

Recipe 9-1 Continued

return @"[No name supplied]";

}

if (!lastName) lastName = @"";

if (!firstName) firstName = @"";

return [NSString stringWithFormat:@"%@ %@", firstName, lastName];

}

// Provide a table cell that contains the contact name and,

// where possible, a contact image

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

{

NSInteger row = [indexPath row];

UITableViewCell *cell = [self.tableView➥dequeueReusableCellWithIdentifier:@"any-cell"];

if (cell == nil)

cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero➥reuseIdentifier:@"any-cell"] autorelease];

id person = [searchArray objectAtIndex:row];

cell.text = [self getName:person];

// Set the cell image when image data is available

if (ABPersonHasImageData(person))

{

UIImage *img = createImage([[UIImage imageWithData:(NSData *)➥ABPersonCopyImageData(person)] CGImage]);

[cell setImage:img];

}

else [cell setImage:NULL];

return cell;

}

Recipe: Displaying Address Book InformationThe AddressBookUI framework simplifies contact information display. Use anABPersonViewController instance to present the data stored in an ABPerson record,as shown in Figure 9-1. AddressBookUI takes all the design details out of the equation,creating a perfect layout.As Recipe 9-2 shows, just allocate the controller, set thedisplayedPerson to an ABPerson record, and then push the view controller intoplace.

273Recipe: Displaying Address Book Information

Page 297: I Phone Developer Cookbook

Recipe 9-2 Using the ABPersonViewController

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath➥*)newIndexPath

{

ABPersonViewController *pvc = [[ABPersonViewController alloc] init];

pvc.displayedPerson = [searchArray objectAtIndex:[newIndexPath row]];

[[self navigationController] pushViewController:pvc animated:YES];

}

Recipe: Browsing the Address BookThe AddressBookUI framework offers several launchable controllers. Browsing yourentire Contacts list turns out to be just as easily accomplished as displaying an individualcontact screen. Use the ABPeoplePickerNavigationController class to present aninteractive browser, as shown in Figure 9-2. Here’s the code that created that figure:

ABPeoplePickerNavigationController *ab = [[ABPeoplePickerNavigationController➥alloc] init];

[ab setPeoplePickerDelegate:self];

[self presentModalViewController:ab animated:YES];

274 Chapter 9 People, Places, and Things

Figure 9-1 Initialize and display anAddressBookPersonViewController instance to present

the data from any ABPerson record.

Page 298: I Phone Developer Cookbook

What remains tricky is deciding when to navigate away and how to recover anyselected data.Three delegate methods control how your picker reacts to user interac-tions.You specify how to react after users tap a contact, or any of a contact’s properties,or when the user taps Cancel:

n peoplePickerNavigationController:

➥shouldContinueAfterSelectingPerson:

When users tap a contact, you have two choices.You can accept the person as thefinal selection and dismiss the modal view controller, or you can navigate to theindividual display seen in Figure 9-1.To pick just the person, this method mustreturn NO.To continue to the individual screen, return YES.The second argumentcontains the selected person, in case you want to stop after selecting any ABPersonrecord.

n peoplePickerNavigationController:

➥shouldContinueAfterSelectingPerson: property: identifier:

This optional method does not get called until the user has progressed to an indi-vidual contact display screen, like the one shown in Figure 9-1.Then, it’s up to youwhether to return control to your program (return NO) or to continue (return YES).Recipe 9-3 demonstrates how to determine which property has been tapped and torecover its value.This is, unfortunately, both ugly and complicated.

275Recipe: Browsing the Address Book

Figure 9-2 The iPhone people picker navigation control enables users to search through the contacts

database and select a person or organization.

Page 299: I Phone Developer Cookbook

n peoplePickerNavigationControllerDidCancel:

After a user taps Cancel, you’ll want to dismiss the modal view. Catch the cancelevent by implementing this delegate method and use it to perform the dismissal.

Recipe 9-3 Handling People Picker Events

// Handle the tap on a username

- (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController➥*)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person

{

self.title = [self getName:person];

return YES; // continue to secondary screen

}

// Handle the tap on a user property

- (BOOL) peoplePickerNavigationController:(ABPeoplePickerNavigationController➥*)peoplePicker shouldContinueAfterSelectingPerson:(ABRecordRef)person➥property:(ABPropertyID)property identifier:(ABMultiValueIdentifier)identifier

{

// Get the selected property and its property type

id theProperty = (id)ABRecordCopyValue(person, property);

int propertyType = ABPersonGetTypeOfProperty(property);

// determine the kind of property passed and

// print out its value to the debugging console

if (propertyType == kABStringPropertyType) {

printf("%s\n", [theProperty UTF8String]);

} else if (propertyType == kABIntegerPropertyType) {

printf("%d\n", [theProperty integerValue]);

} else if (propertyType == kABRealPropertyType) {

printf("%d\n", [theProperty floatValue]);

} else if (propertyType == kABDateTimePropertyType) {

printf("%s\n", [[theProperty description] UTF8String]);

} else if (propertyType == kABMultiStringPropertyType) {

printf("%s\n",

[[(NSArray *)ABMultiValueCopyArrayOfAllValues(theProperty)➥objectAtIndex:identifier] UTF8String]);

} else if (propertyType == kABMultiIntegerPropertyType) {

printf("%d\n", [[(NSArray➥*)ABMultiValueCopyArrayOfAllValues(theProperty) objectAtIndex:identifier]➥integerValue]);

} else if (propertyType == kABMultiRealPropertyType) {

printf("%f\n", [[(NSArray➥*)ABMultiValueCopyArrayOfAllValues(theProperty) objectAtIndex:identifier]➥floatValue]);

} else if (propertyType == kABMultiDateTimePropertyType) {

printf("%s\n", [[[(NSArray➥*)ABMultiValueCopyArrayOfAllValues(theProperty) objectAtIndex:identifier] ➥description] UTF8String]);

276 Chapter 9 People, Places, and Things

Page 300: I Phone Developer Cookbook

Recipe 9-3 Continued

} else if (propertyType == kABMultiDictionaryPropertyType) {

printf("%s\n", [[[(NSArray➥*)ABMultiValueCopyArrayOfAllValues(theProperty) objectAtIndex:identifier] ➥description] UTF8String]);

}

[self dismissModalViewControllerAnimated:YES];

CFRelease(theProperty);

[peoplePicker release];

return NO; // leave the picker after printing property

}

// Handle a user’s Cancel tap

- (void) peoplePickerNavigationControllerDidCancel:➥(ABPeoplePickerNavigationController *)peoplePicker

{

[self dismissModalViewControllerAnimated:YES];

[peoplePicker release];

}

Browsing for (Only) E-Mail AddressesWhen you want users to pick, for example, an e-mail address, you won’t want to presentthem with a person’s street address or fax number. Instead, you can limit the peoplepicker’s displayed properties to show just those items you want the users to select from.To make this happen, choose the displayed properties by submitting an array of propertytypes to the controller:

ABPeoplePickerNavigationController *ab = [[ABPeoplePickerNavigationController ➥alloc] init];

[ab setDisplayedProperties:[NSArray arrayWithObject:[NSNumber➥numberWithInt:kABPersonEmailProperty]]];

Adding New ContactsThe ABNewPersonViewController class provides an interactive way to add new contacts to your Address Book. Unfortunately, at the time of writing, this class was notfully implemented by Apple.At some future point, here’s how the class will work.Themethod shown here presets the person’s first and last name, but that’s not mandatory:

- (void) presentSheet

{

ABNewPersonViewController *np = [[ABNewPersonViewController➥alloc] init];

np.addressBook = ABAddressBookCreate();

277Recipe: Browsing the Address Book

Page 301: I Phone Developer Cookbook

ABRecordRef aRecord = ABPersonCreate();

CFErrorRef anError;

ABRecordSetValue(aRecord, kABPersonFirstNameProperty,➥CFSTR("Katie"), &anError);

ABRecordSetValue(aRecord, kABPersonLastNameProperty,➥CFSTR("Bell"), &anError);

np.displayedPerson = aRecord;

CFRelease(aRecord);

np.newPersonViewDelegate = self;

[self presentModalViewController:np animated:YES];

}

Core LocationCore Location powers one of the nicest iPhone SDK features.This service returns(approximate) on-demand geopositioning with latitude and longitude data for iPhonesand iPod touch units without built-in GPS and (pinpoint-precise) positioning for thoseunits with GPS.Adding location to applications opens up a whole new programmingarena where your computer becomes as important as what you compute.You can meetup with friends, search for local resources, or provide location-based streams of personalinformation.

Core Location enables applications to hook into location-aware social networkingand data storage sites like fireeagle.com, outside.in, upcoming.org, twitter.com, andflickr.com. It lets you provide geotagged content or request local resources such asrestaurant and events listings.With on-demand geolocation, mobile computing takes onnew meaning and opens a wide range of Web 2.0 options.

How Core Location WorksThe iPhone uses three (or possibly four) methods to locate you.These technologiesdepend on several providers including Skyhook Wireless (http://skyhookwireless.com,aka http://loki.com), Google Maps (http://maps.google.com/) and the U.S. Departmentof Defense Global Positioning System (http://tycho.usno.navy.mil/gpsinfo.html).

GPS PositioningIf you own a newer-model 3G iPhone, the onboard GPS system tracks your movementcourtesy of a series of medium Earth orbit satellites provided by the U.S. Department ofDefense.These satellites emit microwave signals, which your iPhone picks up and uses totriangulate your position to a high level of accuracy. Like any GPS system, this requires aclear path between you and the satellites, so it works best outdoors and away from trees.

GPS positioning is not currently available for the first generation 2.5G iPhone or theiPod touch line.

278 Chapter 9 People, Places, and Things

Page 302: I Phone Developer Cookbook

SkyHook WiFi PositioningCore Location’s preferred pseudo-GPS geopositioning method calls on SkyHookWireless. SkyHook offers extremely accurate WiFi placement.When an iPhone knowsthe WiFi and WiMax routers you are near, it uses their MAC addresses to searchSkyHook’s databases, positioning you from that data.

SkyHook WiFi data collection works like this. SkyHook sends drivers down citystreets throughout its covered territories, which includes most U.S. metropolitan areas.The cars scan for WiFi hotspots and when found, they record the location using traditional GPS positioning matched to the WiFi MAC address.

This works great when WiFi routers stay still.This works terribly when people packup their WiFi routers and move with them to, say, Kentucky.That having been said,SkyHook data does get updated. It provides pretty accurate positioning and can usuallylocate you within a few hundred feet of your actual location, even though people andtheir routers will continue to move to Kentucky and other places.

Google Maps Cell Tower PositioningA less-accurate approach involves Google Maps and cell tower positioning. Here, theiPhone uses its antenna to find the nearest four or five cell towers and then triangulatesyour position based on the cell tower signal strength.You’ve probably seen cell towerlocation in action; it’s the kind that shows you about a half mile away from where youare standing—assuming you’re not standing right next to an actual cell tower.

Curiously enough, SkyHook also offers cell tower positioning, but when I lastexplored the iPhone frameworks, the calls went out to Google.This may change basedon Apple licensing agreements.Yahoo! ZoneTag also offers a free cell tower positioningAPI, which is newer and less accurate than Google’s, but it is open, with a public API.

iPod touch units cannot use cell tower positioning, although both first- and second-generation iPhones can.

SkyHook Internet Provider PositioningSkyHook actually offers a third positioning approach, but it is one I’ve never seen theiPhone use.Then again, I live in a major metropolitan area; I haven’t given it a very goodtry.This last-ditch approach uses an Internet provider location to find the nearestmapped Internet provider’s central office.This is a solution of last resort.The returneddata is typically up to several miles off your actual location—unless you happen to bevisiting your Internet provider.

Hybridizing the ApproachesThe iPhone approaches location in stages. Based on the accuracy level you request, ituses a fallback method. If it cannot accurately locate you with GPS or SkyHook WiFimapping, it falls back to the cell tower location of Google Maps. If that doesn’t work, itpresumably falls back further to SkyHook Internet provider location.And if that doesn’twork, it finally fails.The latest releases of the SDK actually provide multiple (asynchro-nous!) success callbacks for each of these fallback methods.You may receive three or fourresults at any time.

279Core Location

Page 303: I Phone Developer Cookbook

Knowing how the iPhone does this is important.That’s because any ten attempts tograb your location on a first generation iPhone may result in maybe three or four WiFisuccesses, the remainder falling back to cell tower hits.Although you can set your desiredlocation accuracy to the highest possible settings (this is kCLLocationAccuracyBest,and I always do so), unless you make multiple requests, you might miss out on catchingthe best possible location.

The cost to this is time.A location request may take 10 or 15 seconds.Working withmultiple requests, averaging and best-results repetition is best done in the backgroundaway from the GUI.When possible, avoid making your user wait for your program tofinish its location queries.

NoteApple requires that users authorize all location requests. Also, you cannot create turn-by-turn applications according to App Store terms. Keep these limitations in mind when devel-oping your applications.

Recipe: Core Location in a NutshellCore Location is easy to use. Get started by following these simple steps.They walk youthrough the process of setting up your program to request latitude and longitude:

1. Add the Core Location framework. Drag it into your Xcode project and addit to the Frameworks folder in the Groups & Files column.

2. Use the proper include files. You need to include both CoreLocation.h andCLLocationManagerDelegate.h:#import <CoreLocation/CoreLocation.h>

#import <CoreLocation/CLLocationManagerDelegate.h>

3. Allocate a location manager. Set its delegate to your primary view controllerand set it to use the best accuracy available.locmanager = [[CLLocationManager alloc] init];

[locmanager setDelegate:self];

[locmanager setDesiredAccuracy:kCLLocationAccuracyBest];

4. Start locating. Tell the location manager to start updating the location. Delegatecallbacks will let you know when the location has been found.This can take manyseconds or up to a minute:[locmanager startUpdatingLocation];

5. Handle the location events. You’ll deal with two types of callbacks: successesthat return CLLocation data and failures that do not.Add the delegate methodsthat handle each situation. In Recipe 9-4, the success callback recovers the latitudeand longitude information and launches Google Maps to display that position.Depending on your requested accuracy, you may get three or four location call-backs based on the various location methods used and the requested accuracy, sotake this nonlinearity into account.

280 Chapter 9 People, Places, and Things

Page 304: I Phone Developer Cookbook

6. Repeat. If you can afford the time—location requests can take 10 or 20 secondseach—consider repeating the location several times to recover the best possibleaccuracy.The location information returns both horizontal and vertical accuracymeasures that you can use to evaluate how accurate the positioning was.

Recipe 9-4 Using Core Location to Retrieve Latitude and Longitude

#import <CoreLocation/CoreLocation.h>

#import <CoreLocation/CLLocationManagerDelegate.h>

@interface HelloController : UIViewController <CLLocationManagerDelegate>

{

UITextView *contentView;

CLLocationManager *locmanager;

BOOL isLocating;

BOOL wasFound;

}

@end

@implementation HelloController

// Perform a location request – either start or stop locating

- (void) doIt

{

if (isLocating)

{

[contentView setText:@"Scanning ended by request."];

[locmanager stopUpdatingLocation];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Find Me"

style:UIBarButtonItemStylePlain

target:self

action:@selector(doIt)]autorelease];

} else {

wasFound = NO;

[contentView setText:@"Scanning for location..."];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Stop"

style:UIBarButtonItemStylePlain

target:self

action:@selector(doIt)]autorelease];

[locmanager startUpdatingLocation];

}

isLocating = !isLocating;

}

281Recipe: Core Location in a Nutshell

Page 305: I Phone Developer Cookbook

Recipe 9-4 Continued

- (void)locationManager:(CLLocationManager *)manager➥didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation➥*)oldLocation

{

// respond to the first location callback only

if (wasFound) return;

wasFound = YES;

// Log the accuracy

NSLog(@"%f %f", [newLocation horizontalAccuracy], [newLocation➥verticalAccuracy]);

// Location has been found. Create Google Maps URL

CLLocationCoordinate2D loc = [newLocation coordinate];

NSString *mapString = [NSString stringWithFormat:➥ @"http://maps.google.com/maps?q=%f,%f", loc.latitude, loc.longitude];

NSURL *url = [NSURL URLWithString:mapString];

// Switch to Safari and display that map

[[UIApplication sharedApplication] openURL:url];

}

- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError➥*)error

{

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Find Me"

style:UIBarButtonItemStylePlain

target:self

action:@selector(doIt)]autorelease];

[contentView setText:@"Location search failed"];

isLocating = NO;

}

- (void)loadView

{

contentView = [[UITextView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setEditable:NO];

self.view = contentView;

[contentView release];

locmanager = [[CLLocationManager alloc] init];

[locmanager setDelegate:self];

[locmanager setDesiredAccuracy:kCLLocationAccuracyBest];

282 Chapter 9 People, Places, and Things

Page 306: I Phone Developer Cookbook

Recipe 9-4 Continued

isLocating = NO;

if (!locmanager.locationServicesEnabled)

{

[contentView setText:@"Location Services Are Not Enabled"];

return;

}

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Find Me"

style:UIBarButtonItemStylePlain

target:self

action:@selector(doIt)]autorelease];

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

Recipe: Reverse Geocoding to an AddressThe phrase reverse geocoding means transforming latitude and longitude information intohuman-recognizable address information.Yahoo’s ZoneTag service provides free, limitedreverse geocoding for noncommercial use.To get started, sign up for a developer ID athttp://developer.yahoo.com/yrb/zonetag/.

Once you have your ID (aka application token), you can call Yahoo! with the latitudeand longitude and recover the country, state, neighborhood, and Zip Code for that loca-tion.The information is not highly accurate (you won’t get a street-level address), but it’sa free and open API. Recipe 9-5 demonstrates how to download ZoneTag XML dataand use the iPhone’s NSXMLParser class to recover location information.

Bear in mind that the Yahoo! API is subject to change. So keep checking in withZoneTag to see whether any details have been updated over time. During the writing ofthis book, ZoneTag started returning the developer ID before the XML text.As you cansee in Recipe 9-5, this forced the client to skip ahead the length of the ID tag beforereaching actual XML.

283Recipe: Reverse Geocoding to an Address

Page 307: I Phone Developer Cookbook

NoteZoneTag also returns “tags,” information about local events, restaurants, and so forth. I’vefound these tags to be of limited use, because the service is still quite new and sparselypopulated. As this book went to press, Yahoo! announced major changes in the DeveloperAPIs. ZoneTag will be incorporated into Yahoo!’s new FireEagle service and the API willchange accordingly.

Recipe 9-5 Reverse Geocoding with ZoneTag

@interface XMLParser : NSObject{

NSString *current; // the current tagNSMutableString *outstring; // results

}@end

@implementation XMLParser

// Entry point for requesting the data from the URL- (NSString *)parseXMLFile: (NSURL *) url{

outstring = [[NSMutableString alloc] init];

NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];[parser setDelegate:self];[parser parse];[parser release];

return [outstring autorelease];}

// Entry point for parsing XML data directly- (NSString *)parseXMLData: (NSData *) data{

outstring = [[NSMutableString alloc] init];

NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];[parser setDelegate:self];[parser parse];[parser release];

return [outstring autorelease];}

// Parse an open XML Tag- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName➥namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName ➥attributes:(NSDictionary *)attributeDict

284 Chapter 9 People, Places, and Things

Page 308: I Phone Developer Cookbook

Recipe 9-5 Continued

{

if (elementName)

current = [NSString stringWithString:elementName];

}

// Parse the close XML tag

- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName➥namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName

{

if (current) [current release];

current = NULL;

}

// On finding data, test to see if the container is recognized

- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string

{

if (!current) return;

if ([current isEqualToString:@"country"] ||

[current isEqualToString:@"state"] ||

[current isEqualToString:@"zipcode"] ||

[current isEqualToString:@"neighbourhood"])

[outstring appendFormat:@"%@: %@\n", current, string];

}

- (void) dealloc

{

[current release];

[outstring release];

[super dealloc];

}

@end

#define APPTOKEN @"YOUR ZONE TAG DEVELOPER ID HERE"

// ZoneTag method from the calling view controller

- (void)locationManager:(CLLocationManager *)manager➥didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation➥*)oldLocation

{

// Respond only to the first location

if (wasFound) return;

wasFound = YES;

self.navigationItem.rightBarButtonItem = NULL;

CLLocationCoordinate2D loc = [newLocation coordinate];

[contentView setText:@"Location found. Looking up information via Yahoo."];

285Recipe: Reverse Geocoding to an Address

Page 309: I Phone Developer Cookbook

Recipe 9-5 Continued

// Please use your own Yahoo Developer Token

if ([APPTOKEN isEqualToString:@"YOUR TOKEN HERE"])

{

[contentView setText:@"You need to supply an application token to use➥Yahoo services"];

return;

}

// Contact Yahoo! and make a local info request

NSString *requestString = [NSString➥stringWithFormat:@"http://zonetag.research.yahooapis.com/services/rest/V1/➥suggestedTags.php?apptoken=%@&latitude=%f&longitude=%f&output=xml",➥APPTOKEN, loc.latitude, loc.longitude];

NSURL *url = [NSURL URLWithString:requestString];

NSString *string = [[NSString stringWithContentsOfURL:url]➥substringFromIndex:[APPTOKEN length]];

[contentView setText:[[XMLParser sharedInstance] parseXMLData:[string➥dataUsingEncoding:NSUTF8StringEncoding]]];

}

Recipe: Accessing Maps Using Core Location DataIn addition to ZoneTag’s reverse geocoding,Yahoo! provides developer-friendly ways togenerate and download maps for use in your applications. Recipe 9-6 uses Yahoo’s Mapsservice to download a map centered on the coordinates returned by Core Location. Itthen loads that image onto a Web view and displays it, enabling users to interactivelyscroll and zoom into the map, as shown in Figure 9-3.

This code uses a two-level approach.The first request returns XML containing aURL for the requested map.An NSScanner instance helps navigate through that simpleXML to locate the map URL.

A second request downloads the map data into an NSData object, which is stored tofile and then used to feed a UIWebView.

If you’d rather save the data to your iPhone photo album instead of displaying it in aWeb view, first create an image with [UIImage imageWithData:imgData] and thencall UIImageWriteToSavedPhotosAlbum(image, NULL, NULL, NULL). If you wantto write the data out as a JPEG image rather than your Documents folder, useUIImageJPEGRepresentation(image, 1.0f) to create a version of the NSData inJPEG format and then save the NSData to file.

286 Chapter 9 People, Places, and Things

Page 310: I Phone Developer Cookbook

NoteThe NSScanner class provides an expedient way to scan through strings. Although not aselegant as Perl-based solutions, it enables you to recover data by matching your stringagainst substrings.

Recipe 9-6 Downloading Map Data from Yahoo

#define APPTOKEN @"YOUR ZONE TAG DEVELOPER ID HERE"

- (void)locationManager:(CLLocationManager *)manager➥didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation➥*)oldLocation

{

// Respond only to the first location

if (wasFound) return;

wasFound = YES;

self.navigationItem.rightBarButtonItem = NULL;

CLLocationCoordinate2D loc = [newLocation coordinate];

[contentView setText:@"Location found. Looking up information via Yahoo."];

287Recipe: Accessing Maps Using Core Location Data

Figure 9-3 This Yahoo! map was generated from the latitude andlongitude values returned by Core Location. On the Simulator, your locationalways defaults to 1 Infinite Loop, in Apple’s headquarters in Cupertino, CA.

Page 311: I Phone Developer Cookbook

Recipe 9-6 Continued

if ([APPTOKEN isEqualToString:@"YOUR TOKEN HERE"])

{

[contentView setText:@"You need to supply an application token to use➥Yahoo services"];

return;

}

// Contact Yahoo! and make a local info request

NSString *requestString = [NSString➥stringWithFormat:@"http://local.yahooapis.com/MapsService/V1/➥mapImage?appid=%@&latitude=%f&longitude=%f", APPTOKEN, loc.latitude,➥loc.longitude];

// Retrieve the direct map location from the request URL

NSURL *url = [NSURL URLWithString:requestString];

NSString *directLocation = [NSString stringWithContentsOfURL:url];

// Scan through XML for the map URL

NSMutableString *mapURLString;

NSScanner *scanner = [NSScanner scannerWithString:directLocation];

[scanner scanUpToString:@">http://" intoString:NULL]; // first

[scanner scanUpToString:@"http://" intoString:NULL]; // second

[scanner scanUpToString:@"<" intoString:&mapURLString];

// Retrieve the image

url = [NSURL URLWithString:mapURLString];

UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];

NSData *imgData = UIImageJPEGRepresentation(img, 1.0f);

// Display it on a Web view

UIWebView *wv = [[UIWebView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView addSubview:wv];

[wv loadData:imgData MIMEType:@"image/jpeg" textEncodingName:NULL➥baseURL:NULL];

[wv setScalesPageToFit:YES];

self.view = wv;

}

Recipe: Accessing Core Device InformationThe UIDevice class enables you to recover key device-specific values, including theiPhone or iPod touch model being used, the device name, and the OS name and version.As Recipe 9-7 shows, it’s a one-stop solution for pulling out system details.Unfortunately, it’s not a complete or exhaustive list, and Apple has disabled direct access

288 Chapter 9 People, Places, and Things

Page 312: I Phone Developer Cookbook

to your iPhone’s IORegistry even though IOKit remains a “public” (rather than private) framework.This keeps you from accessing what Apple would probably considermore sensitive information, such as an iPhone’s IMEI, the international mobile equip-ment identifier used to identify the phone to its carrier network.

NoteUse the iPhone’s unique identifier to register devices for Ad Hoc application distribution.

Recipe 9-7 Using the UIDevice Class

- (void) getDeviceInfo

{

results = [[NSMutableString alloc] init];

[results appendFormat:@"Identifier:\n %@\n", [[UIDevice currentDevice]➥uniqueIdentifier]];

[results appendFormat:@"Model: %@\n", [[UIDevice currentDevice] model]];

[results appendFormat:@"Localized Model: %@\n", [[UIDevice currentDevice]➥localizedModel]];

[results appendFormat:@"Name: %@\n", [[UIDevice currentDevice] name]];

[results appendFormat:@"System Name: %@\n", [[UIDevice currentDevice]➥systemName]];

[results appendFormat:@"System Version: %@\n", [[UIDevice currentDevice]➥systemVersion]];

[contentView setText:results];

}

Recipe: Enabling and Disabling the Proximity SensorUnless you have some pressing reason to hold an iPhone against body parts (or viceversa), enabling the proximity sensor accomplishes little.When enabled, it does onething. It detects if there’s a large object right in front of it. If so, it switches the screenoff. Move the blocking object away and the screen switches back on.

Recipe 9-8 demonstrates how to enable proximity sensing on the iPhone. It sends asetProximitySensingEnabled: message to the shared UIApplication instance. SetYES to enable this feature, NO to disable it.

Recipe 9-8 Enabling Proximity Sensing

- (void) enable

{

[[UIApplication sharedApplication] setProximitySensingEnabled:YES];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Disable Proximity"

289Recipe: Enabling and Disabling the Proximity Sensor

Page 313: I Phone Developer Cookbook

Recipe 9-8 Continued

style:UIBarButtonItemStylePlain

target:self

action:@selector(disable)]autorelease];

}

Recipe: Using Acceleration to Locate “Up”The iPhone provides three onboard sensors that measure acceleration along the iPhone’sperpendicular axes; that is, left/right (X), up/down (Y), and front/back (Z).These valuesindicate the forces affecting the iPhone, from both gravity and user movement.

NoteYou can get some really neat force feedback by swinging the iPhone around your head (centripetal force) or dropping it from a tall building (freefall). Unfortunately, you might notbe able to recover that data after your iPhone becomes an expensive bit of scrap metal.

To subscribe an application to iPhone orientation notification, call[[UIAccelerometer sharedAccelerometer] setDelegate:self] from one ofyour view controllers.After, your delegate will receive accelerometer:didAccelerate: messages, which you can track and respond to.

290 Chapter 9 People, Places, and Things

Figure 9-4 A little math recovers the “up” direction by performing an arctan function using the x and y force vectors. In this sample, the arrow

always points up, no matter how the user reorients the iPhone.

Page 314: I Phone Developer Cookbook

The UIAcceleration object sent to this method returns a floating-point value forthe X, Y, and Z axes.This value ranges from -1.0 to 1.0. Recipe 9-9 uses these values todetermine which way is “up,” by returning the arctangent of the x and y vectors.As newacceleration messages are handled, the method rotates a UIImageView with its picture ofan arrow, which you can see in Figure 9-4, to point up.The real-time response to useractions ensures that the arrow continues pointing upward, no matter how the user reori-ents the phone.

Recipe 9-9 Catching Acceleration Events

#import <math.h>

@interface HelloController : UIViewController <UIAccelerometerDelegate>

{

UIImageView *contentView;

UIImageView *arrowView;

}

@end

@implementation HelloController

- (void)accelerometer:(UIAccelerometer *)accelerometer➥didAccelerate:(UIAcceleration *)acceleration

{

float xx = -[acceleration x];

float yy = [acceleration y];

float angle = atan2(yy, xx);

[arrowView setTransform:CGAffineTransformMakeRotation(angle)];

}

- (void)loadView

{

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

self.view = contentView;

[contentView release];

arrowView = [[UIImageView alloc] initWithImage:[UIImage➥imageNamed:@"arrow.png"]];

[arrowView setCenter:CGPointMake(160.0f, 208.0f)];

[contentView addSubview:arrowView];

[arrowView release];

[[UIAccelerometer sharedAccelerometer] setDelegate:self];

}

291Recipe: Using Acceleration To Locate “Up”

Page 315: I Phone Developer Cookbook

Recipe 9-9 Continued

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

Recipe: Using Acceleration to Move Onscreen ObjectsWith a bit of clever programming, the iPhone’s onboard accelerometer can make objects“move” around the screen, responding in real time to the way the user tilts the phone.Recipe 9-10 builds an animated butterfly that users can slide across the screen.Figure 9-5 shows what the screen looks like as the application runs and the user interac-tively controls the butterfly’s location.

292 Chapter 9 People, Places, and Things

Figure 9-5 Recipe 9-10 enables you to slide this butterflyaround the screen by tilting the iPhone up, down, and sideways.

Page 316: I Phone Developer Cookbook

The secret to making this work lies in adding what I call a “physics timer” to the program. Instead of responding to changes in acceleration, the way Recipe 9-9 did, theaccelerometer callback does nothing more than measure the current forces. It’s up to thetimer routine to apply those forces to the butterfly:

n As long as the direction of force remains the same, the butterfly accelerates. Itsvelocity increases, scaled according to the degree of acceleration force in the X orY direction.

n The tick routine, called by the timer, moves the butterfly by adding the velocityvector to the butterfly’s origin.

n The butterfly’s range is bounded. So when it hits an edge, it stops moving in thatdirection.This keeps the butterfly onscreen at all times.

Recipe 9-10 Sliding an Object Based on Accelerometer Feedback

#import <math.h>

@interface HelloController : UIViewController <UIAccelerometerDelegate>

{

UIImageView *contentView;

UIImageView *butterfly;

float xaccel, yaccel, xvelocity, yvelocity;

}

@end

@implementation HelloController

- (void) tick

{

// Move the butterfly according to the current velocity vector

CGRect rect = [butterfly frame];

rect.origin.x += xvelocity;

rect.origin.y += yvelocity;

// Check for boundary conditions so the butterfly stays on-screen

if (rect.origin.x > 260.0f) rect.origin.x = 260.0f;

if (rect.origin.x < 0.0f) rect.origin.x = 0.0f;

if (rect.origin.y > 356.0f) rect.origin.y = 356.0f;

if (rect.origin.y < 0.0f) rect.origin.y = 0.0f;

[butterfly setFrame:rect];

}

#define SIGN(x) ((x < 0.0f) ? -1.0f : 1.0f)

- (void)accelerometer:(UIAccelerometer *)accelerometer➥didAccelerate:(UIAcceleration *)acceleration

{

293Recipe: Using Acceleration to Move Onscreen Objects

Page 317: I Phone Developer Cookbook

Recipe 9-10 Continued

// extract the acceleration components

float xx = -[acceleration x];

float yy = [acceleration y];

// Has the direction changed?

float accelDirX = SIGN(xvelocity) * -1.0f;

float newDirX = SIGN(xx);

float accelDirY = SIGN(yvelocity) * -1.0f;

float newDirY = SIGN(yy);

// Accelerate. To increase viscosity, lower the values below 1.0f

if (accelDirX == newDirX)

xaccel = (abs(xaccel) + 0.85f) * SIGN(xaccel);

if (accelDirY == newDirY)

yaccel = (abs(yaccel) + 0.85f) * SIGN(yaccel);

// Apply acceleration changes to the current velocity

xvelocity = -xaccel * xx;

yvelocity = -yaccel * yy;

}

- (void)loadView

{

contentView = [[UIImageView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame]];

[contentView setImage:[UIImage imageNamed:@"bluedots.png"]];

self.view = contentView;

[contentView release];

// Create the butterfly

butterfly = [[UIImageView alloc] initWithFrame:CGRectMake(40.0f, 300.0f,➥60.0f, 60.0f)];

[contentView addSubview:butterfly];

[butterfly release];

// Load the animation cells

NSMutableArray *bflies = [[NSMutableArray alloc] init];

for (int i = 1; i <= 17; i++) {

NSString *cname = [NSString stringWithFormat:@"bf_%d.png", i];

UIImage *img = [UIImage imageNamed:cname];

if (img) [bflies addObject:img];

[cname release];

}

// Begin the animation

[butterfly setAnimationImages:bflies];

294 Chapter 9 People, Places, and Things

Page 318: I Phone Developer Cookbook

Recipe 9-10 Continued

butterfly.animationDuration = 0.75f;

[butterfly startAnimating];

[bflies release];

// Center the butterfly

[butterfly setCenter:CGPointMake(160.0f, 208.0f)];

// Set the butterfly’s initial speed and acceleration

xaccel = 2.0f;

yaccel = 2.0f;

xvelocity = 0.0f;

yvelocity = 0.0f;

// Activate the accelerometer

[[UIAccelerometer sharedAccelerometer] setDelegate:self];

// Start the physics timer

[NSTimer scheduledTimerWithTimeInterval: 0.03f

target: self

selector: @selector(tick)

userInfo: nil

repeats: YES];

}

-(void) dealloc

{

[contentView release];

[super dealloc];

}

@end

SummaryThis chapter introduced core functionality including the Address Book, Core Location,and onboard sensors.These features differentiate the iPhone and iPod touch from mostother handheld platforms currently available on the market. Here are a few partingthoughts about the frameworks you just encountered:

n Although extremely useful, the low-level Address Book functions can prove frus-trating to work with because of Apple’s poor documentation and the complex waythey approach that “multivalued” data. It helps to take these routines slowly andrefer directly to the framework headers to supplement your knowledge about howthe various types and records work.

295Summary

Page 319: I Phone Developer Cookbook

n While keeping your iPhone absolutely still, you can still get varying results whencalling Core Location based on the success or failure of its various positioningalgorithms. Checking your location is not like checking the time.There’s morevariability involved.

n Compared to Google,Yahoo! is new to the social networking and geolocationworld, and its developer resources reflect its commitment to building in that area.Don’t overlook its developer APIs and its new location-aware services such asFireEagle and Upcoming.org.

n The iPhone’s accelerometer provides a novel way to complement its touch-basedinterface. Use acceleration data to expand user interactions beyond the “touchhere” basics and to introduce tilt-aware feedback.

296 Chapter 9 People, Places, and Things

Page 320: I Phone Developer Cookbook

10Connecting to Services

As an Internet-connected device, the iPhone is particularly suited to subscribing toWeb-based services.Apple has lavished the platform with a solid grounding in all kinds ofnetwork computing and its supporting technologies.The iPhone SDK handles sockets,password keychains, SQL access, XML processing, and more.This chapter surveys commontechniques for network computing, offering recipes that simplify day-to-day tasks.

Recipe: Adding Custom Settings BundlesAny iPhone application can add preferences into the main Settings screen (see Figure 10-1). Custom settings are listed after system settings, but otherwise look and actlike the ones that Apple preloaded into your system.As these screenshots show, custompreferences provide a variety of data interaction styles, including text fields, switches, andsliders.

Application-based settings create standard NSUserDefaults entries, which you canquery from your program. Use settings to store nonsensitive account preferences such asusernames and option toggles.Although passwords are visually obscured with dots,they’re stored in clear text in your application sandbox.When working with more sensi-tive information, consider using your iPhone’s secure keychain. Keychain recipes appearlater in this chapter.

Declaring Application SettingsA copy of the settings schema resides in your Developer folder at /Developer/Platforms/iPhoneOS.platform/Developer/Library/Xcode/Plug-ins/ iPhoneSettingsPlistStructDefs.xcodeplugin. Xcode uses this file to check property list syntax. In it, you can see all thedefinitions and the required and optional attributes used to specify custom preferences.

Each settings pane corresponds to one property list file. Recipe 10-1 shows thesource for the pane in Figure 10-1 (right). It demonstrates each SDK settings type andits definition.Types include text fields (strings), sliders (floating-point numbers), switches(Boolean values), and multiple selection (one-of-n choices). In addition, you can groupitems and link to child panes.

Page 321: I Phone Developer Cookbook

To add new settings, build a dictionary and add it to the PreferencesSpecifiersarray. Each individual preference dictionary needs, at a minimum, a type and a title.Some settings, like the PSGroupSpecifier group item, require nothing more to work.Others, such as text fields, use quite a few properties.You’ll want to specify capitalizationand autocorrection behaviors as well as the keyboard type and whether the passwordsecurity feature obscures text, as you can see in Recipe 10-1.

To add a settings bundle to your program, follow these steps:

1. Create each of the property lists, one for each screen.The primary plist must benamed Root.plist.

2. Create a new folder and add your property lists.

3. Rename the folder to Settings.bundle. OS X warns you about the name; goahead and confirm the rename.The folder transforms into a bundle. (To view thecontents of your new bundle, right-click [Control-click] and choose ShowPackage Contents from the contextual pop-up.)

4. Drag the bundle into the Groups & File column of your Xcode project (seeFigure 10-2).

298 Chapter 10 Connecting to Services

Figure 10-1 (Left) Custom settings bundles appear on the Settingsscreen after the built-in settings, at the end of the list. On the iPhone, you

may have to scroll down a bit to find them. (Right) Developer-defined preferences elements can include text fields (both regular and secure),

switches, sliders, multivalue choices, group titles, and child panes.

Page 322: I Phone Developer Cookbook

NoteIn Recipe 10-1, the File property uses no extension; the .plist extension is understood.

Recipe 10-1 Creating a Custom Settings Pane

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>Title</key>

<string>YOUR_PROJECT_NAME</string>

<key>StringsTable</key>

When you next run your program, the settings bundle installs and makes itself available to the Settings application. Should your source have any syntax errors, you’llfind a blank screen rather than the settings you expect. It helps to build your settings instages to avoid this.

Xcode offers a limited interactive syntactically aware editing window. Open the property list and then choose View, Property List Type, iPhone Settings plist. I find it easier to edit by hand in TextEdit or the stand-alone Property List Editor.Your toolcomfort may differ from this.

299Recipe: Adding Custom Settings Bundles

Figure 10-2 Add the Settings.bundle into your project’s Groups & Fileslist. Double-click the property lists to edit them further in Xcode.

Page 323: I Phone Developer Cookbook

Recipe 10-1 Continued

<string>Root</string>

<key>PreferenceSpecifiers</key>

<array>

<dict>

<key>Type</key>

<string>PSGroupSpecifier</string>

<key>Title</key>

<string>Group</string>

</dict>

<dict>

<key>Type</key>

<string>PSTextFieldSpecifier</string>

<key>Title</key>

<string>Name</string>

<key>Key</key>

<string>name_preference</string>

<key>DefaultValue</key>

<string></string>

<key>IsSecure</key>

<false/>

<key>KeyboardType</key>

<string>Alphabet</string>

<key>AutocapitalizationType</key>

<string>None</string>

<key>AutocorrectionType</key>

<string>No</string>

</dict>

<dict>

<key>Type</key>

<string>PSTextFieldSpecifier</string>

<key>Title</key>

<string>Password</string>

<key>Key</key>

<string>prefs_preference</string>

<key>DefaultValue</key>

<string></string>

<key>IsSecure</key>

<true/>

<key>KeyboardType</key>

<string>Alphabet</string>

<key>AutocapitalizationType</key>

<string>None</string>

<key>AutocorrectionType</key>

<string>No</string>

</dict>

300 Chapter 10 Connecting to Services

Page 324: I Phone Developer Cookbook

Recipe 10-1 Continued

<dict>

<key>Type</key>

<string>PSToggleSwitchSpecifier</string>

<key>Title</key>

<string>Enabled</string>

<key>Key</key>

<string>enabled_preference</string>

<key>DefaultValue</key>

<true/>

<key>TrueValue</key>

<string>YES</string>

<key>FalseValue</key>

<string>NO</string>

</dict>

<dict>

<key>Type</key>

<string>PSSliderSpecifier</string>

<key>Key</key>

<string>slider_preference</string>

<key>DefaultValue</key>

<real>0.5</real>

<key>MinimumValue</key>

<integer>0</integer>

<key>MaximumValue</key>

<integer>1</integer>

<key>MinimumValueImage</key>

<string></string>

<key>MaximumValueImage</key>

<string></string>

</dict>

<dict>

<key>Type</key>

<string>PSMultiValueSpecifier</string>

<key>Key</key>

<string>multi_preference</string>

<key>DefaultValue</key>

<string>One</string>

<key>Title</key>

<string>MultiValue</string>

<key>Titles</key>

<array>

<string>one</string>

<string>two</string>

<string>three</string>

301Recipe: Adding Custom Settings Bundles

Page 325: I Phone Developer Cookbook

Recipe 10-1 Continued

<string>four</string>

</array>

<key>Values</key>

<array>

<string>one</string>

<string>two</string>

<string>three</string>

<string>four</string>

</array>

</dict>

<dict>

<key>Type</key>

<string>PSGroupSpecifier</string>

<key>Title</key>

<string>Info</string>

</dict>

<dict>

<key>Type</key>

<string>PSChildPaneSpecifier</string>

<key>Title</key>

<string>Legal</string>

<key>File</key>

<string>Legal</string>

</dict>

</array>

</dict>

</plist>

Recipe: Subscribing Applications to Custom URL SchemesCustom schemes enable applications to launch whenever Mobile Safari (or anotherapplication) opens a URL of that type.A URL scheme refers to the first part of theURL that appears before the colon, such as http or ftp. For example, should your appli-cation register xyz, any xyz:// links go directly to your application for handling, wherethey’re passed to the optional application: handleOpenURL: method.

Custom schemes launch applications whether you’ve defined a handler method. If allyou want to do is run an application, adding the scheme and opening the URL enablescross-application launching.

Handlers extend launching to allow applications to do something with the URLthat’s been passed to it.They might open a specific data file, retrieve a particular name, display a certain image, or otherwise process information included in the call.

302 Chapter 10 Connecting to Services

Page 326: I Phone Developer Cookbook

Listing 10-1 shows a simple handler that stores the passed URL into the application’spreferences, presumably to be retrieved by one of the application’s view controllers at alater time.

Listing 10-1 Defining a Typical URL Handler Routine

- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url

{

if (!url) { return NO; }

NSString *URLString = [url absoluteString];

[[NSUserDefaults standardUserDefaults] setObject:URLString forKey:@"url"];

[[NSUserDefaults standardUserDefaults] synchronize];

return YES;

}

Define custom URL schemes in your application’s Info.plist file. Recipe 10-2 shows aproperty list that registers the xyz scheme. It does this by specifying two items in theCFBundleURLTypes array.The first is the CFBundleURLName, which contains an arbi-trary identifier typically using Apple’s standard reversed domain name style.The secondconsists of an array of CFBundleURLSchemes, declared as strings.This array contains oneitem, the xyz scheme.

When the iPhone installs the application, the Info.plist list tells SpringBoard to associ-ate that application with the xyz scheme.Thereafter, whenever SpringBoard encountersa matching scheme, the proper application launches to handle the URL.

Recipe 10-2 Registering Custom URL Schemes

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN""http://www.apple.com/DTDs/PropertyList-1.0.dtd">

<plist version="1.0">

<dict>

<key>CFBundleDevelopmentRegion</key>

<string>en</string>

<key>CFBundleDisplayName</key>

<string>${PRODUCT_NAME}</string>

<key>CFBundleExecutable</key>

<string>${EXECUTABLE_NAME}</string>

<key>CFBundleIconFile</key>

<string>Icon.png</string>

<key>CFBundleIdentifier</key>

<string>com.sadun.${PRODUCT_NAME:identifier}</string>

<key>CFBundleInfoDictionaryVersion</key>

<string>6.0</string>

<key>CFBundleName</key>

303Recipe: Subscribing Applications to Custom URL Schemes

Page 327: I Phone Developer Cookbook

Recipe 10-2 Continued

<string>${PRODUCT_NAME}</string>

<key>CFBundlePackageType</key>

<string>APPL</string>

<key>CFBundleSignature</key>

<string>????</string>

<key>CFBundleVersion</key>

<string>1.0</string>

<key>CFBundleURLTypes</key>

<array>

<dict>

<key>CFBundleURLName</key>

<string>com.sadun.demonstration</string>

<key>CFBundleURLSchemes</key>

<array>

<string>xyz</string>

</array>

</dict>

</array>

</dict>

</plist>

Recipe: Checking Your Network StatusWhen working with networks, you can check your connection status and recover infor-mation about your iPhone’s role in that network.The next few listings demonstrate howto test network status, recover the iPhone’s IP address, look up a site’s IP address, anddetermine whether a site is reachable.

Testing the Network StatusThe System Configuration framework offers many networking aware functions.Amongthese, SCNetworkReachabilityCreateWithAddress enables you to check whether anIP address is reachable. Pass a zeroed out address (for example, 0.0.0.0) to query yournetwork status. Listing 10-2 shows this test in action.This method, courtesy of Applesample code, returns YES when the network is available and NO otherwise.The returnedflags must indicate both that the network is reachable (kSCNetworkFlagsReachable)and that no further connection is required (kSCNetworkFlagsConnectionRequired).Other flags you may use are as follows:

n kSCNetworkReachabilityFlagsIsWWAN tests whether your user is using the car-rier’s network or local WiFi.When set to YES, the network can be reached viaEDGE, GPRS, or another cell connection.

304 Chapter 10 Connecting to Services

Page 328: I Phone Developer Cookbook

n kSCNetworkReachabilityFlagsIsDirect tells you whether the network trafficgoes through a gateway or arrives directly.

NoteToggle airplane mode off and on to test this code with your network enabled and disabled.

Listing 10-2 Checking the iPhone’s Network Connection Status

- (BOOL) connectedToNetwork

{

// Create zero addy

struct sockaddr_in zeroAddress;

bzero(&zeroAddress, sizeof(zeroAddress));

zeroAddress.sin_len = sizeof(zeroAddress);

zeroAddress.sin_family = AF_INET;

// Recover reachability flags

SCNetworkReachabilityRef defaultRouteReachability =➥SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr*)&zeroAddress);

SCNetworkReachabilityFlags flags;

BOOL didRetrieveFlags =➥SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);

CFRelease(defaultRouteReachability);

if (!didRetrieveFlags)

{

printf("Error. Could not recover network reachability flags\n");

return 0;

}

BOOL isReachable = flags & kSCNetworkFlagsReachable;

BOOL needsConnection = flags & kSCNetworkFlagsConnectionRequired;

return (isReachable && !needsConnection) ? YES : NO;

}

Recovering a Local IP AddressEarly on, developers James “core” Cuff and Jay “saurik” Freeman discovered that theiPhone’s hostname lookup features were irregular.Although newer iPhone firmwarereleases have fixed many bugs, the iPhone remains quirky when it comes to recoveringthe iPhone’s hostname. Unlike the simulator and its Mac OS X host, the iPhone doesnot automatically add a local suffix to the iPhone’s hostname.

305Recipe: Checking Your Network Status

Page 329: I Phone Developer Cookbook

Listing 10-3 demonstrates how to retrieve the local IP address for your iPhone.This isa handy routine to have on hand when you’re establishing networked services from theiPhone, such as an FTP server or HTTP daemon.This code appends the missing-in-action local suffix, and, once the name is corrected, uses gethostbyname to return avalid host entity.After, inet_ntoa() extracts the IP address from that host.

NoteThis code does not work on the Simulator. (The Simulator correctly adds the .local suffixautomatically.) Run it only in iPhone- and iPod touch-specific code.

Listing 10-3 Obtaining the iPhone’s Local IP Address

// Return the localized IP address

- (NSString *) localIPAddress

{

char baseHostName[255];

gethostname(baseHostName, 255);

char hn[255];

// This adjusts for iPhone by adding .local to the host name

sprintf(hn, "%s.local", baseHostName);

struct hostent *host = gethostbyname(hn);

if (host == NULL)

{

herror("resolv");

return NULL;

}

else {

struct in_addr **list = host->h_addr_list;

return [NSString stringWithCString:inet_ntoa(*list[0])];

}

return NULL;

}

Querying Site IP AddressesThe iPhone supports standard gethostbyname calls to look up IP addresses for remotesites.The method shown in Listing 10-4 accepts a site name (for example, yahoo.com orwww.google.com) and returns the first available IP address for that site.As you can see,the routine to do this is standard.There are no iPhone-specific quirks to work around.

306 Chapter 10 Connecting to Services

Page 330: I Phone Developer Cookbook

Listing 10-4 Looking Up IP Address by Hostname

- (NSString *) getIPAddressForHost: (NSString *) theHost

{

struct hostent *host = gethostbyname([theHost UTF8String]);

if (host == NULL) {

herror("resolv");

return NULL;

}

struct in_addr **list = (struct in_addr **)host->h_addr_list;

NSString *addressString = [NSString stringWithCString:inet_ntoa(*list[0])];

return addressString;

}

Checking Site AvailabilityAfter recovering a site’s IP address, use the sameSCNetworkReachabilityCreateWithAddress function to check its availability thatyou use to check whether a site can be reached. Pass a sockaddr record populated with the site’s IP address (thank you,Apple, for providing the method that does this),and then check for the kSCNetworkFlagsReachable flag when the function returns.Recipe 10-3 shows the site checking hostAvailable: method. It returns YES or NO.

Recipe 10-3 Checking Whether a Site Is Reachable

- (BOOL) hostAvailable: (NSString *) theHost

{

NSString *addressString = [self getIPAddressForHost:theHost];

if (!addressString)

{

printf("Error recovering IP address from host name\n");

return NO;

}

struct sockaddr_in address;

BOOL gotAddress = [self addressFromString:addressString address:&address];

if (!gotAddress)

{

printf("Error recovering sockaddr address from %s\n", [addressString➥UTF8String]);

return NO;

}

307Recipe: Checking Your Network Status

Page 331: I Phone Developer Cookbook

Recipe 10-3 Continued

SCNetworkReachabilityRef defaultRouteReachability =➥SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&address);

SCNetworkReachabilityFlags flags;

BOOL didRetrieveFlags =➥SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags);

CFRelease(defaultRouteReachability);

if (!didRetrieveFlags)

{

printf("Error. Could not recover network reachability flags\n");

return NO;

}

BOOL isReachable = flags & kSCNetworkFlagsReachable;

return isReachable ? YES : NO;;

}

// Populating a sockaddr_in record.

// Direct from Apple. Thank you Apple

- (BOOL)addressFromString:(NSString *)IPAddress address:(struct sockaddr_in *)➥address

{

if (!IPAddress || ![IPAddress length]) {

return NO;

}

memset((char *) address, sizeof(struct sockaddr_in), 0);

address->sin_family = AF_INET;

address->sin_len = sizeof(struct sockaddr_in);

int conversionResult = inet_aton([IPAddress UTF8String], &address->sin_addr);

if (conversionResult == 0) {

NSAssert1(conversionResult != 1, @"Failed to convert the IP address string➥into a sockaddr_in: %@", IPAddress);

return NO;

}

return YES;

}

Recipe: Interacting with iPhone DatabasesDatabase storage provides another support mechanism for network access.Although theiPhone offers full sqlite3 interactions, I find the Apple-provided libraries cumbersome.Listing 10-5 shows a sample SQLite access routine from Apple’s SQLiteBooks code.This

308 Chapter 10 Connecting to Services

Page 332: I Phone Developer Cookbook

is a pretty typical method. Note the routines that are used.They’re centered on C-basedlibsql calls, with little Cocoa feel to them.This routine queries SELECT author, copy-right FROM book WHERE pk=? against its database.

Listing 10-5 Sample SQL Access from Apple’s SQLiteBooks Example

// Brings the rest of the object data into memory. If already in memory, no action➥is taken (harmless no-op).

- (void)hydrate {

// Check if action is necessary.

if (hydrated) return;

// Compile the hydration statement, if needed.

if (hydrate_statement == nil) {

const char *sql = "SELECT author, copyright FROM book WHERE pk=?";

if (sqlite3_prepare_v2(database, sql, -1, &hydrate_statement, NULL) !=➥SQLITE_OK) {

NSAssert1(0, @"Error: failed to prepare statement with message '%s'.",➥sqlite3_errmsg(database));

}

}

// Bind the primary key variable.

sqlite3_bind_int(hydrate_statement, 1, primaryKey);

// Execute the query.

int success =sqlite3_step(hydrate_statement);

if (success == SQLITE_ROW) {

char *str = (char *)sqlite3_column_text(hydrate_statement, 0);

self.author = (str) ? [NSString stringWithUTF8String:str] : @"";

self.copyright = [NSDate dateWithTimeIntervalSince1970:sqlite3_column_➥double(hydrate_statement, 1)];

} else {

// The query did not return

self.author = @"Unknown";

self.copyright = [NSDate date];

}

// Reset the query for the next use.

sqlite3_reset(hydrate_statement);

// Update object state with respect to hydration.

hydrated = YES;

}

I much prefer to use Gus Mueller’s FMDB Cocoa wrappers for my database needs(http://gusmueller.com/x/fmdb.zip). FMDB provides sqlite bindings that simplifyiPhone database creation, access, and updates. Recipe 10-3 shows FMDB in action,sending a simple SQLite call as an NSString query ([db executeQuery:@"select *from call"]).

The method shown in Recipe 10-4 lists your recent call history. Compare the relativesimplicity of this code with Listing 10-5, which shows a similar database read-through.

309Recipe: Interacting with iPhone Databases

Page 333: I Phone Developer Cookbook

In Recipe 10-4, the focus remains on the high-level data rather than low-level accesscalls, providing an Objective-C approach to SQL access.

To use FMDB in your programs, download the code from Mueller’s site, add theclasses to your program, and drag the iPhone version of libsqlite3.0.dylib into yourXcode project.The dylib file is located at /Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS2.0.sdk/usr/lib/libsqlite3.0.dylib, along with other linkableiPhone libraries.

NoteAt the time of this writing, the iPhone does not sandbox you from reading your call history.Should this policy change, use this same FMDB approach to access any other readableSQL database.

Recipe 10-4 Accessing the iPhone Call History Using FMDB

- (void) fetchCalls

{

// Open the iPhone call history database

NSString *dbpath = @"/private/var/mobile/Library/CallHistory/call_history.db";

FMDatabase* db = [FMDatabase databaseWithPath:dbpath];

if (![db open]) {

printf("Could not open db.\n");

return;

}

FMResultSet *rs = [db executeQuery:@"select * from call"];

int i = 1; // counter

NSMutableString *outstring = [[NSMutableString alloc] init];

while ([rs next]) {

// Recover the call time

int calltime = [rs intForColumn:@"date"];

NSDate *cdate = [NSDate dateWithTimeIntervalSince1970:calltime];

NSString *caldate = [[cdate dateWithCalendarFormat:@"%Y-%m-%d %H:%M:%S"➥timeZone:nil] description];

// Duration in seconds

int secs = [[rs stringForColumn:@"duration"] intValue];

int minutes = (secs / 60) + 1;

// Phone number

NSString *phoneNumber = [rs stringForColumn:@"address"];

310 Chapter 10 Connecting to Services

Page 334: I Phone Developer Cookbook

Recipe 10-4 Continued

[outstring appendFormat:@"%3d: %@ %@ - %3d minutes\n", i++, caldate,➥phoneNumber, minutes];

}

[db close];

[contentView setText:outstring];

[outstring release];

}

Recipe: Converting XML into TreesThe NSXMLParser class provided in the iPhone SDK enables you to scan throughXML, creating callbacks as new elements are processed and finished. In Chapter 9,“People, Places, and Things,” Recipe 9-5 introduced NSXMLParser in its simplest form:looking for leaves and keeping track of the current element.The class is terrific for whenyou’re downloading simple data feeds and want to scrape just a bit or two of relevantinformation. It’s not so great when you’re doing production-type work that relies onerror checking, status information, and back-and-forth handshaking.

Unfortunately, the iPhone SDK relies on NSXMLParser for its core XML handling.So it’s up to you to bridge the gap between NSXMLParser and tree-based parse results. Fortunately, you can easily build an NSXMLParser-based class to return morestandard tree-based data.This requires the kind of simple, standard tree node shown inListing 10-6.A simple double-linked tree node offers a pointer to its parent node, anarray of its children, a key value for the element container and, for leaves, a leaf valuethat stores XML text data from inside the container.

Listing 10-6 Defining a Simple Tree Node for XML Support

@interface TreeNode : NSObject

{

TreeNode *parent;

NSMutableArray *children;

NSString *key;

NSString *leafvalue;

}

@property (nonatomic, retain) TreeNode *parent;

@property (nonatomic, retain) NSMutableArray *children;

@property (nonatomic, retain) NSString *key;

@property (nonatomic, retain) NSString *leafvalue;

@end

311Recipe: Converting XML into Trees

Page 335: I Phone Developer Cookbook

Use this tree node data type to build a parse tree as the NSXMLParser class works itsway through XML source.The three standard NSXML routines (start element, finishelement, and found characters) perform a recursive depth-first descent through the tree.

Add new nodes when reaching new elements (parser: didStartElement:qualifiedName: attributes:) and add leaf values when encountering text (parser:foundCharacters:). Because XML allows siblings at the same tree depth, this code usesa stack to keep track of the current path to the tree root. Siblings always pop back to thesame 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 10-5 Converting XML into a Parse Tree

@implementation XMLParser

static XMLParser *sharedInstance = nil;

// Use just one parser instance at any time

+(XMLParser *) sharedInstance

{

if(!sharedInstance) {

sharedInstance = [[self alloc] init];

}

return sharedInstance;

}

// Public parser returns the tree root

- (TreeNode *)parseXMLFile: (NSURL *) url

{

stack = [[NSMutableArray alloc] init];

root = [[TreeNode alloc] init];

root.parent = NULL;

root.leafvalue = NULL;

root.children = [[NSMutableArray alloc] init];

[stack addObject:root];

[root release];

NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:url];

[parser setDelegate:self];

[parser parse];

[parser release];

// pop down to real root

return [[root children] lastObject];

}

312 Chapter 10 Connecting to Services

Page 336: I Phone Developer Cookbook

Recipe 10-5 Continued

// 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 alloc] init];

leaf.parent = [stack lastObject];

[(NSMutableArray *)[[stack lastObject] children] addObject:leaf];

leaf.key = [NSString stringWithString:elementName];

leaf.leafvalue = NULL;

leaf.children = [[NSMutableArray alloc] init];

[stack addObject:leaf];

[leaf release];

}

// 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

{

[[stack lastObject] setLeafvalue:[NSString stringWithString:string]];

}

@end

Recipe: Storing and Retrieving Keychain ItemsAs mentioned earlier in this chapter,“secure” password text fields are not particularlysecure.Although the text is obscured onscreen, it’s stored in the preferences file in cleartext.Apple provides a highly secure keychain-based password service with its Securityframework.

Recipe 10-6 shows how to use this technology, using the four available Security calls.These calls allow you to add, update, query, and remove passwords from your keychain.They are as follows:

n methodSecItemAdd(). Use SecItemAdd to create new items in your keychain.Once added, you cannot overwrite them directly.You must either update items orremove them from the keychain before adding them again.

313Recipe: Storing and Retrieving Keychain Items

Page 337: I Phone Developer Cookbook

n SecItemUpdate() method. Update calls allow you to modify existing keychainitems.You cannot update an item that does not yet exist, so always check to see ifan item can be found before attempting to change it.

n SecItemCopyMatching() method. Copy-matching provides the essential “find”function. It enables you to query the keychain to discover whether matching itemscan be found.

n SecItemDelete() method. The delete function does exactly what you’d guess.It removes items from the keychain.

What makes the keychain so tricky to use is its reliance on creating dictionaries tofuel all your requests.All four function calls use dictionaries to pass data, attributes, andother parameters back and forth between your program and the keychain. Searchqueries, update requests, and data storage each demand a slightly different set of diction-ary settings.

The code in Recipe 10-6 provides a simple working one-password keychain wrapper.With it, you can set a password (setPassword:) or you can retrieve a password(fetchPassword).All the remaining work is abstracted out. Internal methods build thedictionaries and call the Security functions.

NoteThe Security framework is available only for the iPhone device. You cannot compile or testits features in the Simulator.

Recipe 10-6 Creating a Secure Keychain Password Keeper Wrapper

#import <Security/Security.h>

// Password Keeper securely saves just one password

@interface PasswordKeeper: NSObject

+ (PasswordKeeper *) sharedInstance;

- (void) setPassword: (NSString *) password;

- (NSString *) fetchPassword;

@end

@implementation PasswordKeeper

static PasswordKeeper *sharedInstance = nil;

+(PasswordKeeper *) sharedInstance {

if(!sharedInstance) {

sharedInstance = [[self alloc] init];

}

return sharedInstance;

}

314 Chapter 10 Connecting to Services

Page 338: I Phone Developer Cookbook

Recipe 10-6 Continued

// Translate status messages into return strings

- (NSString *) fetchStatus : (OSStatus) status

{

if (status == 0) return(@"Success!");

else if (status == errSecNotAvailable) return(@"No trust results are➥available.");

else if (status == errSecItemNotFound) return(@"The item cannot be found.");

else if (status == errSecParam) return(@"Parameter error.");

else if (status == errSecAllocate) return(@"Memory allocation error. Failed to➥allocate memory.");

else if (status == errSecInteractionNotAllowed) return(@"User interaction is➥not allowed.");

else if (status == errSecUnimplemented) return(@"Function is not implemented");

else if (status == errSecDuplicateItem) return(@"The item already exists.");

else if (status == errSecDecode) return(@"Unable to decode the provided➥data.");

else

return([NSString stringWithFormat:@"Function returned: %d", status]);

}

#define ACCOUNT @"iPhone Developer Cookbook Test Account"

#define SERVICE @"iPhone Developer Cookbook Test Service"

#define PWKEY @"iPhone Developer Cookbook Test Password Data"

#define DEBUG YES

// Return a base dictionary

- (NSMutableDictionary *) baseDictionary

{

NSMutableDictionary *md = [[NSMutableDictionary alloc] init];

// Password identification keys

NSData *identifier = [PWKEY dataUsingEncoding:NSUTF8StringEncoding];

[md setObject:identifier forKey:(id)kSecAttrGeneric];

[md setObject:ACCOUNT forKey:(id)kSecAttrAccount];

[md setObject:SERVICE forKey:(id)kSecAttrService];

[md setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];

return [md autorelease];

}

// Return a keychain-style dictionary populated with the password

- (NSMutableDictionary *) buildDictForPassword:(NSString *) password

{

NSMutableDictionary *passwordDict = [self baseDictionary];

315Recipe: Storing and Retrieving Keychain Items

Page 339: I Phone Developer Cookbook

Recipe 10-6 Continued

// Add the password

NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];

[passwordDict setObject:passwordData forKey:(id)kSecValueData]; // password

return passwordDict;

}

// Build a search query based

- (NSMutableDictionary *) buildSearchQuery

{

NSMutableDictionary *genericPasswordQuery = [self baseDictionary];

// Add the search constraints: One match returning both

// data and attributes

[genericPasswordQuery setObject:(id)kSecMatchLimitOne

forKey:(id)kSecMatchLimit];

[genericPasswordQuery setObject:(id)kCFBooleanTrue

forKey:(id)kSecReturnAttributes];

[genericPasswordQuery setObject:(id)kCFBooleanTrue

forKey:(id)kSecReturnData];

return genericPasswordQuery;

}

// retrieve data dictionary from the keychain

- (NSMutableDictionary *) fetchDictionary

{

NSMutableDictionary *genericPasswordQuery = [self buildSearchQuery];

NSMutableDictionary *outDictionary = nil;

OSStatus status = SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery,➥(CFTypeRef *)&outDictionary);

if (DEBUG) printf("FETCH: %s\n", [[self fetchStatus:status] UTF8String]);

if (status == errSecItemNotFound) return NULL;

return outDictionary;

}

// create a new keychain entry

- (BOOL) createKeychainValue:(NSString *) password

{

NSMutableDictionary *md = [self buildDictForPassword:password];

OSStatus status = SecItemAdd((CFDictionaryRef)md, NULL);

if (DEBUG) printf("CREATE: %s\n", [[self fetchStatus:status] UTF8String]);

if (status == noErr) return YES; else return NO;

}

316 Chapter 10 Connecting to Services

Page 340: I Phone Developer Cookbook

Recipe 10-6 Continued

// remove a keychain entry

- (void) clearKeychain

{

NSMutableDictionary *genericPasswordQuery = [self baseDictionary];

OSStatus status = SecItemDelete((CFDictionaryRef) genericPasswordQuery);

if (DEBUG) printf("DELETE: %s\n", [[self fetchStatus:status] UTF8String]);

}

// update a keychaing entry

- (BOOL) updateKeychainValue:(NSString *)password

{

NSMutableDictionary *genericPasswordQuery = [self baseDictionary];

NSMutableDictionary *attributesToUpdate = [[NSMutableDictionary alloc] init];

NSData *passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];

[attributesToUpdate setObject:passwordData forKey:(id)kSecValueData];

OSStatus status = SecItemUpdate((CFDictionaryRef)genericPasswordQuery,➥(CFDictionaryRef)attributesToUpdate);

if (DEBUG) printf("UPDATE: %s\n", [[self fetchStatus:status] UTF8String]);

if (status == 0) return YES; else return NO;

}

// fetch a keychain value

- (NSString *) fetchPassword

{

NSMutableDictionary *outDictionary = [self fetchDictionary];

if (outDictionary)

{

NSString *password = [[NSString alloc] initWithData:[outDictionary➥objectForKey:(id)kSecValueData] encoding:NSUTF8StringEncoding];

return [password autorelease];

} else return NULL;

}

- (void) setPassword: (NSString *) thePassword

{

if (![self createKeychainValue:thePassword])

[self updateKeychainValue:thePassword];

}

@end

317Recipe: Storing and Retrieving Keychain Items

Page 341: I Phone Developer Cookbook

Storing Multiple Keychain ValuesWhen working with keychains, I’ve found that it’s easiest to keep to the single-entryapproach even when working with multiple passwords.To accomplish this, store serial-ized dictionaries rather than NSStrings. Serialization turns a dictionary into a singleNSData object.You can recover your dictionary from that data by deserializing it.Listing 10-7 shows how to convert a dictionary to and from data.

Listing 10-7 Using Serialization to Convert Dictionaries to and from Data

// Serialize to data

- (NSData *) dataFromDictionary: (NSMutableDictionary *) dict

{

NSString *errorString;

NSData *outData = [NSPropertyListSerialization dataFromPropertyList:dict ➥format:NSPropertyListBinaryFormat_v1_0 errorDescription:&errorString];

return outData;

}

// Deserialize from data

- (NSMutableDictionary *) dictionaryFromData: (NSData *) data

{

NSString *errorString;

NSMutableDictionary *outDict = [NSPropertyListSerialization➥propertyListFromData:data➥mutabilityOption:kCFPropertyListMutableContainersAndLeaves format:NULL➥errorDescription:&errorString];

return outDict;

}

Serialization keeps your keychain methods simple.You can use the same storage and retrieval calls from Recipe 10-6.At the same time, using a dictionary allows you to expand your password keeper interface to provide multiple key-value storage.Listing 10-8 shows the interface and object calls for a multipassword keychain wrapper.It’s built on the same single data entry code used in Recipe 10-6.

Listing 10-8 Expanding a Single-Entry Keychain to Support Multiple Passwords

@interface PasswordKeeper: NSObject

+ (PasswordKeeper *) sharedInstance;

- (void) setObject: (id) anObject forKey: (NSString *) aKey;

- (void) removeObjectForKey: (NSString *) aKey;

- (id) objectForKey: (NSString *) aKey;

- (NSMutableDictionary *) passwordDict;

@end

// Simple Keychain Access Protocol Methods

318 Chapter 10 Connecting to Services

Page 342: I Phone Developer Cookbook

Listing 10-7 Continued

- (void) setObject: (id) anObject forKey: (NSString *) aKey

{

NSMutableDictionary *dict = [self fetchKeychainValue];

if (dict)

{

// Keychain already has object

[dict setObject:anObject forKey:aKey];

[self updateKeychainValue:dict];

return;

}

// Dictionary not found so create it

dict = [[NSMutableDictionary alloc] init];

[dict setObject:anObject forKey:aKey];

if (![self createKeychainValue:dict]) [self updateKeychainValue:dict];

}

- (void) removeObjectForKey: (NSString *) aKey

{

NSMutableDictionary *dict = [self fetchKeychainValue];

if (dict)

{

// Keychain has object

[dict removeObjectForKey:aKey];

[self updateKeychainValue:dict];

return;

}

}

- (id) objectForKey: (NSString *) aKey

{

NSMutableDictionary *dict = [self fetchKeychainValue];

return [dict objectForKey:aKey];

}

- (NSMutableDictionary *) passwordDict

{

return [self fetchKeychainValue];

}

Keychain PersistenceKeychain data persists even after de-installing your application. Maurice Cusseaux ofApple’s developer relations writes,“Keychain items created by any application will bepersistent across uninstalls because of the mere fact that the keychain store isn’t located

319Recipe: Storing and Retrieving Keychain Items

Page 343: I Phone Developer Cookbook

inside of the application bundle and there is no facility by which the system can be notified of when something is uninstalled to then also uninstall all associated keychainitems. It is also a policy issue between the trade-offs of losing sensitive passwords throughmalicious uninstalling and keeping sensitive passwords intact and potentially secured awayfor the user(s).”

This behavior allows you to use the keychain to maintain persistent iPhone informa-tion.You might keep track of user registration or limit demo mode usage.The persist-ence means that nothing short of firmware reinstall (without a backup restore) will wipethe data.

Sending and Receiving FilesSending files back and forth between your iPhone and another host can be challenging.At the time of this writing,Apple provided no standard way to openly sync data to andfrom the iPhone. (Chapter 7,“Media,” discussed recovering data from iTune’s mdbackupfiles.) Here are technologies you may want to consider adding to your applications:

n Bonjour. The iPhone’s CFNetwork framework allows you to use Bonjour zero-configuration networking to send data between computers. Bonjour allowsautomatic service discovery, making it simple to connect two systems on the samenetwork to transfer data.Apple’s WiTap sample code demonstrates how to useBonjour messaging in your application.With Bonjour, you can send short messagesor entire files from one host to another.

n FTP. The File Transfer Protocol is especially nice to use because it’s standardacross so many platforms.Apple’s Core Foundation FTP sample code is easy towork with.You can find source at http://developer.apple.com/samplecode/CFFTPSample/listing1.html.There’s little you have to do to get the upload anddownload functionality working. Once you have a user’s name, password, and hostinformation stored, FTP data transfer can be easily automated.

n E-mail. Apple no longer allows you to attach local files to outgoing e-mail usingattachments. Because of this, if you want to use e-mail, you’ll have to build yourown client.A little sockets programming is all it takes to build an outgoing SMTPor SMTPAUTH utility. Google for sample code.There’s quite a lot out there,mostly in standard C.

n Web services. You can use the iPhone’s onboard Internet connection to sendand receive data from the World Wide Web.You’ve seen how to download datafrom URLs in this chapter and in recipes scattered through this book.You can alsoupload data from the iPhone to the Web using the NSURLConnection class tohandle synchronous and asynchronous requests for both GET and POST. SeanHeber’s iAppADay source at Google Code (http://code.google.com/p/iappaday/)provides an excellent starting point for learning about data posting.

n Web server.You don’t need to implement an entire Web server to serve data fromyour iPhone to a browser. Just a few functions (serving an index and a few files)

320 Chapter 10 Connecting to Services

Page 344: I Phone Developer Cookbook

are all it takes. If your goal is to expose data to the outside world, creating a basicserver allows you to use standard software such as Safari and Firefox to retrievefiles from the iPhone.

Recipe: Building a Simple Web-Based ServerA Web server provides one of the cleanest ways to serve data off your phone to anothercomputer.You don’t need special client software.Any browser can list and access Web-based files. Figure 10-3 shows an iPhone serving to a Web browser; the iPhone screen-shot is superimposed over the browser screen.

321Recipe: Building a Simple Web-Based Server

Figure 10-3 With a bit of programming, you can set up your iPhone toserve files to networked computers.

A Web server requires just a few key routines.You must establish the service, creatinga loop that listens for a request (startServer), and then pass those requests onto a handler (handleWebRequest:) that responds with the requested data. One moremethod (stopService) is needed to handle service shutdown. Recipe 10-7 shows thesethree core methods for establishing and controlling Web service.

The loop routine uses low-level socket programming to establish a listening port andcatch client requests.When the client issues a GET command, the server intercepts thatrequest and passes it to the Web request handler.The handler decomposes it to find thename of the desired data file. It then looks up the file in its folder and, if found, readsand then serves the requested file data.

Page 345: I Phone Developer Cookbook

Recipe 10-7 Serving iPhone Files Through a Web Service

// Serve files to GET requests

- (void) handleWebRequest:(NSNumber *)fdNum

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

int fd = [fdNum intValue];

static char buffer[BUFSIZE+1];

int len = read(fd, buffer, BUFSIZE);

buffer[len] = ‘\0’;

NSArray *reqs = [[NSString stringWithCString:buffer]➥componentsSeparatedByString:@"\n"];

NSString *getreq = [[reqs objectAtIndex:0] substringFromIndex:4];

NSRange range = [getreq rangeOfString:@"HTTP/"];

if (range.location == NSNotFound)

{

printf("Error: GET request was improperly formed\n");

close(fd);

return;

}

NSString *filereq = [[getreq substringToIndex:range.location]➥stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];

// Serve an index.html file to "GET /" requests

if ([filereq isEqualToString:@"/"])

{

NSString *outcontent = [NSString stringWithFormat:@"HTTP/1.0 200➥OK\r\nContent-Type: text/html\r\n\r\n"];

write(fd, [outcontent UTF8String], [outcontent length]);

NSString *outdata = [self createindex];

write(fd, [outdata UTF8String], [outdata length]);

close(fd);

return;

}

// Check extensions against known MIME types

NSString *mime = [self mimeForExt:[filereq pathExtension]];

if (!mime)

{

printf("Error recovering mime type.\n");

NSString *outcontent = [NSString stringWithFormat:@"HTTP/1.0 200➥OK\r\nContent-Type: text/html\r\n\r\n"];

write (fd, [outcontent UTF8String], [outcontent length]);

outcontent = @"<p>Sorry. This file type is not supported</p>\n";

write (fd, [outcontent UTF8String], [outcontent length]);

close(fd);

322 Chapter 10 Connecting to Services

Page 346: I Phone Developer Cookbook

Recipe 10-7 Continued

return;

}

// Read and serve the file

NSString *outcontent = [NSString stringWithFormat:@"HTTP/1.0 200➥OK\r\nContent-Type: %@\r\n\r\n", mime];

write (fd, [outcontent UTF8String], [outcontent length]);

filereq = [filereq➥stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSString *fullpath = [PICS_FOLDER stringByAppendingString:filereq];

NSData *data = [NSData dataWithContentsOfFile:fullpath];

if (!data)

{

printf("Error: file not found.\n");

NSString *outcontent = [NSString stringWithFormat:@"HTTP/1.0 200➥OK\r\nContent-Type: text/html\r\n\r\n"];

write (fd, [outcontent UTF8String], [outcontent length]);

outcontent = @"<p>File was not found.</p>\n";

write (fd, [outcontent UTF8String], [outcontent length]);

close(fd);

return;

}

printf("%d bytes read from file\n", [data length]);

write(fd, [data bytes], [data length]);

close(fd);

[pool release];

}

// Begin serving data

- (void) startServer

{

NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

int socketfd;

socklen_t length;

static struct sockaddr_in cli_addr;

static struct sockaddr_in serv_addr;

// Set up socket

if((listenfd = socket(AF_INET, SOCK_STREAM,0)) <0)

{

isServing = NO;

return;

}

323Recipe: Building a Simple Web-Based Server

Page 347: I Phone Developer Cookbook

Recipe 10-7 Continued

// Serve to a random port

serv_addr.sin_family = AF_INET;

serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

serv_addr.sin_port = 0;

// Bind

if(bind(listenfd, (struct sockaddr *)&serv_addr,sizeof(serv_addr)) <0)

{

isServing = NO;

return;

}

// Find out what port number was chosen.

int namelen = sizeof(serv_addr);

if (getsockname(listenfd, (struct sockaddr *)&serv_addr, (void *) ➥&namelen) < 0) {

close(listenfd);

isServing = NO;

return;

}

chosenPort = ntohs(serv_addr.sin_port);

// Listen

if(listen(listenfd, 64) < 0)

{

isServing = NO;

return;

}

// Service has now successfully started

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Stop Service"

style:UIBarButtonItemStylePlain

target:self

action:@selector(stopService)]

autorelease];

// Respond to requests until the service shuts down

while (1 > 0) {

length = sizeof(cli_addr);

if((socketfd = accept(listenfd, (struct sockaddr *)&cli_addr, &length)) < 0)

{

isServing = NO;

return;

}

324 Chapter 10 Connecting to Services

Page 348: I Phone Developer Cookbook

Recipe 10-7 Continued

[self handleWebRequest:[NSNumber numberWithInt:socketfd]];

}

[pool release];

}

// Shut down service

- (void) stopService

{

printf("Shutting down service\n");

isServing = NO;

close(listenfd);

[self stoppingService];

self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc]

initWithTitle:@"Start Service"

style:UIBarButtonItemStylePlain

target:self

action:@selector(startService)]

autorelease];

}

Push NotificationsAlthough Apple has announced official push notification services for the iPhone, at thetime of this writing they have not been introduced. Push services allow your applicationto provide user notifications when new data becomes available for download at yourWeb server. Push notices include badges—those little red circles that appear onSpringBoard icons (see Listings 4-2 and 4-3), text-based alerts (Listing 4-1), and customsounds (Recipe 4-11).

To add this functionality to your application, your server needs to send notificationsto the iPhone’s onboard push notification service. From the server end, you supply thebundle identifier for the application you want to notify, the unique device ID for theiPhone, and a node name for your service. Notifications, which must not exceed 1K inlength (including all Apple DTD definitions), can specify the following:

n A short messagen A button that launches your application (normally “View”)n A badge number (a positive integer)n An alert sound that specifies which application sound file to play (orUIDefaultSound)

No data is sent.To keep the push mechanism efficient and to respect that 1K limit,notifications offer status changes only.Your application retrieves data as needed.

325Push Notifications

Page 349: I Phone Developer Cookbook

From the iPhone side, your application registers with a service (thus providing theunique iPhone identifier to the service) and enables or disables notifications.When notifications for your application get pushed to a device, your application responds via aUIApplicationDelegate method, yet to be announced. (See Listing 10-1 to get a hintof what that method might look like, with notification handling replacing URL handling.)

The delegate method can choose to handle the notification or to ignore it.Typicalmethods either update an “available data” list or downloading new data. For example, ifyour user subscribes to a PDF-based newsletter, a push system would allow the user toknow when new editions were available.The user would then launch your application todownload and read those issues.

SummaryThis chapter introduced a wide range network supporting technologies.You’ve seen howto add preferences panes, build custom URL schemes, check for network connectivity,work with keychains, and more. Here are a few thoughts to take away with you beforeleaving this chapter:

n Keychains provide a high level of program security, but they cannot be integratedinto those easy-to-use custom settings panes. Consider carefully whether a minimalsecurity approach of obscuring the password is enough for your program. If so, youcan bypass the keychain and take advantage of one of the nicest developer integra-tion features on the iPhone.

n Although custom URL schemes provide a limited application-to-application com-munication pattern, you can also launch one application from another by callingthe (undocumented) launchApplicationWithIdentifier: suspended:method.

n Most of Apple’s networking support is provided through very low-level C-basedroutines. If you can find a friendly Objective-C wrapper to simplify your program-ming work, consider using it.The only drawback occurs when you specificallyneed tight networking control at the most basic level of your application.

n Even when Apple provides Objective-C wrappers, as they do with NSXMLParser,it’s not always the class you wanted or hoped for.Adapting classes is a big part ofthe iPhone programming experience.

326 Chapter 10 Connecting to Services

Page 350: I Phone Developer Cookbook

11One More Thing:

Programming Cover Flow

A lthough Cover Flow is not officially included in the iPhone SDK, it offers one of themost beautiful features of the iPhone experience.With Cover Flow, you can offer users agorgeously intense visual selection that puts standard scrolling lists to shame.This chapterintroduces Cover Flow and shows how you can use it in your applications. Boom!

The UICoverFlowLayer ClassUICoverFlowLayer is a standard UIKit class although it does not appear in the officialSDK. Steve Nygard’s class-dump (www.codethecode.com/projects/class-dump/) enabledme to extract the UICoverFlowLayer header file shown in Listing 11-1 from the UIKitframework. Class-dump works by generating class declarations from the Objective-Csegments of Mach-O files.

Listing 11-1 The UICoverFlowLayer.h Class Header

/*

* Generated by class-dump 3.1.1.

*

* class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2006 by Steve➥Nygard.

*/

@interface UICoverFlowLayer : NSObject

{

void *_private;

}

- (id)initWithFrame:(struct CGRect)fp8 numberOfCovers:(unsigned int)fp24;

- (void)dealloc;

- (void)setDelegate:(id)fp8;

Page 351: I Phone Developer Cookbook

Listing 11-1 Continued

- (void)setPlaceholderImage:(void *)fp8;

- (void)_prefetch:(unsigned int)fp8 atIndex:(unsigned int)fp12;

- (void)_requestBatch;

- (void)addSublayer:(id)fp8;

- (void)_requestImageAtIndex:(int)fp8 quality:(unsigned int)fp12;

- (void)_requestImageAtIndex:(int)fp8;

- (void)_notifySelectionDidChange;

- (void)startHeartbeat:(SEL) aSelector inRunLoopMode: (id)whatever2;

- (void)transitionIn:(float)fp8;

- (void)transitionOut:(float)fp8;

- (void)transition:(unsigned int)fp8 withCoverFrame:(struct CGRect)fp12;

- (void)transitionIn:(float)fp8 fromFrame:(struct CGRect)fp12;

- (void)transitionOut:(float)fp8 toFrame:(struct CGRect)fp12;

- (void)setDisplayedOrientation:(int)fp8 animate:(BOOL)fp12;

- (void)setInfoLayer:(id)fp8;

- (void)setImage:(void *)fp8 atIndex:(unsigned int)fp12 type:(unsigned int)fp16;

- (void)setImage:(void *)fp8 atIndex:(unsigned int)fp12 type:(unsigned int)fp16➥imageSubRect:(struct _NSRect)fp20;

- (void)setImage:(void *)fp8 atIndex:(unsigned int)fp12;

- (unsigned int)indexOfSelectedCover;

- (unsigned int)_coverAtScreenPosition:(struct CGPoint)fp8;

- (void)_recycleLayer:(int)fp8 to:(int)fp12;

- (void)_setNewSelectedIndex:(int)fp8;

- (void)_updateTick;

- (void)displayTick;

- (void)dragFlow:(unsigned int)fp8 atPoint:(struct CGPoint)fp12;

- (void)selectCoverAtIndex:(unsigned int)fp8;

- (void)selectCoverAtOffset:(int)fp8;

- (unsigned int)coverIndexAtPosition:(float)fp8;

- (void)_setupFlippedCoverLayer:(id)fp8;

- (void)flipSelectedCover;

- (int)benchmarkTick;

- (void)benchmarkHeartbeatLongScrub;

- (void)benchmarkHeartbeatShortScrub;

- (void)benchmarkHeartbeatScrubAndWait;

- (void)benchmarkTightLoop;

- (void)benchmarkTightLoopScrub;

- (BOOL)benchmarkLoadScrub;

- (BOOL)benchmarkImageManager:(void *)fp8;

- (void)benchmarkSetEnv;

- (void)benchmarkMode:(int)fp8;

- (void)benchmarkTickMode:(int)fp8;

- (void)benchmarkImageMode:(int)fp8;

- (void)benchmarkPerformanceLog:(BOOL)fp8;

- (void)benchmarkTightLoopTime:(unsigned int)fp8;

328 Chapter 11 One More Thing: Programming Cover Flow

Page 352: I Phone Developer Cookbook

Listing 11-1 Continued

- (void)benchmarkLongScrubSpeed:(float)fp8;

- (void)benchmarkSkipImageLoad:(BOOL)fp8;

@end

As this listing shows, the UICoverFlowLayer class defines a layer, not a view. Layersare the abstract rendering class that lies inside every UIView object, providing an internalcanvas for those views. So if you’re going to use this class, you need to wrap it in ausable view.The next section shows you how.

Building a Cover Flow ViewTo start working with Cover Flow, you need to insert UICoverFlowLayer instances intoUIViews. Placing layers into a view brings Cover Flow technology into standard UIKitterms, letting you work with the UIView class you’ve seen throughout this book.Toaccomplish this, you do nothing more than create the view and assign the layer. Gettingthe view to display and work properly takes a few more steps.

Those extra steps mean customizing the way that users interact with Cover Flow.TheCover Flow view needs to respond properly when users touch, drag, and tap within theview.To do this, the view must hand off touch and drag interactions to the Cover Flowlayer by sending dragFlow: atPoint: messages.

Double-tap handling provides a different challenge.The Cover Flow layer does notimplement any standard response to double-taps. Instead, you want your application torecognize that interaction and provide some meaningful response.The way I designedthis class, you can set a host that conforms to the CoverFlowHost protocol.When theview intercepts double-taps, it invokes the host’s doubleTapCallback method.Thisenables the view controller that owns the Cover Flow view to respond when users per-form a double-tap on the selected cover.

Cover flipping refers to the interaction where a Cover Flow image flips out of theway to reveal a secondary view (flipSelectedCover).As a rule, you can use double-tap feedback or you can use cover flipping, but do choose one or the other. By imple-menting both, you cause the two interaction styles to unnecessarily compete, and youmay not be able to control which response gets called.

NoteThe complete project for this chapter appears in this book’s sample code. See http://ericasadun.com for details.

Listing 11-2 Building a Cover Flow View

#import <UIKit/UIKit.h>

#import "UICoverFlowLayer.h"

329Building a Cover Flow View

Page 353: I Phone Developer Cookbook

Listing 11-2 Continued

@protocol CoverFlowHost <NSObject>

- (void) doubleTapCallback;

@end

@interface CoverFlowView : UIView {

id <CoverFlowHost> host;

id info;

UICoverFlowLayer *cfLayer;

UILabel *label;

}

- (CoverFlowView *) initWithFrame: (CGRect) aFrame andCount: (int) aCount;

- (void) tick;

- (void) setHost: (id)anObject;

- (void) flipSelectedCover;

- (UILabel *) label;

- (UICoverFlowLayer *) cfLayer;

@end

@implementation CoverFlowView

- (CoverFlowView *) initWithFrame: (CGRect) aRect andCount: (int) count

{

self = [super initWithFrame:aRect];

cfLayer = [[UICoverFlowLayer alloc] initWithFrame:[[UIScreen mainScreen]➥bounds] numberOfCovers:count];

[[self layer] addSublayer:cfLayer];

// Add the placeholder (image stand-in) layer

CGRect phrect = CGRectMake(0.0f, 0.0f, 200.0f, 200.0f);

UIImageView *phimg = [[UIImageView alloc] initWithFrame:phrect];

[cfLayer setPlaceholderImage: [phimg layer]];

// Add its info (label) layer

label = [[UILabel alloc] init];

[label setTextAlignment:UITextAlignmentCenter];

[label setFont:[UIFont boldSystemFontOfSize:20.0f]];

[label setBackgroundColor:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f➥alpha:0.0f]];

[label setTextColor:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f➥alpha:0.75f]];

[label setNumberOfLines:2];

[label setLineBreakMode:UILineBreakModeWordWrap];

[cfLayer setInfoLayer:[label layer]];

return self;

}

330 Chapter 11 One More Thing: Programming Cover Flow

Page 354: I Phone Developer Cookbook

Listing 11-2 Continued

- (void) touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event

{

UITouch *touch = [touches anyObject];

CGPoint pt = [touch locationInView:self];

[cfLayer dragFlow:0 atPoint:pt];

}

- (void) touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event

{

UITouch *touch = [touches anyObject];

CGPoint pt = [touch locationInView:self];

[cfLayer dragFlow:1 atPoint:pt];

}

- (void) touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event

{

UITouch *touch = [touches anyObject];

CGPoint pt = [touch locationInView:self];

if ([touch tapCount] == 2)

{

if (host) [host doubleTapCallback];

return;

}

[cfLayer dragFlow:2 atPoint:pt];

}

- (void) flipSelectedCover

{

[cfLayer flipSelectedCover];

}

- (BOOL) ignoresMouseEvents {return NO;}

- (void) tick {[cfLayer displayTick];}

- (void) setHost: (id)anObject {host = anObject;}

- (UILabel *) label {return label;}

- (UICoverFlowLayer *) cfLayer {return cfLayer;}

@end

Building a Cover Flow View ControllerOnce the CoverFlowView class is established, you can add that view to any view con-troller you like.The secret to making everything work lies in checking off several to-do

331Building a Cover Flow View Controller

Page 355: I Phone Developer Cookbook

boxes.The following sections show the steps you need to take to make your Cover Flowpresentation work best:

n Hide the application status bar. Your Cover Flow application should occupythe entire screen without a distracting status bar on hand. Hide the status bar bycalling [[UIApplication sharedApplication] setStatusBarHidden:YES].

n Allocate and initialize the view. You must know in advance the number ofcovers you’ll use. Prepare the cover art and title strings.These power the view datasource.Then pass the initializer a count of the covers.

n Start the heartbeat. Wait until your loadView method to set the Cover Flow asyour main view, and then start its heartbeat for the current run loop.

n Supply delegate and data source methods. Make sure your view controllercan handle the required Cover Flow delegate and data source callbacks. Detailsabout these required and optional methods follow in the next sections.

When you’ve accomplished each of these goals, you should have a working CoverFlow presentation quickly up and running. Figure 11-1 shows a Cover Flow interfacebuilt by the view controller established in Listing 11-3. In this Cover Flow, users are pre-sented with crayon colors as a series of circular swatches.Tapping flips the swatch andreveals the six-character hex code for that color.

332 Chapter 11 One More Thing: Programming Cover Flow

Cover Flow Data Source MethodsFor standard Cover Flow operation, your view controller supplies UIImages to thecoverFlow: requestImageAtIndex: quality: method. Use the supplied index todetermine which image to return.This method is required for the data source towork properly. For faster loading, use lazy image generation rather than the array I use

Figure 11-1 The undocumented UIKit Cover Flow layer class allows you to build compelling image-based Cover Flow

presentations in your applications.

Page 356: I Phone Developer Cookbook

here—create the images you need on-the-fly.You can store them into a sparse dictionary,only building those images that are used.

Flip layers are optional.They’re the layers that enable your center covers to flipwhen a user taps them.When working with flip layers, add a coverFlow:requestFlipLayerAtIndex: method to your view controller.This method returnsan image layer showing the back of the flipped item.

Cover Flow Delegate MethodsAs with tables, Cover Flow views produce a selectionDidChange: callback whenusers pick a new cover. Use this method to update the displayed label text and to keeptrack of which item was most recently selected.

Another delegate method, coverFlowFlipDidEnd:, lets you know when any flipanimation completes.This method is called for both front to back flips as well as for backto front flips, so keep track of the state; note whether the flip view is out or hidden.

Listing 11-3 Build a Cover Flow View in a View Controller

#import <UIKit/UIKit.h>

#import "CoverFlowView.h"

@interface CoverViewController : UIViewController

{

CoverFlowView *cfView;

UICoverFlowLayer *cfLayer;

UILabel *label;

NSMutableArray *covers;

NSMutableArray *titles;

int whichItem;

id target;

SEL selector;

NSMutableDictionary *colorDict;

UILabel *flippedView;

BOOL flipOut;

}

@end

// *********************************************

// Cover Flow View Controller

//

@implementation CoverViewController

- (CoverViewController *) init

{

if (!(self = [super init])) return self;

333Building a Cover Flow View Controller

Page 357: I Phone Developer Cookbook

Listing 11-3 Continued

// Read in the crayons and set up the arrays and dictionaries

NSArray *crayons = [[NSString stringWithContentsOfFile:[[NSBundle mainBundle]➥pathForResource:@"crayons" ofType:@"txt"]]➥componentsSeparatedByString:@"\n"];

covers = [[NSMutableArray alloc] init];

titles = [[NSMutableArray alloc] init];

colorDict = [[NSMutableDictionary alloc] init];

// Create the title and cover arrays

for (NSString *crayon in crayons)

{

NSArray *theCrayon = [crayon componentsSeparatedByString:@"#"];

if ([theCrayon count] != 2) continue;

[titles addObject:[theCrayon objectAtIndex:0]];

[covers addObject:createImage([theCrayon objectAtIndex:1])];

[colorDict setObject:[theCrayon objectAtIndex:1] forKey:[theCrayon➥objectAtIndex:0]];

}

// Create the flip object

CGRect fliprect = CGRectMake(0.0f, 0.0f, 480.0f, 480.0f);

flippedView = [[FlipView alloc] initWithFrame:fliprect];

[flippedView setTransform:CGAffineTransformMakeRotation(3.141592f / 2.0f)];

[flippedView setUserInteractionEnabled:YES];

// Initialize

cfView = [[CoverFlowView alloc] initWithFrame:[[UIScreen mainScreen]➥applicationFrame] andCount:[titles count]];

[cfView setUserInteractionEnabled:YES];

[cfView setHost:self];

cfLayer = [cfView cfLayer];

label = [cfView label];

// Finish setting up the Cover Flow layer

whichItem = [titles count] / 2;

[cfLayer selectCoverAtIndex:whichItem];

[cfLayer setDelegate:self];

selector = NULL;

target = NULL;

return self;

}

// *********************************************

// Cover Flow delegate methods

//

334 Chapter 11 One More Thing: Programming Cover Flow

Page 358: I Phone Developer Cookbook

Listing 11-3 Continued

- (void) coverFlow: (id) coverFlow selectionDidChange: (int) index

{

whichItem = index;

[label setText:[titles objectAtIndex:index]];

}

// Detect the end of the flip — both on reveal and hide

- (void) coverFlowFlipDidEnd: (UICoverFlowLayer *)coverFlow

{

if (flipOut)

[[[UIApplication sharedApplication] keyWindow] addSubview:flippedView];

else

[flippedView removeFromSuperview];

}

// *********************************************

// Cover Flow datasource methods

//

- (void) coverFlow:(id)coverFlow requestImageAtIndex: (int)index quality:➥(int)quality

{

UIImage *whichImg = [covers objectAtIndex:index];

[coverFlow setImage:[whichImg CGImage] atIndex:index type:quality]; // used➥to be cfLayer

}

// Return a flip layer, one that preferably integrates into the flip presentation

➥- (id) coverFlow: (UICoverFlowLayer *)coverFlow requestFlipLayerAtIndex: (int)➥index

{

if (flipOut) [flippedView removeFromSuperview];

flipOut = !flipOut;

// Prepare the flip text

[flippedView setText:[NSString stringWithFormat:@"%@\n%@", [titlesobjectAtIndex:index], [colorDict objectForKey:[titles objectAtIndex:index]]]];

// Flip with a simple blank square

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 140.0f,➥140.0f)];

[view setBackgroundColor:[UIColor clearColor]];

return [view layer];

}

// *********************************************

335Building a Cover Flow View Controller

Page 359: I Phone Developer Cookbook

Listing 11-3 Continued

// Utility methods

//

- (CoverFlowView *) cfView {return cfView; }

- (UICoverFlowLayer *) cfLayer {return cfLayer; }

- (int) selectedItem {return whichItem; }

- (void) start

{

[cfView startHeartbeat: @selector(tick) inRunLoopMode:➥(id)kCFRunLoopDefaultMode];

[cfLayer transitionIn:1.0f];

}

- (void) stop

{

[cfView stopHeartbeat: @selector(tick)];

}

- (void) loadView

{

[super loadView];

self.view = cfView;

[self start];

}

// *********************************************

// Callback method for double tap

//

- (void) doubleTapCallback

{

}

@end

SummaryAs you’ve seen in this chapter, and throughout this book, some of the nicest bits ofiPhone programming are included in the public iPhone frameworks but not in the SDK.Apple’s unofficial policy on this is clear:You can use these items in your programs, butyou do so at your own risk.Your code may break at each firmware release. Striking thebalance between risk and reward is up to you.

336 Chapter 11 One More Thing: Programming Cover Flow

Page 360: I Phone Developer Cookbook

AABNewPersonViewController class, 78

ABPeoplePickerNavigationController class, 274

ABPersonCopyImageData function, 271

ABPersonGetTypeOfProperty function, 271

ABPersonHasImageData function, 271

ABPersonViewController class, 273-274, 277

acceleration sensors

catching acceleration events, 291-292locating “up” direction, 290-292moving objects based on

accelerometer feedback, 292-295accessing

Address Book image data, 271-273core device information, 288-289maps, 286-288pixel-by-pixel values, 54

accessoryButtonTappedForRowWithIndexPath: method, 163

ad hoc distribution, 35-36

Address Book

AddressBook.frameworkAddress Book queries, 270-271overview, 270people picker calls, handling, 270

AddressBookUI.framework, 269-270contact information

adding, 277-278browsing, 274-278displaying, 273-274

image data, accessing, 271-273overview, 269querying, 270-271

AddressBook.framework

Address Book queries, 270-271overview, 270people picker calls, handling, 270

AddressBookUI.framework, 269-270

Page 361: I Phone Developer Cookbook

addresses, e-mail, 277

addStatusBarImageNamed: method, 131

addSubview: method, 38, 42-43

addTextFieldWithValue: method, 113

affine transforms (views), 72-74

albums (image), 207

alerts

application badges, 132-134audio alerts, 134-136autotimed no-button alerts, 112-113creating, 109-110customizing, 110displaying, 110logging results

with custom log functions, 108with freeopen function, 108with printf function, 108

menus, 115-116multiline button displays, 110-112overview, 107“Please,Wait” overlay, 123-127progress indicators

head-up display progress indicators,117-119

overview, 117progress bar, 121-123spinning circle, 117-120

scroll-down alerts, 127-131status bar, adding images to, 131-132text fields, 113-115UIAlertView class, 107-109vibration alerts, 136

alignment in toolbars, 267

alloc method, 140

animations

animated butterfly project, 292-295animated overlays, 123-127buttons

adding to, 236-237animating responses to, 238-239

UIView animationsCore Animation Transitions, 66-69flipping views, 64-65methods, 60overview, 59swapping views, 62-64transitions, 64-65with transparency changes, 60-62

application badges, 132-134

application compiles, 33-34

application distribution

ad hoc distribution, 35-36compiling applications for, 33-34

application identifiers, 29

application limits, 9

application sandbox

Application Support folder, 197Documents folder

backups, 197browsing folders by file type, 198finding documents in, 198table of contents creation, 199-200writing images to, 207-208

Library backups, 197tmp folder, deleting files from, 197

Application Support folder (application sandbox), 197

application testing, 32-33

applicationDidFinishLaunching: method, 18-20

applicationIconBadgeNumber property, 132

applications. See specific applications, 19

applicationWillTerminate: method, 18, 49

arrays, view controller arrays, 102-103

assembling projects, 2-3

assigning

data sources, 140-141delegates, 141text styles to table cells, 151-152

338 addresses, e-mail

Page 362: I Phone Developer Cookbook

audio

alerts, 134-136Audio Queue services, 134, 214AVController class, 215-217Celestial media framework

playing audio via, 215-217stopping audio playback via, 217

Media Player, playing via, 215-219MPArrayQueueFeeder class, 215MPAVController class, 215MPItem class, 215-216recording, 219-226System Audio services, 214

Audio Queue services, 134, 214

AudioServicesAddSystemSoundCompletionmethod, 135

AudioServicesCreateSystemSoundIDmethod, 135

AudioServicesDisposeSystemSoundIDmethod, 135

AudioServicesPlaySystemSound method, 135

autotimed no-button alerts, 112-113

availability of sites, checking, 307-308

AVController class, playing audio via, 215-217

Bbackgrounds

of cells, 172-175of views, 40

backups

Documents folder (application sandbox), 197

Library (application sandbox), 197recovering media files from, 228-229

badges, adding to applications, 132-134

bitmaps, testing touch hits against, 54-56

blue and white table cells, creating, 177-178

Bonjour, 320

breakponts, 26

bringSubviewToFront: method, 39

browsing Address Book information

contacts, adding, 277-278for e-mail addresses, 277overview, 274-276people picker events,

handling, 276-277butterfly animation project, 292-295

buttons

adding color to, 242animation

adding to, 236-237button responses, 238-239

Contact Add button type, 232custom buttons, creating, 233-236customizing, 241-242Detail Disclosure button type, 232glass buttons, 236hidden buttons in toolbars, 267Info Light/Dark button type, 232multiline button displays,

creating, 110-112Navigation bar, adding to, 231-232“precooked” button types, 232Rounded Rectangle button type, 232toolbars, view configuration in, 267Undo button, adding to text

views, 250-251

Ccallout views, 258-260

camera (iPhone)

accessing, 213camera rolls, editing images

from, 209-212images, snapping via, 212screenshots, snapping via, 212

catching acceleration events, 291-292

339catching acceleration events

Page 363: I Phone Developer Cookbook

CATransitions. See Core AnimationTransitions

Celestial media framework

playing audio via, 215-217stoppimg audio playback via, 217

cellForRowAtIndexPath: method, 173, 190

cells (table)

adding, 159-160alternate blue and white cells,

creating, 177-178cell selections, removing, 152-153check mark selections,

creating, 155-156coupled cell controls, 180-182custom backgrounds, 172-175customizing, 153-155deleting

on-the-fly deletion, 159-160with remove controls, 157-158swiping cells, 158-159

disclosures, 162-163loading images into, 149-150reordering, 161-162reusing, 143swiping, 158-159text styles, setting, 151-152

centering landscape views, 74

CGPoint structure, 39

CGPointMake method, 39

CGRect structure, 39

CGRectFromString method, 49

CGRectInset method, 39, 42

CGRectMake method, 39

CGRectZero method, 39

CGSize structure, 39

check mark cell selections,creating, 155-156

checking site availability, 307-308

chevrons (in cells). See disclosures

circular path, clipping views to, 51-53

classes

ABNewPersonViewController, 78ABPeoplePickerNavigation

Controller, 274ABPersonViewController,

273-274, 277DragView, 46-48HelloController, 20MPMoviePlayerController, 78multiple inheritance, 11NSScanner, 286UIActionSheet, 107UIActivityIndicatorView, 117-120UIActivityIndicatorViewStyle

Gray, 119UIActivityIndicatorViewStyle

White, 119UIActivityIndicatorViewStyleWhite

Large, 119UIAlertSheet, 108UIAlertView, 107-109. See also alertsUIApplication, 17UIApplicationWillChangeStatusBar

OrientationNotification, 16UICoverFlowLayer, 327-329UIDatePicker, 186

properties, 188selecting times/dates, 188-189

UIDevice, 288-289UIDeviceOrientationDidChange

Notification, 16UIImagePickerController, 78UINavigationController

controller initialization, 91-92creating two-item menu

with, 93-95drilling through views

with, 100-103modal presentation, 92overview, 78, 91pushing/popping controllers, 92

340 CATransitions

Page 364: I Phone Developer Cookbook

segmented controls, 95-97UINavigationItem class, 92-93UIToolbar class, 97-100

UINavigationItem, 92-93UIProgressHUD, 117-119UIProgressView, 121-123UIRemoveControl, 157-158UITabBarController

creating, 103-105overview, 78, 103

UITableView, 139-140, 172UITableViewCell, 149, 174UITableViewCellAccessoryDetail

DisclosureButton, 162UITableViewCellAccessoryDisclosure

Indicator, 162UITableViewController, 78, 139, 141UITableViewIndex, 173UIToolbar, 97-100UITouch, 42, 46-48UIView, 12-13, 37. See also viewsUIViewAnimationTransitionFlipFrom

Left, 64UIViewAnimationTransitionFlipFrom

Right, 64UIViewController

creating, 79-80didRotateFromInterface

Orientation: method, 80Fahrenheit to Celsius conversion

example, 81-90init method, 79overview, 12-13, 77shouldAutorotateToInterface

Orientation: method, 80viewDidAppear: method, 80viewDidDisappear: method, 80

UIViewUIWindow, 37

clearColor method, 173

clickedButtonAtIndex: method, 110

clipping views

accessing pixel-by-pixel values, 54checking views against touches, 53testing touch hits against

bitmap, 54-56to circular path, 51-53

Cocoa Touch

definition, 2progress indicators. See

progress indicatorscolor

of buttons, 242in tables, 177-178of views, 40

commitAnimations method, 59

compiling applications, 33-34

configuring views, 267

connectivity. See networking support

Console tab (Organizer), 31

Contact Add button type, 232

contacts

adding, 277-278contact information

browsing, 274-277displaying, 273-274

images, accessing, 271-273controls

buttons. See buttonscell controls, 180-182disclosures, 162-163keyboards, dismissing, 246-248page indicator controls, 260-263remove controls

creating, 157-158dismissing, 158

segmented controls, 95-97slider thumbs

adding text to, 246creating, 242-245

switches, 239-241

341controls

Page 365: I Phone Developer Cookbook

converting XML into trees, 311-313

Core Animation Transitions

applying to layers, 66-67general Core Animation calls, 68-69undocumented animation types, 67-68

core device information, accessing, 288-289

Core Location

accessing maps with, 286-288Google Maps cell tower

positioning, 279GPS positioning, 278how it works, 279-280latitude and longitude,

retrieving, 280-283overview, 278reverse geocoding to

addresses, 283-286SkyHook Internet provider

positioning, 279SkyHook WiFi positioning, 279

countDownDuration property (UIDatePickerclass), 188

cover flipping, 329

Cover Flow

UICoverFlowLayer class, 327-329view controller

building, 331-332data source methods, 332-333delegate methods, 333sample code listing, 333-336

views, building, 329-331coverFlowFlipDidEnd: method, 332-333

Crash Logs tab (Organizer), 31

Crash Reporter, 25

createSectionList method, 166

custom settings bundles, adding, 297-302

custom URL schemes, subscribingapplications to, 302-304

customizing

alerts, 110buttons, 233-236, 241-242

cell backgrounds, 172-175group tables, 189-194Hello World project, 24-25log functions, 108overlays, 123-127switches, 239-241table cells, 153-155table views, 176-177toolbars, 263-266

DDashcode, 2

data access limits, 8

data recovery, 228-229

data sources

assigning, 140-141functions, 142-143overview, 16-17section-based data sources, 166-170uncovering, 18

databases, interacting with, 308-311

dataFromDictionary: method, 318

date pickers

available styles, 186creating, 186-188selecting times/dates, 188-189

date property (UIDatePicker class), 188

Debugger window, opening, 26

debugging, 26-27

default images, 6

Default.png files, 6

delegates, assigning, 141

delegation, 13-14

deleting

table cellson-the-fly deletion, 159-160with remove controls, 157-158swiping cells, 158-159

tmp files, 197

342 converting XML into trees

Page 366: I Phone Developer Cookbook

deselect method, 152

Detail Disclosure button type, 232

Developer Mode for Crash Reporter, 25

developer program

application identifiers, 29development phones, 28fees, 28

development phones, 28

Devices list (Organizer), 31

dictionaries, converting to and from data, 318

dictionaryFromData: method, 318

didEndElement: method, 312

didRotateFromInterfaceOrientation: method, 80

didSelectRowAtIndexPath: method, 143, 191

didSelectRow: inComponent method, 184

didStartElement: method, 312

disclosures, 162-163

dismissing remove controls, 158

dismissModalViewControllerAnimated:method, 92

displaying

Address Book information, 273-274alerts, 110progress indicators

head-up display progress indicators,117-119

overview, 117progress bar, 121-123spinning circle, 117-120

distribution

ad hoc distribution, 35-36compiling applications for, 33-34

Documents folder (application sandbox)

backups, 197browsing folders by file type, 198finding documents in, 198images, writing to, 207-208table of contents, creating, 199-200

Done button, adding to Navigation bar, 248-250

downloading

image data, 202-203SDK (Software Developer Kit),

downloading, 1Yahoo maps, 287-288

dragging views

multiple draggable views,building, 46-48

overview, 45-46persistence, 48-51UITouch class, 46-48

DragView class, 46-48

drawRect: method, 40

drilling through views withUINavigationController, 100-103

dynamic linking, 133-134

Ee-mail, 320

addresses, browsing, 277editing

identification information, 25-26images, 209-212

enabling proximity sensor, 289-290

energy limits, 9

events

acceleration events, catching, 291-292people picker events,

handling, 276-277exchangeSubviewAtIndex: method, 39, 64

executable files, 4

Ffading views in and out, 60-62

Fahrenheit to Celsius conversion application, 81-90

feedback, live, 256

File Transfer Protocol (FTP), 320

343File Transfer Protocol (FTP)

Page 367: I Phone Developer Cookbook

files

Default.png files, 6executable files, 4HelloWorld_Prefix.pch file, 3Icon.png files, 6info.plist files, 4-6receiving, 320-321sandboxes, 7sending, 320-321UICoverFlowLayer.h, 327-329XIB files

loading, 90overview, 6

finding documents in Documents folder(application sandbox), 198

fixed spaces in toolbars, 266

flipping views, 64-65

floating semitranslucent tables, creating,176-177

FMDB Cocoa wrappers, 309-311

folders

hierarchy, 4Library, 269

font table example, 143-145

foundCharacters: method, 312

framed tables, 179-180

frame geometry

background color, 40CGPoint structure, 39CGRect structure, 39CGSize structure, 39transforms, 40translucency, 40view layout, 40-41

frameworks, 3

freeopen function, 108

FTP (File Transfer Protocol), 320

functions. See methods

Ggeneral Core Animation calls, 68-69

geometry (views)

background color, 40CGPoint structure, 39CGRect structure, 39CGSize structure, 39transforms, 40translucency, 40view layout, 40-41

gestures, 42

getIPAddressForHost: method, 307

Google Maps, 278-279

GPS positioning, 278

grouped tables

fully customized group tables,189-194

grouped preferences tables, 189-194overview, 165-166section headers, 171section-based data sources, 166-170

HhandleOpenURL: method, 303

handleWebRequest: method, 321-322

headers

section headers for grouped tables, 171

UICoverFlowLayer.h, 327-329heads-up display progress indicator, 117-119

heightForRowAtIndexPath: method, 190

Hello World application

applicationDidFinishLaunching:method, 20

classes, 19-20code listing, 20-22creating, 23-24customizing, 24-25debugging, 26-27

344 files

Page 368: I Phone Developer Cookbook

HelloController class, 20identification information,

editing, 25-26main function, 20running skeleton, 24shouldAutorotateToInterface

Orientation: method, 20HelloController class, 20

HelloWorld_Prefix.pch file, 3

hidden buttons in toolbars, 267

hierarchy

of folders, 4of views, 37-39

hit tests, 53

HTML

text view editor creation, 253-255UIWebView class objects, loading

into, 206

IIB (Interface Builder), 2

IBOutlet keyword, 81

Icon.png files, 6

icons. See images

identification information, editing, 25-26

image picker controllers, 78

images

adding to status bar, 131-132Address Book image data,

accessing, 271-273albums, 207application badges, 132-134custom button creation, 233-236default images, 6displaying, 200-205Documents folder (application

sandbox), writing to, 207-208downloading data of, 202-203editing, 209-212libraries, browsing in, 206-207

loading, 200loading into table cells, 149-150snapping with iPhone camera, 212startup images, 51

Info Light/Dark button type, 232

Info.plist files, 4-6, 228

inheritance, multiple, 11

init method, 79

initializing navigation controllers, 91-92

initWithFrame: method, 140, 165

initWithRootViewController: method, 91

initWithStyle: method, 165

Instruments (SDK), 1

interaction limits, 9

Interface Builder (IB), 2

interfaces, ToggleView, 60-62

IP addresses

local IP addresses, retrieving, 305-306site IP addresses, querying, 306-307

iPhone databases, interacting with, 308-311

iPhone developer program

application identifiers, 29development phones, 28fees, 28

iTunes backups, recovering manifest datafrom, 228-229

J-Kkeyboards, dismissing, 246

UITextField control, 247UITextView control, 248

keychain items, storing and retrieving, 313

keychain password keeper wrapper,314-317

multiple keychain values, 318-319persistence, 319-320

keywords, IBOutlet, 81

kSCNetworkFlagsConnectionRequired flag, 304

kSCNetworkFlagsReachable flag, 304

345kSCNetworkFlagsReachable flag

Page 369: I Phone Developer Cookbook

kSCNetworkReachabilityFlagsIsDirect flag, 305

kSCNetworkReachabilityFlagsIsWWAN flag, 304

Llandscape views, centering, 74

latitude, retrieving, 280-283

launchApplicationWithIdentifier: method,149, 326

layers, applying Core Animation Transitionsto, 66-67

layout of views, 40-41

libraries

application sandbox, 197image libraries, browsing in, 206-207Library folder, 269

Library folder, 269

linking, dynamic, 133-134

list tables

creating, 143-145data source functions, 142-143overview, 142reusable cells, 143

live feedback, 256

loading

view controller arrays, 102-103XIB files, 90

loadNibNamed: method, 90

loadView method, 18, 42

local IP addresses, retrieving, 305-306

locations of views, defining, 40

logging results, 108

longitude, retrieving, 280-283

Mmain function, 18-20

Manifest.plist files, 228

maps, accessing with Core Location data,286-288

maximumDate property (UIDatePickerclass), 188

Mdbackup files, 228

media files, recovering from backup, 228-229

Media Player

playing audio via, 215-219playing video via, 217-219

memory limits, 8

menus, creating, 93-95, 115-116

methods

ABPersonCopyImageData, 271ABPersonGetTypeOfProperty, 271ABPersonHasImageData, 271accessoryButtonTappedForRow

WithIndexPath:, 163addStatusBarImageNamed:, 131addSubview:, 38, 42-43addTextFieldWithValue:, 113alloc, 140applicationDidFinishLaunching:,

18-20applicationWillTerminate:, 18, 49AudioServicesAddSystemSound

Completion, 135AudioServicesCreateSystemSound

ID, 135AudioServicesDisposeSystemSound

ID, 135AudioServicesPlaySystemSound, 135bringSubviewToFront:, 39cellForRowAtIndexPath:, 173, 190CGPointMake, 39CGRectFromString, 49CGRectInset, 39, 42CGRectMake, 39CGRectZero, 39clearColor, 173clickedButtonAtIndex:, 110commitAnimations, 59coverFlowFlipDidEnd:, 332-333

346 kSCNetworkReachabilityFlagsIsDirect flag

Page 370: I Phone Developer Cookbook

createSectionList, 166custom log functions, 108dataFromDictionary:, 318deselect, 152dictionaryFromData:, 318didEndElement:, 312didRotateFromInterface

Orientation:, 80didSelectRow:, 184didSelectRowAtIndexPath:, 143, 191didStartElement:, 312dismissModalViewController

Animated:, 92drawRect:, 40exchangeSubviewAtIndex:, 39, 64foundCharacters:, 312freeopen, 108getIPAddressForHost:, 307handleOpenURL:, 303handleWebRequest:, 321-322heightForRowAtIndexPath:, 190init, 79initWithFrame:, 140, 165initWithRootViewController:, 91initWithStyle:, 165launchApplicationWithIdentifier:,

149, 326loadNibNamed:, 90loadView, 18, 42main, 18-20NSStringFromCGRect, 49numberOfComponentsInPicker

View:, 184numberOfRowsInComponent:, 184numberOfRowsInSection:, 167, 190numberOfSectionsInTableView:,

142, 167, 190parseXMLFile:, 312peoplePickerNavigation

Controller, 275

peoplePickerNavigationControllerDidCancel, 276

popToRootViewControllerAnimated:, 102

popToViewController:, 102printf, 108removeFromSuperview, 39removeStatusBarImageNamed:, 131requestFlipLayerAtIndex:, 333respondsToSelector:, 18SCNetworkReachabilityCreateWith

Address, 304, 307sectionIndexTitlesForTableView, 171sendSubviewToBack:, 39setAnimationCurve:, 59setAnimationTransition:, 64setApplicationBadge:, 132setBackgroundColor:, 40SetCustomLeftItem:, 93SetCustomRightItem:, 93setDataSource:, 141setDimsBackground:, 111setNumberOfRows:, 110, 121setRemovedOnCompletion:, 67setStatusBarOrientation:, 41setTag:, 39setText:, 117setTransform:, 72shouldAutorotateToInterface

Orientation:, 18-20, 80showFromTabBar, 115showInView:, 115showsReorderControl:, 161titleForHeaderInSection:, 170, 190titleForRow:, 184toIndexPath:, 161touchesForView:, 46touchesForWindow:, 46trackNotifications:, 16UIApplicationDelegate, 326

347methods

Page 371: I Phone Developer Cookbook

UIImageJPEGRepresentation, 286UIImageWroteToSavedPhotos

Album, 286undocumented API calls, 34-35viewDidAppear:, 80viewDidDisappear:, 80willRotateToInterfaceOrientation:, 44

minimumDate property (UIDatePicker class), 188

minuteInterval property (UIDatePicker class), 188

modal presentation, 92

model

data sources, 16-17UIApplication class, 17

Model-View-Controller. See MVCdesign pattern

moving onscreen objects based onaccelerometer feedback, 292-295

MPArrayQueueFeeder class, 215

MPAVController class, 215

MPItem class, 215-216

MPMoviePlayerController class, 78, 218-219

Mueller, Gus, 309

multiline button displays, 110-112

multiple draggable views, 46-48

multiple inheritance, 11

multiple keychain values, storing, 318-319

multitouch, detecting, 56-58

multiwheel tables, 182-186

MVC (Model-View-Controller) design pattern

controllersdelegation, 13-14target-actions, 14-15

modeldata sources, 16-17UIApplication class, 17

notifications, 15-16overview, 11-12view classes, 12-13

NNavigation bar

adding buttons to, 231-232adding Done button to, 248-250

navigation controllers

creating two-item menu with, 93-95drilling through views with, 100-103initializing, 91-92overview, 78, 91pushing/popping, 92segmented controls, 95-97UINavigationItem class, 92-93UIToolbar class, 97-100

nested subviews, 42-43

network status, testing, 304-305

networking support

custom settings bundles,adding, 297-302

custom URL schemes, subscribingapplications to, 302-304

files, sending and receiving, 320-321iPhone databases, interacting with,

308-311keychain items, storing and

retrieving, 313keychain password keeper

wrapper, 314-317multiple keychain values, 318-319persistence, 319-320

local IP addresses, retrieving, 305-306network status, testing, 304-305push notifications, 325-326site availability, checking, 307-308site IP addresses, querying, 306-307Web servers, building, 321-325XML, converting into trees, 311-313

Newman, Lucas, 68

no-button alerts, 112-113

notifications, 15-16, 325-326

NSDistributedNotificationCenter, 15

348 methods

Page 372: I Phone Developer Cookbook

NSNotificationCenter, 15

NSScanner class, 286

NSString class, 227

NSStringFromCGRect method, 49

NSXMLParser, 311

numberOfComponentsInPickerView: method, 184

numberOfRowsInComponent: method, 184

numberOfRowsInSection: method, 167, 190

numberOfSectionsInTableView: method, 142,167, 190

Oobject-oriented programming, 11

Objective-C 2.0, 2

objects

UIAcceleration, 291UIImage, 271UITableCell, 271

onscreen objects, moving based onaccelerometer feedback, 292-295

opening Debugger window, 26

Organizer

Console tab, 31Crash Logs tab, 31Devices list, 31Projects & Sources list, 30Screenshot tab, 32Summary tab, 31

overlays, “Please, Wait” overlay, 123-127

Ppage indicator controls, 260-263

parseXMLFile: method, 312

people picker events, handling,270, 276-277

peoplePickerNavigationController method, 275

peoplePickerNavigationControllerDidCancelmethod, 276

persistence

adding to viewsrecovering state, 50-51startup images, 51storing state, 48-49

of keychain data, 319-320pictures

adding to status bar, 131-132Address Book image data,

accessing, 271-273albums, 207application badges, 132-134custom button creation, 233-236default images, 6displaying, 200-205Documents folder (application

sandbox), writing to, 207-208downloading data of, 202-203editing, 209-212libraries, browsing in, 206-207loading, 200loading into table cells, 149-150snapping with iPhone camera, 212startup images, 51

pixel-by-pixel values, accessing, 54

platform limitations

application limits, 9data access limits, 8energy limits, 9interaction limits, 9memory limits, 8overview, 8storage limits, 8user behavior limits, 10

playing audio alerts, 134-136

“Please, Wait” overlay, 123-127

popping items off navigation stack, 92

popToRootViewControllerAnimated: method, 102

popToViewController: method, 102

349popToViewController: method

Page 373: I Phone Developer Cookbook

“precooked” button types, 232

preferences tables, grouped, 189-194

printf function, 108

progress bars, 121-123

progress indicators

head-up display progress indicators,117-119

overview, 117progress bar, 121-123spinning circle, 117-120

projects

assembling, 2-3creating, 23-24

Projects & Sources list (Organizer), 30

property lists, displaying, 227

proximity sensor, enabling, 289-290

push notifications, 325-326

pushing items onto navigation stack, 92

QQuartz Core framework, 66. See also Core

Animation Transitions

querying

Address Book, 270-271site IP addresses, 306-307views, 38

Rreading text data, 227

receiving files, 320-321

recording audio, 219-226

recovering

media files, 228-229state, 50-51

redirecting stderr, 108

registering custom URL schemes, 303-304

remove controls

creating, 157-158dismissing, 158

removeFromSuperview method, 39

removeStatusBarImageNamed: method, 131

removing

cell selections, 152-153status bar images, 132subviews, 39

reordering

subviews, 39table cells, 161-162

reorientation support (views), 44-45

requestFlipLayerAtIndex: method, 333

resizing contact images, 272

respondsToSelector: method, 18

results, logging, 108

reusing table cells, 143

reverse geocoding to addresses, 283-286

Rounded Rectangle button type, 232

running Hello World skeleton, 24

Ssandbox (application), 7

Application Support folder, 197Documents folder

backups, 197browsing folders by file type, 198finding documents in, 198table of contents creation, 199-200writing images to, 207-208

Library, 197tmp folder, deleting files from, 197

SCNetworkReachabilityCreateWithAddressmethod, 304, 307

Screenshot tab (Organizer), 32

screenshots, snapping with iPhone camera, 212

scroll-down alerts, 127-131

SDK (Software Developer Kit) download, 1

SDK (Software Developer Kit) limitations, 10

350 “precooked” button types

Page 374: I Phone Developer Cookbook

search bars

creating, 255live feedback with, 256

section headers, adding to grouped tables, 171

section tables, 171

section-based data sources, 166-170

sectionIndexTitlesForTableView method, 171

segmented controls, adding to navigationbars, 95-97

selection sheets, table-based, 145-149

semitranslucent tables, 176-177

sending files, 320-321

sendSubviewToBack: method, 39

sensors

acceleration sensorscatching acceleration events,

291-292locating “up” direction, 290-292moving objects based on

accelerometer feedback, 292-295proximity sensor, 289-290

serialization, 318

serialized data, appearance of, 228

servers, Web, 321-325

services, connecting to. Seenetworking support

setAnimationCurve: method, 59

setAnimationTransition: method, 64

setApplicationBadge: method, 132

setBackgroundColor: method, 40

SetCustomLeftItem: method, 93

SetCustomRightItem: method, 93

setDataSource: method, 141

setDimsBackground: method, 111

setNumberOfRows: method, 110, 121

setRemovedOnCompletion: method, 67

setStatusBarOrientation: method, 41

setTag: method, 39

setText: method, 117

Settings screen, 297-302

setTransform: method, 72

shouldAutorotateToInterfaceOrientation:method, 18-20, 80

showFromTabBar method, 115

showInView: method, 115

showsReorderControl: method, 161

Simulator, 2

site availability, checking, 307-308

site IP addresses, querying, 306-307

SkyHook Wireless, 278-279

slide-down alerts, 127-131

slider thumbs

adding text to, 246creating, 242-245

sliding onscreen objects based onaccelerometer feedback, 292-295

Software Developer Kit (SDK) download, 1

Software Developer Kit (SDK) limitations, 10

sound alerts, 134-136

spaces, fixed spaces in toolbars, 266

spinning circle progress indicator, 117-120

SpringBoard, 4, 133-134

SQLite access routine, 309

startup images, 51

state

recovering, 50-51storing, 48-49

status of network, testing, 304-305

status bar, adding images to, 131-132

stderr, redirecting, 108

storage limits, 8

storing

keychain items, 313keychain password keeper

wrapper, 314-317multiple keychain values, 318-319persistence, 319-320

state, 48-49stuctures, 39

351structures

Page 375: I Phone Developer Cookbook

subviews, 38-39

adding, 38, 42-43nested subviews, 42-43querying, 38removing, 39reordering, 39reorientation support, 44-45tagging, 39

Summary tab (Organizer), 31

swapping views, 62-64

swiping

table cells, 158-159views, 69-71

switches, customizing, 239-241

System Audio services, 214

Ttab bars

creating, 103-105overview, 78, 103

table-based selection sheets, 145-149

tables

cellsadding, 159-160alternate blue and white cells,

creating, 177-178cell selections, removing, 152-153check mark selections, creating,

155-156coupled cell controls, 180-182custom backgrounds, 172-175customizing, 153-155deleting, 157-160disclosures, 162-163loading images into, 149-150reordering, 161-162reusing, 143swiping, 158-159text styles,setting, 151-152

creatingdata sources, 140-141delegates, 141table views, 140

data sources, 142-143date pickers

available styles, 186creating, 186-188selecting times/dates, 188-189

of documents, 199-200floating semitranslucent tables,

creating, 176-177font table example, 143-145framed tables, 179-180grouped tables

fully customized group tables, 189-194

grouped preferences tables, 189-194

overview, 165-166section headers, 171section-based data sources, 166-170

list tablescreating, 143-145data source functions, 142-143overview, 142reusable cells, 143

multiwheel tables, 182-186section tables, 171table-based selection sheets, 145-149UITableView class, 139-140UITableViewController class, 139-141views, customizing, 176-177

target-actions, 14-15

temperature conversion application, 81-90

testing

applications, 32-33network status, 304-305

tethering, 32

352 subviews

Page 376: I Phone Developer Cookbook

text

customizing in switches, 240-241property lists, 227reading data, 227slider thumbs, adding to, 246soliciting text input from

users, 113-115text field keyboards, dismissing

UITextField control, 247UITextView control, 248

text styles, assigning to cells, 151-152text views

adding Undo button to, 250-251HTML editor text view

creation, 253-255thumbnails, adding to table cells, 149-150

titleForHeaderInSection: method, 170, 190

titleForRow: forComponent method, 184

tmp folder (application sandbox), deletingfiles from, 197

ToggleView interface, 60-62

toIndexPath: method, 161

toolbars

adding to navigation bars, 97-100alignment in, 267buttons, 267customizing, 263-266fixed spaces in, 266hidden buttons, 267Navigation bar

adding buttons to, 231-232adding Done button to, 248-250

search barscreating, 255live feedback with, 256

touches

checking views against, 53multitouch, detecting, 56-58testing touch hits against

bitmap, 54-56

touchesForView: method, 46

touchesForWindow: method, 46

trackNotifications: method, 16

transform views, 40

transforming views, 72-74

transitions

Core Animation Transitionsapplying to layers, 66-67general Core Animation

calls, 68-69undocumented animation

types, 67-68views, 64

translucency of views, 40

transparency, animations with transparencychanges, 60-62

trees

converting XML into, 311-313nodes, defining, 311

two-item menus, creating withUINavigationController, 93-95

UU.S. Department of Defense Global

Positioning System, 278

UIAcceleration object, 291

UIActionSheet class, 107, 242

UIActivityIndicatorView class, 117-120

UIActivityIndicatorViewStyleGray class, 119

UIActivityIndicatorViewStyleWhite class, 119

UIActivityIndicatorViewStyleWhiteLarge class, 119

UIAlertSheet class, 108

UIAlertView class, 107-109, 241-242. Seealso alerts

UIAlertViewDelegate protocol, 110

UIApplication class, 17

UIApplicationDelegate method, 326

UIApplicationWillChangeStatusBarOrientationNotification class, 16

UIBarButtonItem class, 231-232

353UIBarButtonItem class

Page 377: I Phone Developer Cookbook

UIButton class, 232

button animations, 236-237custom button creation, 233-236

UICalloutView class, 258-260

UIControl class, 239-241

UICoverFlowLayer class, 327-329

UIDatePicker class, 186

properties, 188selecting times/dates, 188-189

UIDevice class, 288-289

UIDeviceOrientationDidChangeNotificationclass, 16

UIGlassButton class, 236

UIImage objects, 271

UIImageJPEGRepresentation method, 286

UIImagePickerController class, 78

browsing image libraries, 206-207editing images, 209-212

UIImageView class, 201

UIImageWroteToSavedPhotosAlbum method, 286

UINavigationController class

controller initialization, 91-92creating two-item menu with, 93-95drilling through views with, 100-103modal presentation, 92overview, 78, 91pushing/popping controllers, 92segmented controls, 95-97UINavigationItem class, 92-93UIToolbar class, 97-100

UINavigationItem class, 92-93

UIPageControl class, 260-263

UIPickerView instances, 182-186

UIProgressHUD class, 117-119

UIProgressView class, 121-123

UIRemoveControl class, 157-158

UIScrollView class, 202-203

UISearchBar class, 255-256

UISlider class, 242-245

UISwitch class, 240-241

UITabBar class, 263

UITabBarController class

creating, 103-105overview, 78, 103

UITableCell object, 271

UITableView class, 139-140, 172

UITableViewCell class, 149, 174

UITableViewCellAccessoryCheckmark accessory type, 155

UITableViewCellAccessoryDetailDisclosureButton, 162

UITableViewCellAccessoryDisclosureIndicator, 162

UITableViewController class, 78, 139-141

UITableViewDataSource protocol, 141

UITableViewIndex class, 173

UITextField class, 246-247

UITextView class

adding Done button to Navigationbar, 248-250

creating text view-based HTML editors, 253-255

dismissing keyboards, 248UIToolbar class

adding to navigation bars, 97-100customizing toolbars, 263-266

UITouch class, 42, 46-48

UIView class, 37. See also views

animating button responses, 238-239overview, 12-13

UIViewAnimationTransitionFlipFromLeftclass, 64

UIViewAnimationTransitionFlipFromRightclass, 64

UIViewController class

creating, 79-80didRotateFromInterfaceOrientation:

method, 80Fahrenheit to Celsius conversion

example, 81-90init method, 79

354 UIButton class

Page 378: I Phone Developer Cookbook

overview, 12-13, 77shouldAutorotateToInterface

Orientation: method, 80viewDidAppear: method, 80viewDidDisappear: method, 80

UIWebView class

loading HTML source, 206viewing images, 200, 203-205

UIWindow class, 37

Undo button, adding to text views, 250-251

undocumented animation types, 67-68

undocumented API calls, 34-35

“up” direction, locating with accelerationsensors, 290-292

URLs, custom URL schemes, 302-304

users

alerting. See alertssoliciting text input from, 113-115

Vvibration alerts, 136

video, playing, 217-219

view controllers

ABNewPersonViewController class, 78

delegation, 13-14loading view controller

arrays, 102-103MPMoviePlayerController class, 78overview, 13, 77popping back to root, 102target-actions, 14-15UIImagePickerController class, 78UINavigationController class

controller initialization, 91-92creating two-item menu

with, 93-95drilling through views

with, 100-103modal presentation, 92

overview, 78, 91pushing/popping controllers, 92segmented controls, 95-97UINavigationItem class, 92-93UIToolbar class, 97-100

UITabBarController classcreating, 103-105overview, 78, 103

UITableViewController class, 78UIViewController class

creating, 79-80didRotateFromInterface

Orientation: method, 80Fahrenheit to Celsius conversion

example, 81-90init method, 79overview, 77shouldAutorotateToInterface

Orientation: method, 80viewDidAppear: method, 80viewDidDisappear: method, 80

viewDidAppear: method, 80

viewDidDisappear: method, 80

views, 77. See also view controllers

animationsCore Animation Transitions, 66-69flipping views, 64-65methods, 60overview, 59swapping views, 62-64transitions, 64-65with transparency changes, 60-62

background color, 40clipping

accessing pixel-by-pixel values, 54checking views against touches, 53testing touch hits against

bitmap, 54-56to circular path, 51-53

configuring, 267

355views

Page 379: I Phone Developer Cookbook

Cover Flow viewsbuilding, 329-331view controller, 331-336

disclosure views, 162-163dragging

multiple draggable views,building, 46-48

overview, 45-46persistence, 48-51UITouch class, 46-48

drilling through withUINavigationController, 100-103

fading in and out, 60-62flipping, 64-65frame geometry

CGPoint structure, 39CGRect structure, 39CGSize structure, 39transforms, 40view layout, 40-41

gestures, 42hierarchy, 37-39landscape views, centering, 74locations, defining, 40multitouch, detecting, 56-58overview, 12-13, 37querying, 38subviews, 38-39

adding, 38, 42-43nested subviews, 42-43querying, 38removing, 39reordering, 39reorientation support, 44-45tagging, 39

swapping, 62-64swiping, 69-71table views

customizing, 176-177laying out, 140UITableView class, 139

ToggleView interface, 60-62transforming, 72-74transitions, 64translucency, 40UIActivityIndicatorView, 117-120UIAlertView, 107-109. See also alertsUIPickerView instances for

multicolumn selection, 182-186UIProgressHUD, 117-119UIProgressView, 121-123UIVie, 37UIWindow, 37

WWeb servers, building, 321-325

white and blue table cells, creating, 177-178

willRotateToInterfaceOrientation: method, 44

X-Y-ZXCode

OrganizerConsole tab, 31Crash Logs tab, 31Devices list, 31Projects & Sources list, 30Screenshot tab, 32Summary tab, 31

overview, 1XIB files

loading, 90overview, 6

XML, converting into trees, 311-313

Yahoo

maps, accessing with Core Location,286-288

ZoneTag, 283-286

ZoneTag, 283-286

356 views

Page 380: I Phone Developer Cookbook