Cocoa Programming For OS X. The Big Nerd Ranch Guide (PDFDrive)
Cocoa Programming For OS X. The Big Nerd Ranch Guide (PDFDrive)
All rights reserved. Printed in the United States of America. This publication is
protected by copyright, and permission must be obtained from the publisher
prior to any prohibited reproduction, storage in a retrieval system, or
transmission in any form or by any means, electronic, mechanical,
photocopying, recording, or likewise. For information regarding permissions,
contact
The 10-gallon hat with propeller logo is a trademark of Big Nerd Ranch, LLC.
Exclusive worldwide distribution of the English edition of this book by
The authors and publisher have taken care in writing and printing this book but
The authors and publisher have taken care in writing and printing this book but
make no expressed or implied warranty of any kind and assume no responsibility
for errors or omissions. No liability is assumed for incidental or consequential
damages in connection with or arising out of the use of the information or
programs contained herein.
Many of the designations used by manufacturers and sellers to distinguish their
products are claimed as trademarks. Where those designations appear in this
book, and the publisher was aware of a trademark claim, the designations have
been printed with initial capital letters or in all capitals.
Print ISBN-10 0134076958
Print ISBN-13 978-0134076959
Introducing Swift
Types in Swift
Using Standard Types
Inferring types
Specifying types
Literals and subscripting
Initializers
Properties
Instance methods
Optionals
Subscripting dictionaries
Loops and String Interpolation
Enumerations and the Switch Statement
Enumerations and raw values
Exploring Apple’s Swift Documentation
3. Structures and Classes
Structures
Instance methods
Operator Overloading
Classes
Designated and convenience initializers
Add an instance method
Inheritance
Computed Properties
Reference and Value Types
Implications of reference and value types
Choosing between reference and value types
Making Types Printable
Swift and Objective-C
Working with Foundation Types
Basic bridging
Bridging with collections
Runtime Errors
More Exploring of Apple’s Swift Documentation
Challenge: Safe Landing
Challenge: Vector Angle
4. Memory Management
Setting up RGBWell
Creating the MainWindowController class
Creating an empty XIB file
Creating an instance of MainWindowController
Connecting a window controller and its window
About Controls
Working with Controls
A word about NSCell
Connecting the slider’s target and action
A continuous control
Setting the slider’s range values
Adding two more sliders
NSColorWell and NSColor
Disabling a control
Using the Documentation
Changing the color of the color well
Controls and Outlets
Implicitly unwrapped optionals
For the More Curious: More on NSColor
For the More Curious: Setting the Target Programmatically
Challenge: Busy Board
Debugging Hints
6. Delegation
Setting up SpeakLine
Creating and using an Xcode snippet
Creating the user interface
Synthesizing Speech
Updating Buttons
Delegation
Being a delegate
Implementing another delegate
Common errors in implementing a delegate
Cocoa classes that have delegates
Delegate protocols and notifications
NSApplication and NSApplicationDelegate
The main event loop
For the More Curious: How Optional Delegate Methods Work
Challenge: Enforcing a Window’s Aspect Ratio
7. Working with Table Views
Bindings
Setting up Thermostat
Using bindings
Key-value observing
Making keys observable
Binding other attributes
KVC and Property Accessors
KVC and nil
Debugging Bindings
Using the Debugger
Using breakpoints
Stepping through code
The LLDB console
Using the debugger to see bindings in action
For the More Curious: Key Paths
For the More Curious: More on Key-Value Observing
For the More Curious: Dependent Keys
Challenge: Convert RGBWell to Use Bindings
9. NSArrayController
Formatters
Formatters, programmatically
Formatters and a control’s objectValue
Formatters and localization
Validation with Key-Value Coding
Adding Key-Value validation to RaiseMan
For the More Curious: NSValueTransformer
11. NSUndoManager
NSUserDefaults
Adding User Defaults to SpeakLine
Create Names for the Defaults
Register Factory Defaults for the Preferences
Reading the Preferences
Reflecting the Preferences in the UI
Writing the Preferences to User Defaults
Storing the User Defaults
What Can Be Stored in NSUserDefaults?
Precedence of Types of Defaults
What is the User’s Defaults Database?
For the More Curious: Reading/Writing Defaults from the Command Line
For the More Curious: NSUserDefaultsController
Challenge: Reset Preferences
15. Alerts and Closures
NSAlert
Modals and Sheets
Completion Handlers and Closures
Closures and capturing
Make the User Confirm the Deletion
For the More Curious: Functional Methods and Minimizing Closure
Syntax
Challenge: Don’t Fire Them Quite Yet
Challenge: Different Messages for Different Situations
16. Using Notifications
What Notifications Are
What Notifications Are Not
NSNotification
NSNotificationCenter
Starting the Chatter Application
Using Notifications in Chatter
For the More Curious: Delegates and Notifications
Challenge: Beep-beep!
Challenge: Add Usernames
Challenge: Colored Text
Challenge: Disabling the Send Button
17. NSView and Drawing
NSResponder
NSEvent
Getting Mouse Events
Click to Roll
Improving Hit Detection
Gesture Recognizers
Challenge: NSBezierPath-based Hit Testing
Challenge: A Drawing App
19. Keyboard Events
NSResponder
NSEvent
Adding Keyboard Input to DieView
Accept first responder
Receive keyboard events
Putting the dice in Dice
Focus Rings
The Key View Loop
For the More Curious: Rollovers
20. Drawing Text with Attributes
NSFont
NSAttributedString
Drawing Strings and Attributed Strings
Drawing Text Die Faces
Extensions
Getting Your View to Generate PDF Data
For the More Curious: NSFontManager
Challenge: Color Text as SpeakLine Speaks It
21. Pasteboards and Nil-Targeted Actions
NSPasteboard
Add Cut, Copy, and Paste to Dice
Nil-Targeted Actions
Looking at the XIB file
Menu Item Validation
For the More Curious: Which Object Sends the Action Message?
For the More Curious: UTIs and the Pasteboard
Custom UTIs
For the More Curious: Lazy Copying
Challenge: Write Multiple Representations
Challenge: Menu Item
22. Drag-and-Drop
NSTimer-based Animation
How Timers Work
NSTimer and Strong/Weak References
For the More Curious: NSRunLoop
24. Sheets
Adding a Sheet
Create the Window Controller
Set Up the Menu Item
Lay Out the Interface
Configuring the Die Views
Present the Sheet
Modal Windows
Encapsulating Presentation APIs
Challenge: Encapsulate Sheet Presentation
Challenge: Add Menu Item Validation
25. Auto Layout
Size constraints
Intrinsic Content Size
Creating Layout Constraints Programmatically
Visual Format Language
Does Not Compute, Part 1: Unsatisfiable Constraints
Does Not Compute, Part 2: Ambiguous Layout
For the More Curious: Autoresizing Masks
Challenge: Add Vertical Constraints
Challenge: Add Constraints Programmatically
26. Localization and Bundles
Testing in Xcode
Your First Test
A Note on Literals in Testing
Creating a Consistent Testing Environment
Sharing Constants
Refactoring for Testing
For the More Curious: Access Modifiers
For the More Curious: Asynchronous Testing
Challenge: Make Course Implement Equatable
Challenge: Improve Test Coverage of Web Service Responses
Challenge: Test Invalid JSON Dictionary
30. View Controllers
NSViewController
Starting the ViewControl Application
Windows, Controllers, and Memory Management
Container View Controllers
Add a Tab View Controller
View Controllers vs. Window Controllers
Considerations for OS X 10.9 and Earlier
Challenge: SpeakLineViewController
Challenge: Programmatic View Controller
Challenge: Add a Window Controller
31. View Swapping and Custom Container View Controllers
View Swapping
NerdTabViewController
Adding Tab Images
Challenge: Boxless NerdTabViewController
Challenge: NerdSplitViewController
Challenge: Draggable Divider
32. Storyboards
CALayer
Scattered
Implicit Animation and Actions
More on CALayer
Challenge: Show Filenames
Challenge: Reposition Image Layers
34. Concurrency
Multithreading
A Deep Chasm Opens Before You
Improving Scattered: Time Profiling in Instruments
Introducing Instruments
Analyzing output from Instruments
NSOperationQueue
Multithreaded Scattered
Thread synchronization
For the More Curious: Faster Scattered
Challenge: An Even Better Scattered
35. NSTask
ZIPspector
Asynchronous Reads
iPing
Challenge: .tar and .tgz Files
36. Distributing Your App
Build Configurations
Preprocessor Directives: Using Build Configurations to Change Behavior
Creating a Release Build
A Few Words on Installers
App Sandbox
Entitlements
Containers
Mediated file access and Powerbox
The Mac App Store
Receipt Validation
Local receipt verification
Server-based verification
37. Afterword
Index
Introduction
If you are developing applications for OS X, or are hoping to do so, this book
will be your foundation and will help you understand Cocoa, the set of
frameworks for developing applications for OS X. You, the developer, are going
to love developing for OS X because Cocoa will enable you to write full-
featured applications in a more efficient and elegant manner.
About This Book
This book covers the major design patterns of Cocoa and includes an
introduction to the Swift language. It will also get you started with the most
commonly-used developer tools: Xcode and Instruments. After reading this book,
you will understand these major design patterns which will enable you to
understand and use Apple’s documentation – a critical part of any Cocoa
developer’s toolkit – as well as build your own Cocoa applications from scratch.
This book teaches ideas and provides hands-on exercises that show these ideas in
action. Each chapter will guide you through the process of building or adding
features to an application.
Often, we will ask you to do something and explain the details or theory
afterward. If you are confused, read a little more. Usually, the help you seek will
be only a paragraph or two away.
Because of the hands-on nature of the book, it is essential that you do the
exercises and not just read the words. Doing the exercises will help build the
kind of solid understanding that will enable you to develop on your own when
you are finished with this book. You will also learn a great deal from making
mistakes, reading error messages, and figuring out what went wrong – practical
experience you can’t get from reading alone. At first, you may want to stick with
what we show you, but later in the book when you are more comfortable with
the environment, you should feel free to experiment with the exercises and add
your own ideas.
Most chapters end with one or two challenge exercises. These exercises are
important to do as well. Taking on these challenges gives you the opportunity to
test your skills and problem-solve on your own.
You can get help with this book at bignerdranch.com/books, where you will find
errata and downloadable solutions for the exercises. You can also post questions
and find relevant conversations on the Big Nerd Ranch forums at
forums.bignerdranch.com.
We ask that you not use the downloadable solutions as a shortcut for doing the
exercises. The act of typing in code has far more impact on your learning than
most people realize. By typing the code yourself (and, yes, making mistakes),
you will absorb patterns and develop instincts about Cocoa programming, and
you will absorb patterns and develop instincts about Cocoa programming, and
you will miss out on these benefits if you rely on the solutions or copy and paste
the code instead.
There is a lot of code in this book. Through that code, we will introduce you to
the idioms of the Cocoa community. Our hope is that by presenting exemplary
code, we can help you to become more than a Cocoa developer – a stylish Cocoa
developer.
Most of the time, Cocoa fulfills the following promise: Common things are easy,
and uncommon things are possible. If you find yourself writing many lines of
code to do something rather ordinary, you are probably on the wrong track.
There is a popular adage in the community which you should bear in mind:
Don’t fight the framework. Cocoa is opinionated and you will benefit greatly
from adapting your way of doing things to its way of doing things.
Prerequisites
This book is written for programmers and assumes that you are familiar with
basic programming concepts (like functions, variables, and loops) as well as
object-oriented concepts (like classes, objects, and inheritance). If you do not fit
this profile, you will find this book tough going. You are not expected to have
any experience with Mac programming.
One of the challenges of learning Cocoa programming is learning the Swift
language. If you have a basic foundation in programming and know something
about objects, you will find learning Swift to be easy. This book includes three
chapters to introduce to you to the language. Then you will learn more Swift as
you build Cocoa applications throughout the book. If you would prefer a gentler
introduction, start with Apple’s The Swift Programming Language, available in
the iBooks store or from developer.apple.com/swift, offers a more gentle
introduction. Or, if you can wait until Summer 2015, you can read Swift
Programming: The Big Nerd Ranch Guide first.
This is a hands-on book and assumes that you have access to OS X and the
developer tools. The book requires OS X Yosemite (10.10) or higher. The
exercises are written for Xcode 6.3 and Swift 1.2.
We strongly recommend that you join Apple’s Mac Developer Program at
developer.apple.com/programs. Joining the program gives you access to pre-release
versions of Xcode and OS X. These can be very useful when trying to stay ahead
of Apple’s development curve. In addition, you must be a member of the
developer program to distribute your apps on the App Store.
Typographical conventions
To make the book easier to follow, we have used several typographical
conventions.
In Swift, class names are always capitalized. In this book, we have also made
them appear in a monospaced bold font. In Swift, method names start with a
lowercase letter. Here, method names will also appear in a monospaced bold
font. For example, you might see “The class NSWindowController has the method
showWindow(_:).”
Other literals, including instance variable names that you would see in code, will
appear in a regular monospaced font. Also, filenames will appear in this same
font. Thus, you might see “In MyClass.swift, set the optional favoriteColor to
nil.”
Code samples in this book appear in the regular monospaced font. New portions,
which you will need to type yourself, will appear in bold. Code that you should
delete is struck-through.
Using an eBook
If you are reading this book on a Kindle, KindleFire, Kindle for Android, or
Kindle for iPad, we want to point out that reading the code may be tricky at
times. Longer lines of code may wrap to a second line depending on your
selected font size. Even more problematic, on Kindle for iPad, wrapping code
lines may be hyphenated. If you type these extra hyphens in, they will definitely
break your code.
The longest lines of code in this book are 86 monospace characters, like this one.
let fetchResult: [AnyObject]? =
managedObjectContext.executeFetchRequest(fetchRequest)
You can play with your eReader’s settings to find the best for viewing long code
lines.
When you get to the point where you are actually typing in code, we strongly
suggest opening the book on your PC or Mac in the appropriate Kindle
application. (Kindle for Mac and Kindle for PC are free applications you can
download from Amazon.com.) Make the application window large enough so
that you can see the code with no wrapping lines. You will also be able to see the
figures in full detail.
On the lefthand side of the panel, find the OS X section and from that section,
select Application. From the choices presented on the right, select the Cocoa
Application template and click Next.
The next panel asks you for some project details (Figure 1.3). For Product Name,
enter RandomPassword. For organization name and identifier, you can use Big Nerd
Ranch and com.bignerdranch or you can use your company’s name and
com.yourcompany. From the Language drop-down menu, select Swift. Ensure that the
Use Storyboards, Create Document-Based Application, and Use Core Data boxes are not
checked. Then click Next.
Figure 1.3 Choosing project options
Finally, Xcode asks you where to create the folder that will contain the
RandomPassword project. The default location is the root of your home folder, but
that can get crowded. Instead, find the New Folder button at the bottom left of the
panel and create a new subfolder. Name it something like CocoaProjects and save
your projects there. After you have selected where to save the project, click
Create.
Model-View-Controller
In object-oriented programming, one of the principal design patterns is Model-
View-Controller, or MVC. This design pattern says that a well-designed
application has three separate layers: model, view, and controller. Let’s start
with the view layer.
The view layer is made up of view objects, or views. You use these objects to
compose the application’s user interface. Buttons and text fields are examples of
views. View classes tend to be standard classes that can be used across many
applications.
In RandomPassword, your view layer will consist of four standard views: a window,
a content view, a button, and a text field. These are shown in Figure 1.5.
Figure 1.5 RandomPassword’s four view objects
The window, button, and text field are familiar concepts. As a user, you have
clicked buttons, closed windows, and typed in text fields. The content view may
be less familiar. It is a transparent rectangle, and its job is to contain the button
and the text field. Typically in Cocoa, whenever you have a window, you also
have a content view that contains other view objects.
Views exist in a hierarchy. The view hierarchy for RandomPassword is shown in
Figure 1.6.
Figure 1.6 RandomPassword’s view hierarchy
Each view object is an instance of an existing Cocoa class. RandomPassword will
have instances of NSWindow, NSView, NSButton, and NSTextField. (The NS prefix
indicates that the class is part of the Cocoa frameworks. NS stands for
NeXTSTEP, the platform for which the frameworks were originally created.)
The model layer is an abstraction of the real-world problem that the application
is built to solve. The real-world problem in RandomPassword is the need to create
passwords that are hard for other people to guess.
Many applications solve problems that are complex. Models for these
applications might include various data, data stores, calculations, and web
services.
RandomPassword, on the other hand, solves a simple problem. Its model layer will
consist only of an array of characters and two functions that produce a random
string.
The controller layer manages the flow of the application. It consists of controller
objects, or controllers. Unlike view classes, controller classes are usually custom
classes that you write for a specific application.
RandomPassword will have two controllers: an instance of a class named
AppDelegate and an instance of a class named MainWindowController: The
AppDelegate’s job is to manage the interaction between your application and the
system. The system launches the application and notifies the app delegate. The
app delegate then creates the window controller and puts the window (along with
the rest of the view objects) on screen.
The MainWindowController’s job is to coordinate with the view and model layers
to get the user the requested random password. It receives information from the
view layer, gets data from the model layer, and then updates the view layer with
that data.
Figure 1.7 shows the model, views, and controllers that comprise RandomPassword.
Figure 1.7 Object diagram for RandomPassword
Creating the MainWindowController class
Time to get to work. In the project navigator, select the group named
RandomPassword. (Remember, groups have folder icons.) This is the group that
contains the AppDelegate class file, and it is the group in which you want to
create the MainWindowController class file.
Next, from Xcode’s main menu bar, select File → New → File... (or use the keyboard
shortcut Command-N).
In the panel that appears, find the OS X section on the left and select Source. From
the choices that appear on the right, select Cocoa Class. Then click Next (Figure 1.8).
Figure 1.8 Choosing a template for the new class
In the next panel, name the class MainWindowController. Make it a subclass of
NSWindowController and ensure that the box labeled Also create XIB file for user interface
is checked. For the language, choose Swift. Then click Next (Figure 1.9).
Figure 1.9 Choosing options for the new class
Xcode will ask where to save the new files. Accept the default location and click
Create.
Notice that there are two new files in the project navigator:
MainWindowController.swift and MainWindowController.xib.
MainWindowController.swift is a Swift file. Swift is the language that you will
use to develop Cocoa apps. This file contains a skeleton of the
MainWindowController class that you will flesh out later.
If you see a stack of icons in the dock rather than a list of object names, you are
looking at the dock’s icon view. Click the Show Document Outline button in the
bottom-left corner of the canvas to see the document outline.
In the document outline, select Window at the bottom of the list, and the window
will be highlighted in the canvas. To see the content view, click the disclosure
arrow next to Window and select View.
At the bottom of the library, find the search bar and start typing “text field.” The
library results will update to show the matching object types. Find the Text Field
object type.
A word of caution: Do not press Return when searching the object library.
Pressing Return will put a new object on the canvas, but it will not be on the
window. If you create a an object in the canvas but not on the window, simply
select the object in the canvas and press Delete. Then keep reading to see how to
create an object where you want it.
Drag from the Text Field type in the library onto the window in the canvas
(Figure 1.12). When you release the mouse, a text field object will appear on the
window where you dropped it and be added to the XIB.
Figure 1.12 Dragging a text field onto the window
In the document outline, the window’s View now has a disclosure arrow, and
clicking it will reveal an object named Text Field.
Notice that you are creating a hierarchy of view objects that matches the one
shown in Figure 1.6. The window is the root, and it has a single view as its direct
child. The view has a text field child and soon will have another child – a button.
(Ignore the Text Cell child of the text field for now. The cell of a text field is an
internal detail that you will not be interacting with.) Next, return to the object
library and start typing “button”. The search will yield many button types. These
types are all instances of the NSButton class and differ in their appearance and
behavior. For a basic button, find Push Button and drag it onto the window below
the text field.
Figure 1.13 Dragging a button onto the window
Configuring view objects
The new button needs a better title than Button. To change the button’s title,
double-click the button in the canvas and type Generate Password. In the document
outline, notice that the button object is now labeled Generate Password.
The window needs a better title, too. You cannot change it directly in the canvas;
you have to use the attributes inspector. The attributes inspector lets you view
and edit the attributes of the currently-selected object.
First, select Window in the document outline. Then head to the utilities area on the
right side of the Xcode window. At the top of the utilities area is the inspector bar.
From the inspector bar, select the icon to reveal the attributes inspector
(Figure 1.14).
Figure 1.14 Showing the attributes inspector
Find the Title attribute, which is currently set to Window. Change this attribute to
Password Generator.
Now select the text field in the document outline or on the canvas and return to
the attributes inspector. There are three attributes of the text field that you are
going to change to improve the user interface.
First, find the Alignment attribute (Figure 1.15). Change the alignment to centered.
Second, find the Font attribute. Your text field only has one job, so it might as
well do it loud and proud. Change the font attribute from the default System Regular
to System 26. This will increase the font size of the text.
Third, find the Behavior attribute. This attribute describes how users will be able to
interact with the text field. The default is Editable, which means that users can
select and edit the text. You want users to be able to select the text (so that they
can copy it and paste it elsewhere), but you do not want them to be able to edit
the text. To keep users from editing passwords, change the Behavior attribute to
Selectable.
Not only is the window empty, it also has the wrong title. This is not your
window. It is a window that the template created for the AppDelegate controller
to control. To understand why this is the window you are seeing, let’s look at the
app’s structure, diagrammed in Figure 1.18.
Figure 1.18 window of the AppDelegate is shown
You can see two problems. The system works with the AppDelegate to launch
and manage the application. The AppDelegate only knows about its window.
Thus, the window you created in MainWindowController.xib is not shown to the
user. Figure 1.19 shows the structure that will put your window on screen.
Figure 1.19 window of the MainWindowController is shown
func applicationDidFinishLaunching(aNotification:
NSNotification) {
// Insert code here to initialize your
application
}
...
In the listing above, the code to be added is in bold and the code to be deleted is
struck-through. Code that is neither bold nor struck-through is existing code for
you to use as a landmark. These are conventions we will follow throughout the
book.
Finally, add code to the applicationDidFinishLaunching(_:) method that
creates an instance of MainWindowController and shows its window.
class AppDelegate: NSObject, NSApplicationDelegate {
var mainWindowController: MainWindowController?
func applicationDidFinishLaunching(aNotification:
NSNotification) {
// Create a window controller with a XIB file
of the same name
let mainWindowController =
MainWindowController(windowNibName:
"MainWindowController")
// Put the window of the window controller on
screen
mainWindowController.showWindow(self)
Creating an outlet
Open MainWindowController.swift and add the following code. (Again, just type
the code in for now; you will learn how it works in the next chapter.)
class MainWindowController: NSWindowController {
}
This code creates an outlet named textField in the MainWindowController class.
However, this outlet does not yet reference an object. The @IBOutlet qualifier
on textField tells Xcode that you are going to connect this outlet to an object
using Interface Builder. Let’s do that now.
Connecting an outlet
Reopen MainWindowController.xib. In the document outline, under the Placeholders
heading, find the File's Owner object. In this XIB file, File's Owner stands for an
instance of MainWindowController. Why do you need a placeholder? In
Interface Builder, there is no existing instance of MainWindowController, and there
will not be until the application has finished launching. To make a connection to
the MainWindowController, you connect to File's Owner.
Right-click (or Control-click) on File's Owner to reveal the connections panel.
Within this panel, find the Outlets section (Figure 1.20).
Figure 1.20 Outlets exposed in connections panel
The first outlet is named textField, and it is the outlet that you just added to the
MainWindowController class. The second outlet, named window, is an outlet that
MainWindowController inherits from its superclass, NSWindowController.
Unlike the textField outlet, the window outlet is already connected – it
references the window object in this XIB file. The template connected the window
outlet when it created MainWindowController.xib along with the
MainWindowController class.
To connect the textField outlet, drag from the empty circle next to textField in
the connections panel to the text field object in the canvas (Figure 1.21). When
you release the mouse, the connection will be made.
Figure 1.21 Dragging from connections panel to text field
Now your code can use the textField outlet to access the text field object that
will be unarchived from the XIB file when the application is launched.
}
Adding @IBAction to generatePassword(_:) tells Xcode that this method will be
the action of some object and that you will assign this action in Interface Builder.
Connecting actions
The action of a button has a partner: the target of the button. The action is the
name of the method to be executed, and the target is the object whose class
implements that method.
To connect the action of a button in Interface Builder, you Control-drag from the
button to its target. For your button, you know that the method to be executed is
generatePassword(_:). Thus, the target is the instance of MainWindowController.
Now when the user clicks the Generate Password button, the generatePassword(_:)
method will be executed.
You may have noticed that the action is generatePassword:, and the name of the
method is generatePassword(_:). This is not sloppy typing on our part. We will
explain the reason for the difference in Chapter 3.
Take another look at the object diagram for RandomPassword (Figure 1.23) to see
what you have accomplished. Your view layer is now correctly connected to the
controller layer, and these connections are archived in
MainWindowController.xib.
If the app does not build or does not run as you expect, check your connections
in MainWindowController.xib. You can do this in the connections inspector.
Select File's Owner in the document outline. Then, in the inspector pane, select the
icon to reveal the connections inspector. This inspector displays the
connections that you created. If you mouse over a connection, it will highlight
the connected object in the canvas. Your connections should look like
Figure 1.25:
Figure 1.25 Confirming connections are correct in the connections
inspector
If you made a mistake, you can delete the bad connection by clicking the x on the
connection and remake it as described earlier.
Now that the connections between the controller and view layers are complete, it
is time to turn to the model layer and write some password-generating code.
Creating the Model Layer
In this section, you are going to create a new file that will contain the code that
creates a random string. The MainWindowController will receive input from the
view layer (a click of the button), call a function in the model layer to do the
real-world work (generate a random string), and then update the view layer
(display the string in the text field).
In the project navigator, Control-click the RandomPassword group and select New
File.... From the Source section under OS X, choose Swift File (Figure 1.26). Save the
file as GeneratePassword in the default location.
Figure 1.26 Creating a new Swift file
Xcode will open the GeneratePassword.swift file. At the top of the file, add an
array named characters. This is the set of characters from which you will
randomly select to create a string.
import Foundation
return string
}
}
Run the app, click the button, and generate a password. Notice that you can copy
and paste the password. This functionality comes for free in the NSTextField
class and is a good example of how Cocoa classes are well-designed to assist
developers in building apps.
If your application will not build or if it does not run as you expect, check for
typos in both Swift files. See if the compiler is reporting any errors and compare
your code with the code in the book.
Improving Controller Design
Currently, in AppDelegate.swift, you create a window controller and pass in a
string – "MainWindowController". The string is the name of a NIB file. When
the project is launched, the window controller is created and the objects in the
named NIB file are unarchived along with their connections.
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification:
NSNotification) {
func applicationDidFinishLaunching(aNotification:
NSNotification) {
...
}
Run the app again. It should work the same, but your MainWindowController
class no longer relies on another object to know its business.
Many tutorials that build simple apps would have only used one controller – the
AppDelegate. This may be a simpler way to start, but we find it misleading. The
AppDelegate is not intended to control a user interface or to coordinate an app’s
model and view layers. Soon, you will be creating apps with multiple windows
and windows that can have multiple view controllers. So we believe that it is
best to start with a solid architecture on which you can build.
Your first Cocoa application is now complete! In the next chapter, you will learn
a little more about the Swift language.
2
Swift Types
In the next three chapters, you are going to focus on learning the Swift language.
You will not learn everything you want to know about Swift, but you will learn
enough to get started. Then, as you continue through this book, you will learn
more Swift as you are learning Cocoa development.
If you do not think you will be comfortable picking up Swift at the same time as
Cocoa development, you may want to read Apple’s Swift tutorials first. You can
find them at developer.apple.com/swift. Or you can start with Swift Programming:
The Big Nerd Ranch Guide (available Summer 2015).
But if you have some programming experience and are willing to learn as you
go, then you can start your Swift education here.
Introducing Swift
Swift is a new language that Apple introduced in 2014. It replaces Objective-C
as the recommended development language for Cocoa and iOS.
Swift maintains the expressiveness of Objective-C while introducing a syntax
that is safer as well as succinct and readable. It emphasizes type safety and
introduces advanced features such as optionals, generics, and sophisticated
structures and enumerations.
Most importantly, Swift allows the use of these new features while relying on
the same tested, elegant Cocoa frameworks that developers have built upon for
years.
If you know Objective-C, then the challenge is recasting what you know. It may
seem awkward at first, but we have come to love Swift at Big Nerd Ranch and
believe you will, too.
Types in Swift
Swift types can be arranged into three basic groups: structures, classes, and
enumerations. There are other types that fall outside of these three, such as
closures, but these are the building blocks of Swift. All three can have
properties: values associated with a type
initializers: similar to a function, code that initializes an instance of a type
instance methods: functions specific to a type that can be called on an
instance of that type
class or static methods: functions specific to a type that can be called on
the type itself
Figure 2.1 Swift building blocks
Swift’s structures and enumerations are significantly more powerful than in most
languages. In addition to supporting properties, initializers, and methods, they
can also conform to protocols and can be extended.
Swift’s implementation of typically “primitive” types such as numbers and
boolean values may surprise you: they are all structures, as are the commonly
used string and collection types provided by the Swift standard library.
Numbers: Int, Float, Double
Boolean: Bool
Click Next and save this file where you saved the RandomPassword project that you
created in Chapter 1.
When the file opens, notice that the playground is divided into two sections: The
larger white area to the left is the editor where you write code. The gray column
on the right is the sidebar. The playground compiles your code after every line
and shows the results in the sidebar.
Figure 2.3 A playground
In the example code, the var keyword denotes a variable, so the value of str can
be changed from its initial value. Type in the code below to change the value of
str, and you will see the results appear in the sidebar to the right.
var str = "Hello, playground"
"Hello, playground"
str = "Hello, Swift"
"Hello, Swift"
(Notice that we are showing sidebar results to the right of the code for the
benefit of readers who are not actively doing the exercise.)
The let keyword denotes a constant value, which cannot be changed. Add a
constant to the mix:
var str = "Hello, playground"
"Hello, playground"
str = "Hello, Swift"
"Hello, Swift
let constStr = str
"Hello, Swift"
Because constStr is a constant, attempting to change its value will cause an
error.
var str = "Hello, playground"
"Hello, playground"
str = "Hello, Swift"
"Hello, Swift"
let constStr = str
"Hello, Swift"
constStr = "Hello, world"
An error in the playground code will prevent you from seeing any further results
in the sidebar, so you usually want to address it right away. Remove the line that
attempts to change the value of constStr.
var str = "Hello, playground"
"Hello, playground"
str = "Hello, Swift"
"Hello, Swift"
let constStr = str
"Hello, Swift"
constStr = "Hello, world"
In your Swift code, you should use let unless you expect the value will need to
change.
Inferring types
At this point, you may have noticed that neither the constant nor the variable has
a specified type. This does not mean they are untyped! Instead, the compiler
infers their types from the initial values. This is called type inference.
You can find out what type was inferred using Quick Help. Option-click on
constStr to see the Quick Help information for this constant.
Specifying types
If your constant or variable has an initial value, you can rely on type inference. If
a constant or variable does not have an initial value or if you want to ensure that
it is a certain type, you can specify the type in the declaration.
Add more variables with specified types.
var str = "Hello, playground"
"Hello, playground"
str = "Hello, Swift"
"Hello, Swift"
let constStr = str
"Hello, Swift"
The most common type for integers is Int. There are additional integer types
based on word size and signedness, but Apple recommends using Int unless you
really have a reason to use something else.
For floating-point numbers, Swift provides three types with different levels of
precision: Float for 32-bit numbers, Double for 64-bit numbers, and Float80 for
80-bit numbers.
A boolean value is expressed in Swift using the type Bool. A Bool can be either
true or false.
Collection types
The Swift standard library offers three collections: arrays, dictionaries, and sets.
An array is an ordered collection of elements. The array type is written as
Array<T>, where T is the type of element that the array will contain. This type
can be any kind of type: a standard type, a structure, or a class.
Add a variable for an array of integers:
...
var hasPet: Bool
var arrayOfInts: Array<Int>
Arrays are strongly-typed. Once you have declared an array as containing
elements of, say, Int, you cannot add a String to this array.
There is a shorthand syntax for declaring arrays: you can simply use square
brackets around the type that the array will contain. Update the declaration of
arrayOfInts to use the shorthand:
...
var hasPet: Bool
var arrayOfInts: Array<Int>
var arrayOfInts: [Int]
A dictionary is an unordered collection of key-value pairs. The values can be of
any type, including structures and classes. The keys can be of any type as well,
but they must be unique. Specifically, the keys must be hashable, which allows
the dictionary to guarantee that the keys are unique as well as access the value
for a given key more efficiently. Basic Swift types such as Int, Float, Character,
and String are all hashable.
Like Swift arrays, Swift dictionaries are strongly-typed and can only contain
keys and values of the declared type. For example, you might have a dictionary
that stores capital cities by country. The keys for this dictionary would be the
country names and the values would be the city names. Both keys and values
would be strings. Add a variable for such a dictionary:
...
var arrayOfInts: [Int]
var dictionaryOfCapitalsByCountry:
Dictionary<String,String>
There is a shorthand syntax for declaring dictionaries, too. Update
dictionaryOfCapitalsByCountry to use the shorthand:
...
var arrayOfInts: [Int]
var dictionaryOfCapitalsByCountry:
Dictionary<String,String>
var dictionaryOfCapitalsByCountry: [String:String]
A set is similar to an array in that it contains a number of elements of a certain
type. However, sets are unordered and the members must be unique as well as
hashable. The unorderedness of sets makes them faster when you simply need to
determine whether something is a member of a set. Add a variable for a set:
var winningLotteryNumbers: Set<Int>
Initializers
So far, you’ve initialized your constants and variables using values. But you can
also create new instances of a specific type. An instance is a particular
embodiment of a type. Historically, this term has been only used with classes,
but in Swift it is used to describe structures, too. For example, the constant
secondElement holds an instance of String.
To create a new instance of a type, you use the type name followed by a pair of
parentheses and, if required, arguments. This signature – the combination of type
and arguments – corresponds to an initializer. Initializers are responsible for
initializing the contents of a new instance of a type. When an initializer is
finished, the instance is ready for action.
Some standard types have initializers that return empty literals when no
arguments are supplied. Add an empty string and an empty array to your
playground.
let emptyString = String()
""
let emptyArrayOfInts = [Int]()
0 elements
let emptySetOfFloats = Set<Float>()
0 elements
Other types have default values:
let defaultNumber = Int()
0
let defaultBool = Bool()
false
Types can have multiple initializers. For example, String has an initializer that
accepts an Int and creates a string based on that value.
let number = 42
42
let meaningOfLife = String(number)
"42"
To create a set, you use the Set initializer that accepts an array literal:
let availableRooms = Set([205, 411, 412])
{412, 205, 411}
Float has several initializers. The parameter-less initializer returns an instance of
Float with the default value. There is also an initializer that accepts a floating-
point literal.
let defaultFloat = Float()
0.0
let floatFromLiteral = Float(3.14)
3.14
If you use type inference for a floating-point literal, the type defaults to Double.
Create the following constant with a floating-point literal.
let easyPi = 3.14
3.14
Use the Float initializer that accepts a Double to create a Float from this Double.
let easyPi = 3.14
3.14
let floatFromDouble = Float(easyPi)
3.14
You can achieve the same result by specifying the type in the declaration.
let easyPi = 3.14
3.14
let floatFromDouble = Float(easyPi)
3.14
let floatingPi: Float = 3.14
3.14
Properties
A property is a value associated with an instance of a type. String has the
property isEmpty, which is a Bool that tells you whether the string is empty.
Array<T> has the property count, which is the number of elements in the array as
an Int. Access these properties in your playground:
let emptyString = ""
emptyString.isEmpty
true
...
Instance methods
An instance method is a function that is specific to a particular type and can be
called on an instance of that type. Try out the append(_:) and reverse() instance
methods from Array<T>:
countingUp.append("three")
["one", "two", "three"]
let countingDown = countingUp.reverse()
["three, "two", "one"]
The append(_:) method accepts an element of the array’s type and adds it to the
end of the array. The reverse() method returns a new array of the same type but
with the order of the elements reversed.
We will discuss methods, including naming, in the next chapter.
Optionals
Swift has a generic optional type, Optional<T>. In practice, an optional is
indicated by appending ? to a type name:
var anOptionalFloat: Float?
var anOptionalArrayOfStrings: [String]?
var anOptionalArrayOfOptionalStrings: [String?]?
An optional lets you express the possibility that a variable may not store a value
at all. The value of an optional will either be an instance of the specified type or
nil.
Throughout the book, you will have many chances to use optionals. What
follows is an example to get you familiar with the syntax so that you can focus
on the use of the optionals later.
Imagine a group of instrument readings.
var reading1: Float
var reading2: Float
var reading3: Float
Sometimes, an instrument might malfunction and not report a reading. You do
not want this malfunction showing up as, say 0.0; you want it to be something
completely different that tells you to check your instrument or takes some other
action.
You can do this by declaring the readings as optionals:
var reading1: Float?
nil
var reading2: Float?
nil
var reading3: Float?
nil
As an optional float, each reading can contain either a Float or nil. If not given
an initial value, then the value defaults to nil.
You can assign values to an optional just like any other. Assign floating-point
literals to the readings:
reading1 = 9.8
9.8
reading2 = 9.2
9.2
reading3 = 9.7
9.7
However, you cannot use these optional floats like non-optional floats – even if
they have been assigned Float values. Before you can read the value of an
optional variable, you must address the possibility of its value being nil. This is
called unwrapping the optional.
You are going to try out two ways of unwrapping an optional variable: optional
binding and forced unwrapping. You will implement forced unwrapping first.
This is not because it is the better option; in fact, it is the less safe one, but
implementing forced unwrapping first will let you see the dangers and
understand why optional binding is typically better.
To forcibly unwrap an optional, you append a ! to its name. First, try averaging
the readings as if they were non-optional variables:
reading1 = 9.8
9.8
reading2 = 9.2
9.2
reading3 = 9.7
9.7
let avgReading = (reading1 + reading2 + reading3) / 3
This results in an error because optionals require unwrapping. Forcibly unwrap
the readings to make the error go away:
let avgReading = (reading1 + reading2 + reading3) / 3
let avgReading = (reading1! + reading2! + reading3!) /
3 9.56667
Everything looks fine, and you see the correct average in the sidebar. But a
danger lurks in your code. When you forcibly unwrap an optional, you tell the
compiler that you are sure that the optional will not be nil and can be treated as
if it were a normal Float. But what if you are wrong? To find out, comment out
the assignment of reading3, which will return it to its default value, nil.
reading1 = 9.8
9.8
reading2 = 9.2
9.2
reading3 = 9.7
// reading3 = 9.7
You now have an error. To see the console where the error is reported in a
playground, you must open the assistant editor. From Xcode’s View menu, select
Assistant Editor → Show Assistant Editor. Or use the keyboard shortcut Option-
Command-Return. The error reads
fatal error: unexpectedly found nil while unwrapping
an Optional value
If you forcibly unwrap an optional and that optional turns out to be nil, it will
cause a trap, stopping your application.
A safer way to unwrap an optional is optional binding. Optional binding works
within a conditional if-let statement: You assign the optional to a temporary
constant of the corresponding non-optional type. If your optional has a value,
then the assignment is valid and you proceed using the non-optional constant. If
the optional is nil, then you can handle that case with an else clause.
Change your code to use an if-let statement that tests for valid values in all
three readings.
let avgReading = (reading1! + reading2! + reading3!) /
3
if let r1 = reading1,
let r2 = reading2,
let r3 = reading3 {
let avgReading = (r1 + r2 + r3) / 3
} else {
let errorString = "Instrument reported a reading
that was nil."
}
reading3 is currently nil, so its assignment to r3 fails, and the sidebar shows the
error string.
To see the other case in action, restore the line that assigns a value to reading3.
Now that all three readings have values, all three assignments are valid, and the
sidebar updates to show the average of the three readings.
Subscripting dictionaries
Recall that subscripting an array beyond its bounds causes a trap. Dictionaries
are different. The result of subscripting a dictionary is an optional:
let nameByParkingSpace = [13: "Alice", 27: "Bob"]
[13: "Alice", 27: "Bob"]
let spaceAssignee: String? = nameByParkingSpace[13]
"Alice"
If the key is not in the dictionary, the result will be nil. As with other optionals,
it is common to use if-let when subscripting a dictionary:
let spaceAssignee: String? = nameByParkingSpace[13]
if let spaceAssignee = nameByParkingSpace[13] {
println("Key 13 was in the dictionary!")
}
Loops and String Interpolation
Swift has all the familiar control flow statements: if-else, which you saw
above, while, for, for-in, do-while, and switch. While they are familiar, there
may be some differences from what you are accustomed to. The key differences
with C-like languages is that while enclosing parentheses are not necessary on
these statements’ expressions, braces on clauses are necessary. Additionally, the
expressions for if and while-like statements must evaluate to a Bool.
Consider this very traditional C-style loop, written in Swift:
for var i = 0; i < countingUp.count; i++ {
let string = countingUp[i]
// Use 'string'.
}
You could do the same thing a little more cleanly using Swift’s Range type and
the for-in statement:
let range = 0..<countingUp.count
for i in range {
let string = countingUp[i]
// Use 'string'.
}
The most direct route would be to enumerate the items in the array themselves:
for string in countingUp {
// Use 'string'.
}
What if you wanted the index of each item in the array? Swift’s enumerate()
function returns a sequence of integers and values from its argument:
for (i, string) in enumerate(countingUp) {
// (0, "one"), (1, "two")
}
What are those parentheses, you ask? The enumerate() function actually returns
a sequence of tuples. A tuple is an ordered grouping of values, similar to an
array, except each member may have a distinct type. In this example the tuple is
of type (Int, String). We will not spend much time on tuples in this book as
they are not used in Cocoa APIs (Objective-C does not support tuples). They can
be useful in your Swift code, however.
Another application of tuples is in enumerating the contents of a dictionary:
let nameByParkingSpace = [13: "Alice", 27: "Bob"]
struct Vector {
var x: Double
var y: Double
}
Much like C structures, Swift structures are composite data types. They are
composed of one or more fields, or properties, each of which has a specified
type. A few lines down, create an instance of Vector and access its properties:
let gravity = Vector(x: 0.0, y: -9.8) // {x 0, y
-9.800000000000001}
gravity.x // 0
gravity.y //
-9.800000000000001
You just used Swift’s automatic initializer to create an instance of this structure.
The automatic initializer has a parameter for each property in the structure. If
you were to add a z field, this code would cause a compiler error because it lacks
a z parameter. (Do not worry about the zeros; that is just typical floating point
fun.)
You can provide your own initializers, but when you do, the automatic initializer
is no longer provided. Go back to Vector and add an initializer that takes no
parameters and initializes x and y to 0.
struct Vector {
var x: Double
var y: Double
init() {
x = 0
y = 0
}
}
Initializers in Swift use the init keyword, followed by the parameter list, and
then the body of the initializer. Within the body, the x and y properties are
assigned directly.
An initializer must initialize all of the properties of its structure.
As we warned, defining this initializer has caused the automatic one to vanish,
causing an error in the playground. You can easily define it manually, however:
struct Vector {
var x: Double
var y: Double
init() {
x = 0
y = 0
}
init() {
x = 0
y = 0
self.init(x: 0, y: 0)
}
Instance methods
Methods allow you to add functionality to your data types. In Swift, you can add
methods to structures as well as classes (and enums!). Instance methods operate
within the context of a single instance of the type. Add an instance method for
multiplying a vector by a scalar:
struct Vector {
...
As in initializers, self represents the instance that the method is being called on.
As long as there is no conflict with named parameters or local variables,
however, it is entirely optional, so we prefer to leave it off. Make this change to
vectorByAddingVector(_:).
struct Vector {
...
class Particle {
}
Classes and structures differ significantly in terms of initializers. Most
noticeably, classes do not have automatic initializers, so you will see a compiler
error: Class 'Particle' has no initializers.
Fix this by adding an initializer to Particle:
class Particle {
init(position: Vector) {
self.position = position
self.velocity = Vector()
self.acceleration = Vector()
}
convenience init() {
self.init(position: Vector())
}
}
There is an exception to these designated initializer rules: required initializers,
which you will see in Chapter 12.
convenience init() {
self.init(position: Vector())
}
}
The tick(_:) method takes an NSTimeInterval parameter, dt, the number of
seconds to simulate. NSTimeInterval is an alias for Double.
Below the definition of Particle, define the Simulation class, which will have
an array of Particle objects and its own tick(_:) method:
class Particle {
...
}
class Simulation {
}
The Simulation class has no initializers defined since all of its properties have
default values. The for-in loop iterates over the contents of the particles
property. The tick(_:) method applies constant acceleration due to gravity to
each of the particles before simulating them for the time interval.
Before you warm up the simulator and add a particle, add a line to evaluate
particle.position.y. You will use this shortly with the playground’s Value
History. Additionally, add some code to remove particles once they drop below y
= 0:
class Simulation {
...
}
The last chunk of code filters the particles array, removing any particles that
have fallen to the ground. This is a closure, and it is OK if you do not understand
it at this point. You will learn more about closures in Chapter 15.
Now you are ready to run the simulator. Create an instance of the simulator and
a particle, add the particle to the simulation, and see what happens.
class Simulation {
...
}
}
The thrust property represents the magnitude of the rocket’s thrust.
thrustTimeRemaining is the number of seconds that the thrust will be applied for.
direction is the direction that the thrust will be applied in.
Take a minute to go through the initializers you just typed in. Which is the
designated initializer? (Remember the rule of thumb about designated
initializers?)
In order to guarantee that a class’s properties are initialized, initializers are only
inherited if a subclass does not add any properties needing initialization. Thus,
Rocket provides its own initializers and calls the superclass’s designated
initializer.
Next you will override the tick(_:) method, which will do a little math to
calculate the acceleration due to thrust and apply it before calling the
superclass’s – Particle’s – tick(_:) method.
class Rocket: Particle {
...
}
Finally, create an instance of Rocket and add it to the simulation in place of the
ball:
let simulation = Simulation()
Basic bridging
Swift types are automatically bridged to their Foundation counterparts:
let string = "Howdy"
let objcString: NSString = string
The reverse is not true, however:
let swiftString: String = objcString //
Error!
Instead, you must explicitly cast it using as:
let swiftString: String = objcString as String //
Ok!
Another class that you may see is NSNumber. Because Foundation collections can
only store objects, in order to store numbers they must be represented by an
object. NSNumber is the class that Objective-C programmers use for this task.
Swift numbers also bridge easily with NSNumber:
let objcNumber: NSNumber = 3
let swiftNumber = objcNumber as Int
When tree is set to nil, the root node’s reference count is decremented to 0,
which deallocates the root node. Because the children array is a value type on
the node, its strong references to the children are released, which causes the
child’s reference count to reach 0, deallocating it. The entire tree of objects has
been freed.
The key to avoiding strong reference cycles is to think in terms of “what owns
what” in your object graph and make only those references strong. In a tree, the
parent owns the children (strong reference); the child does not own the parent
(weak reference).
Weak references are commonly used in three places in Cocoa:
Outlets to subviews: A window controller has a strong reference to the
window, and a window has strong references (indirectly) to its view
hierarchy. Therefore, a strong reference is unnecessary and could
inadvertently keep a subtree of the view hierarchy alive.
Targets from controls: You will learn more about controls in Chapter 5, but
because a controller typically has indirect strong references to its controls,
they should have weak references back to the controller where the action
methods are implemented.
Delegates: Delegates will be covered in Chapter 6, but the idea is the same
as with controls and their targets.
Unowned references
Rarely, you may see references declared as unowned. An unowned reference is a
non-strong, non-optional reference. Like a weak reference, it does not add to an
object’s reference count. Unlike a weak reference, it is not set to nil when the
referenced object is deallocated.
What are unowned references for? For one thing, they have been used to make
pre-ARC parts of the Cocoa frameworks compatible with ARC. Unowned
references are also sometimes used in closures, which you will learn about in
Chapter 15.
In your code, an unowned reference is best used when you know that the
reference will never be accessed after the referenced object has been deallocated.
For example, you might have an one object whose lifetime is known to be a
subset of the lifetime of the object that it is referencing.
What is ARC?
To understand ARC, you should know a little history. In the old days, Cocoa
programmers managed their objects’ reference counts manually by “retaining”
and “releasing” objects. There were rules, and the rules were fairly simple. This
worked well.
Then garbage collection came to Cocoa in 2007. Garbage collection was fine,
but it had awkward constraints on the contexts in which it could be used – it was
all garbage collection or nothing.
A couple years later, Xcode 3.2 offered an exciting new feature: the Clang Static
Analyzer, which knew the retain counting rules and could alert you to memory
management errors in your code! Garbage collection never came to iOS. Instead,
ARC was announced in 2011, and garbage collection was deprecated shortly
thereafter.
ARC is compiler technology, based on the Clang Static Analyzer. The static
analyzer looks at your code and determines where it should be retaining and
releasing (incrementing and decrementing retain counts). It then inserts
optimized calls, making for faster performance and less error-prone reference
counting than could be achieved before.
5
Controls
Once upon a time, there was a company called Taligent. Taligent was created by
IBM and Apple to develop a set of tools and libraries like Cocoa. About the time
Taligent reached the peak of its mindshare, Aaron met one of its engineers at a
trade show and asked him to create a simple application: A window appears with
a button. When the button is clicked, the words “Hello, World!” appear in a text
field.
The engineer created a project and started subclassing madly, subclassing the
window and the button and the event handler. Then he started generating code:
dozens of lines to get the button and the text field onto the window. After 45
minutes, he was still trying get the app to work. A couple of years later, Taligent
quietly closed its doors forever.
Most C++ and Java tools work on the same principles as the Taligent tools. The
developer subclasses many of the standard classes and generates many lines of
code to get controls to appear on windows. Most of these tools work.
While writing a Cocoa application, you seldom subclass the classes that
represent windows, buttons, or events. Instead, you create objects that work with
the existing classes. Also, you do not create code to get controls on windows.
Instead, the XIB file contains all this information. The resulting application has
significantly fewer lines of code. At first, this outcome may be alarming. In the
long run, most programmers find it delightfully elegant.
In this chapter, you are going to create a simple Cocoa application. The
application is named RGBWell. It displays a color and allows the user to change
the color by moving sliders that represent the different color components.
(Figure 5.1).
Figure 5.1 Each slider adjusts an RGB color component
In addition to creating RGBWell, you will learn more about how NIB files work,
take a closer look at the NSControl class, and explore Apple’s documentation on
the Cocoa frameworks. This list of items makes for a long chapter, but you will
gain a lot of useful Cocoa knowledge and experience.
Setting up RGBWell
Create a new project (Command-Shift-N). From the OS X options, choose Cocoa
Application and configure the project as shown in Figure 5.2.
As you did in RandomPassword from Chapter 1, you are going to adjust the project
that the template created to use a window controller as the primary controller for
the application. However, in RandomPassword you used an Xcode template to create
the MainWindowController class along with MainWindowController.xib. In this
project, you are going to create the class and the XIB file from scratch to see
how the parts work together.
}
Importing Cocoa gives you access to three frameworks: Foundation, AppKit,
and CoreData. AppKit is critical for this file because it provides all of the Cocoa
user interface classes, including NSWindowController.
(You have given the XIB file and the window controller class the same name.
This naming convention is not required, but it is widely followed and
understood. Also, note that it is only a naming convention and giving these files
the same name does not have any further consequences.) This empty XIB needs
a window object that will be the root of the hierarchy of objects that comprise
the interface of your application. At the bottom of the utilities area on the
righthand side of the Xcode window, locate the object library ( ). In the search
bar at the bottom of the library, search for a “window” and then drag a Window
from the library onto the canvas (Figure 5.5).
Figure 5.5 Dragging a window onto the canvas
The document outline will update to show two new objects: a Window and a View.
The view is the window’s content view, which is the view that will contain all of
the other views in the interface. When you drag a Window from the object library,
it always comes with a content view.
Select the window in the document outline and, in the inspector area above the
library, reveal the attributes inspector by selecting the icon in the inspector
bar. Set the window’s Title attribute to RGBWell and uncheck the box labeled Visible
At Launch.
func applicationDidFinishLaunching(aNotification:
NSNotification) {
}
In this code, you add a property to AppDelegate whose type is an optional
MainWindowController. The property must be an optional because it is not
initialized until applicationDidFinishLaunching(_:) is called.
In the implementation of applicationDidFinishLaunching(_:), you create an
instance of MainWindowController and then ask it to show its window. Showing
the window puts the window and the rest of its hierarchy on screen and prepares
these objects for user interaction. Finally, you save this instance to the property
so that the AppDelegate can keep a reference to it.
You can now run the app, but no window will appear. You have created a
window controller class and a window object in a XIB file, but you have not yet
connected the MainWindowController’s window outlet to the window object in
MainWindowController.xib.
}
When the MainWindowController loads this NIB file, the window object is
unarchived. This raises the question of when a window controller loads its NIB.
The NIB is loaded as a side effect of accessing the window property for the first
time. Your MainWindowController attempts to access its window when
showWindow(_:) is called in applicationDidFinishLaunching(_:). Because the
window property is nil, the window controller loads the NIB file.
When the NIB has been loaded, the window object exists, but showWindow(_:)
can only complete its job if the window outlet is connected to this object.
Connecting the outlet and showing the window require the help of the File's
Owner placeholder in MainWindowController.xib.
(If Visible At Launch was still checked, your window would now appear on screen
despite the window outlet not being connected. However, in a real app, you
cannot rely on only this setting to get the window on screen. For example, what
if the window was closed with the intent of reopening it later? You would call
showWindow(_:) to reopen the window, and showWindow(_:) will only work if the
window outlet is connected.)
Finally, to connect the window outlet, Control-click on File's Owner to open the
connections panel. Drag from the window outlet to the window on the canvas
(Figure 5.8).
Figure 5.8 Connecting the window outlet
Now the MainWindowController can find and show its window. Run the app and
confirm that you have an empty RGBWell window as shown in Figure 5.9.
Figure 5.9 Empty RGBWell window confirming correct set-up
Your project is now set up and you are ready to add to the application’s
interface. So let’s take a closer look at an important type of object found in
interfaces – controls.
About Controls
A control is a view that is designed specifically for user interaction. A control is
implemented as a subclass of NSControl, which is a subclass of NSView. Controls
are not part of the controller layer, so keep the terms “control” and “controller”
separate in your mind. It may help to think of controls as “user controls.”
In RandomPassword, you worked with two controls: NSButton and NSTextField. In
RGBWell, you will use two more: NSSlider and NSColorWell.
NSControl inherits from NSView, which inherits from NSResponder, which inherits
from NSObject. Each member of the family tree adds some capabilities
(Figure 5.10).
Figure 5.10 Inheritance diagram for NSControl
NSControl adds the ability to send action messages. The target is a reference to
the object that receives the action message. The action is a selector that is sent
to the target of the action message. (Remember that a selector is essentially the
name of a method in string form.) When the user interacts with a control, the
control sends an action message to the target and the target then executes the
associated action method.
NSControl also has several properties relating to the value of a control. For
numerical values, you can use integerValue, floatValue, or doubleValue. For
text, you can use stringValue. There is also objectValue. This property returns a
model object that is represented by the control. Usually, this is an NSString or an
NSNumber, but it can be an instance of any NSObject subclass. The conversion
from string to model object and back is managed by a formatter. You will learn
more about them in Chapter 10.
Working with Controls
Let’s get back to RGBWell and add a control – an NSSlider – to your interface.
A slider allows a user to select a value from some range. In the object library,
search for a slider and drag a Horizontal Slider onto the top righthand side of the
window (Figure 5.11).
Figure 5.11 Dragging a slider onto the window
Next, find a label in the object library and drag it onto the canvas to the left of
the slider and change its text to R. Figure 5.12 shows what the interface should
look like at this point.
Figure 5.12 RGBWell with one label and one slider
Cocoa controls tend to be very versatile. The same class may have a number of
different visual styles. A Label is an NSTextField, the same class that is used to
accept text input, configured to display static text. Specifically, its border and
background drawing have been disabled, as well as selection and editing.
Cells have a long history in Cocoa. They were originally added to address
performance issues: many of the basic tasks of a control were handed off to the
cell, which could do them more efficiently than the control. Mac computers are
much more powerful than they were in those days, and cells have become an
encumbrance. Apple has stated that it is in the process of deprecating cells, but
you will still see them in your document outline and in older code.
Dragging to File's Owner connects the slider’s target and selecting the
adjustRed: action connects the action.
In connecting the target outlet, you give the slider a reference to the
MainWindowController. The slider is in the MainWindowController’s view
hierarchy, so the window controller has an indirect strong reference to the slider.
As you learned in Chapter 4, when two objects reference each other, you must
make one of the references weak to prevent a strong reference cycle and allow
the objects to be deallocated when appropriate.
To avoid the possibility of strong reference cycles between controls and their
targets, NSControl defines the target outlet as a weak reference: weak var target:
AnyObject?
Run the app. Move the slider up and down and watch the console at the bottom
of the project window. (Xcode should automatically show the console. If it does
not, press Shift-Command-C to show it yourself.)
Figure 5.16 Console reporting integerValue for slider
Whenever you release the mouse button, the slider sends its action message to its
target, and its target (the MainWindowController) executes the adjustRed(_:)
method.
A continuous control
NSControl has a property named continuous that specifies when the action
message is sent. By default, continuous = false, and the action message is
only sent when the user interaction has ended, like when the mouse button is
released or the Return key is pressed. This is how your slider currently works.
You can drag the slider back and forth, but you will only see a new value
reported when you release the mouse button.
When continuous = true, the action message is sent continuously during the
interaction. For a slider, this means the action message is sent over and over
again while the user is dragging the knob along the slider.
To see the difference, select the slider and reveal the attributes inspector ( ).
Find the Control section and check the checkbox labeled Continuous, as shown in
Figure 5.17.
Figure 5.17 Making slider continuous
Run the app and move the slider. The console will now report values non-stop as
you drag the slider left and right. This is the slider sending its action message to
its target continuously.
its target continuously.
Back in the attributes inspector, notice that the attributes are divided into
sections. The top section is labeled Slider, the next section down is labeled Control,
and the section beneath that is labeled View. These sections reflect the slider’s
inheritance hierarchy. (See Figure 5.10 to review this hierarchy.) The attributes
in each section are properties on a Cocoa class that have been exposed so that
you can set them in Interface Builder.
Note that the labels in the attributes inspector do not exactly match the property
names. NSSlider has the properties minValue and maxValue, which correspond to
the Minimum and Maximum fields in the inspector. NSControl has the properties
continuous and enabled, which are exposed in Interface Builder as the Continuous and
Enabled checkboxes. You will see later how to find all of properties and methods
of a class in Apple’s documentation.
Run the app and move the slider. Now that you have changed the slider’s range,
its integerValue is much less interesting. Any value that is not 1 is rounded
down to 0.
Figure 5.19 integerValue for slider after reducing range
}
Run the app and test out the new values.
Figure 5.20 Console reporting floatValue for slider
Adding two more sliders
RGBWell needs two more sets of sliders and labels. Instead of dragging them from
the object library, you can copy and paste the existing objects.
In MainWindowController.xib, select the label and slider. (Use Command-click
to select multiple objects.) Copy and paste to produce two more label-slider pairs
on the window. Align the pairs and change the new labels to G and B
(Figure 5.21).
Figure 5.21 RGBWell with three label-slider pairs
}
Return to MainWindowController.xib. For each new slider, Control-drag from the
slider to File's Owner and select the appropriate action.
Figure 5.22 New actions need connecting
Run the app and test the sliders. Note that the new sliders report values between
0 and 1 and send their action messages continuously. When you copy and paste
an object in a XIB, all of its attribute values are copied, too.
...
}
Second, in MainWindowController.xib, Control-click File's Owner and connect
the colorWell outlet to the color well on the canvas.
Figure 5.24 New colorWell outlet needs connecting
A color well lets the user pick a specific color and save it to use elsewhere in the
application. By default, clicking a color well opens a color picker window that
sets its color. Run the app, click the color well, and see what happens.
Figure 5.25 Clicking color well shows color picker
In this simple app, you only want the color well to display a color. To prevent
the color picker from appearing when the color well is clicked, you are going to
disable the color well.
Disabling a control
Every control has an enabled property. By default, enabled = true. When
enabled = false, the control will not respond to user interaction. For the color
well, this means that clicking the color well will do nothing, which is what you
want. For some controls, setting enabled to false also changes the control’s
appearance. For example, a disabled button is “grayed-out.”
Like continuous, enabled is exposed in Interface Builder, so you can disable the
color well there. With the color well selected, reveal the attributes inspector. In
the Control section, uncheck the box labeled Enabled (Figure 5.26).
Figure 5.26 Disabling the color well
Run the app to confirm that clicking the color well now does nothing.
Now you must tell your color well what color it should display. How can you do
this? To find out, let’s visit the Apple documentation for the NSColorWell class.
Using the Documentation
There are over 300 classes in Cocoa and related frameworks, and every one is
documented in Apple’s API reference. Cocoa programmers spend a lot of time
searching this reference to find answers to development questions. Apple’s
documentation also includes topic-based guides, which are excellent resources
for learning more about a particular subject.
In this section, you are going to use the reference to figure out how to get your
color well to display a color specified by a set of RGB values. From Xcode’s
toolbar, select Window → Documentation and API Reference. In the window that appears,
find the search bar at the top, search for NSColorWell, and select the NSColorWell
Class Reference (Figure 5.27).
The class reference page begins with an overview and then lists the properties
and methods of the class. The properties and methods are organized into distinct
development tasks for which the class is typically used.
On the lefthand pane of the documentation window, you can see and navigate to
individual tasks, properties, and methods. NSColorWell is not a large class – it
has only three properties and four methods distributed among four tasks. Find
and select the color property to see its details displayed in the center panel of the
page (Figure 5.28).
Figure 5.28 Investigating the color property
This is property you set to change the color well’s color. Its type is NSColor, so
the next question is how to create an NSColor using RGB values. To find the
answer, click on NSColor in the property definition to go to the reference page for
the NSColor class.
On the NSColor reference page, find the task for creating a color from component
values. Within this task, there are several methods that create and return a new
NSColor. Find and select a method with the parameters calibratedRed, green,
blue, and alpha (Figure 5.29).
var r = 0.0
var g = 0.0
var b = 0.0
let a = 1.0
...
}
Next, update the action methods to store the sliders’ values in these properties.
Note that you use doubleValue because the properties that you just initialized
with floating-point literals have been inferred to be of type Double.
class MainWindowController: NSWindowController {
...
...
func updateColor() {
let newColor = NSColor(calibratedRed:
CGFloat(r),
green:
CGFloat(g),
blue:
CGFloat(b),
alpha:
CGFloat(a))
colorWell.color = newColor
}
}
Finally, in the action methods, call updateColor().
class MainWindowController: NSWindowController {
...
...
}
Run the app, move the sliders, and make pretty colors.
Controls and Outlets
So far, you have not needed outlets to the sliders. Everything has been done
within the action methods, so you have been able to use sender to access these
controls. But if you want to access a control outside of its action method, you
need to create and connect an outlet.
One place you might want to access a control is the NSWindowController method
windowDidLoad(). This method is called after the objects and their connections
are loaded from the NIB but before the window is displayed to the user. It is
where you can do extra initialization for your interface.
In this section, you are going to override windowDidLoad() to ensure that when
the app starts, the sliders and the color well correspond to the default values in
MainWindowController.
...
let a: Float = 1.0
...
}
(You are probably wondering what the ! means at the end of outlet declarations.
It declares the outlets as implicitly unwrapped optionals, which you will learn
about in the next section.)
Second, in MainWindowController.xib, Control-click on File's Owner and use the
connections panel to connect the slider outlets to the corresponding sliders.
Figure 5.30 New outlets need connecting
...
...
}
The call to super.windowDidLoad() does nothing in this implementation, but it
is a good habit to get into. Some NSWindowController subclasses may inherit
from other NSWindowController subclasses instead of directly from
NSWindowController. When this is true, you must ensure that the superclass has
done its extra initialization before you do yours.
Run the app and notice the change in the starting positions of the sliders and the
color of the color well (Figure 5.31).
Figure 5.31 Sliders now set to initial values
Let’s take a moment to talk about the two buttons and how they will function in
the app. The buttons that start and stop the speech synthesizer will be disabled
when appropriate: the Speak button will be disabled when the speech synthesizer
is speaking; the Stop button will be disabled when the speech synthesizer is not
speaking. A disabled button is “grayed-out” and cannot be clicked. In Figure 6.1,
the Speak button is disabled.
Changing the state of the buttons at the right times will require implementing a
delegate method from the NSSpeechSynthesizerDelegate protocol. But before
you get to delegation, you are going to set up the application and get it talking.
Setting up SpeakLine
Create a new Cocoa Application project (Command-Shift-N) named SpeakLine.
Configure it as shown in Figure 6.2.
Figure 6.2 Configuring the project settings for SpeakLine
A pop-up will appear asking you describe the snippet. Name it Single-Window
Application and summarize it as Initializes a main window controller, as shown in
Figure 6.5. Click Done to add the snippet to the library.
Figure 6.5 Naming your snippet
Now you can drag this snippet from the library into your projects. Return to the
new SpeakLine project. Open AppDelegate.swift and delete the contents of the
class implementation so that the file looks like this:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
}
Open the snippet library and search for the Single-Window Application snippet. Drag
the snippet from the library onto the code editor and into the empty class
implementation. When you release the mouse, the code in the snippet will be
added to the file.
Figure 6.6 Dragging from the snippet library to the class file
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification:
NSNotification) {
// Create a window controller with a XIB file
of the same name
let mainWindowController =
MainWindowController()
// Put the window of the window controller on
screen
mainWindowController.showWindow(self)
}
Run the application to confirm that everything is set up correctly. You should
see an empty Window.
If you are adding snippets to the library, you should know how to delete them,
too. To delete a snippet, simply select the snippet in the library and press Delete.
Note that this procedure is not the only way to connect outlets and actions, but it
is a good choice when you are learning. It reminds you of what is actually
happening because you are always dragging from the object with the connection
to the object to be connected. In this case, the MainWindowController has outlets
that reference buttons, so you drag from File's Owner to the buttons to connect the
outlets. Each button has a target outlet that should reference the
MainWindowController as well as an action outlet, so you Control-drag from
each button to File's Owner and select the appropriate action.
Run the application. Enter some text, click the buttons, and confirm the results in
the console.
Synthesizing Speech
Cocoa includes the class NSSpeechSynthesizer; instances of this class produce
speech from text.
In MainWindowController.swift, create an instance of NSSpeechSynthesizer.
class MainWindowController: NSWindowController {
...
}
The NSSpeechSynthesizer class includes these important methods:
func startSpeakingString(_ text: String!) -> Bool
func stopSpeaking()
Modify your action methods to call these methods.
class MainWindowController: NSWindowController {
...
...
...
Next, adjust the button’s action methods to set this property.
// MARK: - Action methods
...
func updateButtons() {
if isStarted {
speakButton.enabled = false
stopButton.enabled = true
} else {
stopButton.enabled = false
speakButton.enabled = true
}
}
}
To tie everything together, you are going to call updateButtons() whenever the
value of isStarted changes. You can make this happen using Swift’s didSet
feature.
didSet and willSet are observers that you can declare as part of a stored
property. Implementing observers allows you to respond to the property’s value
being changed.
In the isStarted property, add a didSet observer that updates the buttons.
class MainWindowController: NSWindowController {
...
@IBOutlet weak var stopButton: NSButton!
...
Now updateButtons() will be called whenever the value of isStarted is
changed. Run the app to confirm.
Notice that when the application is launched, both buttons are enabled. The
isStarted property is initialized to false, but that does not trigger the observer
to call to updateButtons(). Only changing the property from its initial value does
that.
In MainWindowController.swift, call updateButtons() directly in
windowDidLoad() to set the initial states of the buttons.
class MainWindowController: NSWindowController {
...
...
Run the app and confirm that the Stop button is disabled when the window is
loaded.
Your code works. There is, however, an important situation it does not handle:
when the speech synthesizer finishes on its own without interruption, the buttons
do not update to reflect that speaking has stopped. You need to know when the
speech synthesizer has finished, regardless of whether it was stopped
speech synthesizer has finished, regardless of whether it was stopped
prematurely or finished on its own. To address this problem, you are going to
learn about an important design pattern in Cocoa: delegation.
Delegation
Let’s start with a story: Once upon a time, there was a man with no name.
Knight Industries decided that if this man were given guns and wheels and
booster rockets, he would be the perfect crime-fighting tool. First they thought,
“Let’s subclass him and override everything we need to add the guns and wheels
and booster rockets.” The problem was that to subclass Michael Knight, they
needed to wire his insides to the guns, wheels, and booster rockets - a time-
consuming task requiring lots of specialized knowledge. So instead, Knight
Industries created a helper object, the Knight Industries 2000, or “KITT,” a well-
equipped car designed to assist Michael Knight in a variety of crime-fighting
situations.
While approaching the perimeter of an arms dealer’s compound, Michael Knight
would say, “KITT, I need to get to the other side of that wall.” KITT would then
blast a big hole in the wall with a small rocket. After destroying the wall, KITT
would return control to Michael, who would charge through the rubble and
capture the arms dealer.
Note how creating a helper object is different from the RoboCop approach.
RoboCop was a man subclassed and extended. The RoboCop project involved
dozens of surgeons who extended the man into a fighting machine. This is the
approach taken by many object-oriented frameworks.
In the Cocoa framework, many objects are extended in the Knight Industries
way – by supplying them with helper objects. In this section, you are going to
provide the speech synthesizer with a type of helper object called a delegate.
Being a delegate
The first thing to understand about being a delegate is that it is a role – possibly
one of many – that an object may take on. In Cocoa, roles are defined by
protocols. The specific role of “delegate that serves a speech synthesizer” is
defined by the NSSpeechSynthesizerDelegate protocol. To sign up for this role,
an object must be an instance of a class that conforms to the
NSSpeechSynthesizerDelegate protocol.
The beauty of having delegate roles defined by protocols (delegation) instead of
classes (subclassing) is that any object whose type conforms to the protocol can
serve in that capacity. Thus, you can assign critical duties without associating
them with any particular object class. Moreover, an object can take on multiple
roles as needed.
Now let’s get back to the app and your current problem and see how assigning a
delegate to the speech synthesizer can help. The NSSpeechSynthesizerDelegate
protocol contains five methods:
protocol NSSpeechSynthesizerDelegate :
NSObjectProtocol {
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
didFinishSpeaking: Bool)
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
willSpeakWord: NSRange,
ofString: String)
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
willSpeakPhoneme: Int16)
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
didEncounterErrorAtIndex: Int,
ofString: String,
message: String)
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
didEncounterSyncMessage: String)
}
The optional keyword tells you that a class conforming to the protocol is not
required to implement the method. The NSSpeechSynthesizerDelegate protocol
does not have any methods that you are required to implement, but many other
protocols do. If a class conforming to a protocol does not implement a required
method, you will get a compile-time error.
Your current problem is that the buttons do not update to reflect that speaking
has stopped when the speech synthesizer finishes speaking on its own. You can
solve this problem with the NSSpeechSynthesizerDelegate method
speechSynthesizer(_:didFinishSpeaking:). Here are the steps you will follow
to implement this delegate method:
declare that the MainWindowController class conforms to the
NSSpeechSynthesizerDelegate protocol
Conforming to a protocol
First, to inform the compiler that a class conforms to a protocol, you add the
protocol name to the class definition.
In MainWindowController.swift, change the MainWindowController class
definition to include NSSpeechSynthesizerDelegate:
class MainWindowController: NSWindowController {
class MainWindowController: NSWindowController,
NSSpeechSynthesizerDelegate {
...
func speechSynthesizer(sender:
NSSpeechSynthesizer,
didFinishSpeaking
finishedSpeaking: Bool) {
isStarted = false
println("finishedSpeaking=\
(finishedSpeaking)")
}
...
...
}
Note that NSSpeechSynthesizer’s delegate is not defined as a strong reference.
This is true of all delegate properties and prevents a strong reference cycle from
occurring between the delegate and the object with the delegate.
Build and run the application. Type something in, start up the speech
synthesizer, and let it finish. The buttons should now update appropriately, and
the console will report that finishedSpeaking = true.
Let’s try the other case. Start the speaking again and this time, click Stop to
interrupt the speaking before it finishes. The console will report that
finishedSpeaking = false. The speechSynthesizer(_:didFinishSpeaking:)
method is called whenever speaking stops – whether the speech synthesizer
finished on its own or whether it was stopped mid-stream. Because this method
is called whenever speaking stops, you no longer need to update the buttons
when the Stop button is clicked.
In stopIt(_:), remove the redundant call to updateButtons().
@IBAction func stopIt(sender: AnyObject) {
speechSynth.stopSpeaking()
isStarted = false
}
Run the app again and confirm that the buttons still update as you expect when
the Stop button is clicked.
...
}
Then, implement windowShouldClose(_:) to return the value of the isStarted
property.
...
// MARK: - NSSpeechSynthesizerDelegate
func speechSynthesizer(sender:
NSSpeechSynthesizer,
didFinishSpeaking
finishedSpeaking: Bool) {
updateButtons()
println("finishedSpeaking=\
(finishedSpeaking)")
}
// MARK: - NSWindowDelegate
}
What about setting the delegate property? It has already been done. When you
ask Xcode to create a XIB file along with your NSWindowController subclass, that
XIB file includes a window, and that window’s delegate property is set to File's
Owner.
Run the app. Start up the speech synthesizer and then try to close the window.
The window will not close until the speech synthesizer has stopped speaking.
You did not even have to subclass the window to customize its behavior.
Notice the lack of warnings or errors from Xcode. Run the app and start up
the speech synthesizer. The Stop button can never be enabled. The delegate
method is not being called and there is nothing to tip you off to the problem
– other than the app not working right.
(Be sure to correct the spelling before continuing!)
The best way to avoid misspelling method names is to use auto-complete
when typing them in. If you do not know the method name well enough to
start typing it, you can copy the method declaration from the documentation
or from the protocol’s header file.
To get to the header file for NSSpeechSynthesizerDelegate, select the
protocol name in the class definition and Command-click. The header file
will open in the editor, and you can see all the definitions in the protocol.
(Command-clicking to see the header file also works with other types, like
classes.)
To leave the header file and get back to MainWindowController.swift, click
the back button ( ) at the top left of the editor’s toolbar.
func speechsynthesizer(sender:
NSSpeechSynthesizer,
didFinishSpeaking
finishedSpeaking: Bool) {
updateButtons()
println("finishedSpeaking=\
(finishedSpeaking)")
}
Before diving into the application, let’s go over some general ideas about table
views.
About Table Views
A table view is used to display data arranged in rows and columns. You can find
an example of a table view in the Sound Effects tab of the Sound preference pane in
System Preferences (Figure 7.2). Each row in the table view corresponds to a
different sound effect. The two columns correspond to attributes of the a sound:
its name and its type.
Figure 7.2 Alert sounds are presented in a table view with two columns
...
...
}
Run the app, check the console, and confirm that your users will have many
voices to choose from.
// MARK: - NSSpeechSynthesizerDelegate
...
}
Then, in windowDidLoad(), print out the name of each voice.
class MainWindowController: NSWindowController,
NSSpeechSynthesizerDelegate,
NSWindowDelegate {
...
...
}
Run the app and confirm that you have a list of friendly voice names. Later, you
will use the voiceNameForIdentifier(_:) method to assist the data source in
returning data to the table view. But for now, let’s turn to adding the table view
to the application’s interface.
Adding a Table View
Open MainWindowController.xib. As shown in Figure 7.1, the table view will sit
to the right of the text field and be roughly half its size.
In the canvas, select the window and drag its right edge to make it wide enough
to allow for the table view. Then, in the object library, find Table View and drag
one onto the window next to the text field (Figure 7.4).
Figure 7.4 Dragging a table view
(A yellow Auto Layout warning may appear to the right of the window in the
document outline. Do not worry about this warning. It will not cause any
problems, and you will learn about Auto Layout in Chapter 25.)
You will not be interacting with the objects surrounding the table view in this
exercise, but it is good to know what these objects are and what they do. At the
outermost-level, there is a Bordered Scroll View – an instance of NSScrollView. The
scroll view is necessary because a table can have more data than it can display at
one time, so the user can scroll the data as needed. The scroll view contains a
clip view, two scrollers, and a table header view:
The Clip View is an instance of NSClipView. A scroll view always comes with
clip view, which manages drawing the currently visible portion of the table.
The clip view contains the instance of NSTableView.
The two Scroller objects are instances of NSScroller. These are the controls
that allow the user to scroll the table.
The Table Header View is an instance of NSTableHeaderView. The table header
view displays table column titles and responds to mouse clicks on the titles.
Table columns
The Table View object is the instance of NSTableView, and a table view contains
one or more instances of NSTableColumn.
The default table view in Interface Builder comes with two columns, but your table
view only needs one. In the document outline, select one of the Table Column
objects and delete it.
The remaining column needs a title. In the document outline, select the Table
Column and, in the attributes inspector, change the title attribute to Voices.
Notice that the table header view still appears to have two column headers. In
reality, it has a single column header that has not yet resized itself. Select the
Scroll View in the document outline and then drag its edges in the canvas to resize
it for a single column. This will cause the header to resize itself as well.
Earlier you enlarged the window to make room for the table view. When you
change the size of the window of an app that you have already run, OS X
remembers the last window size. This means that the window’s new size may
not be reflected in the next run.
In Chapter 25, you will learn how to add Auto Layout constraints to your layout,
which will ensure that the window and its views size and arrange themselves in a
reasonable manner no matter what changes are made or how the window was
previously sized. In the meantime, to keep OS X from helpfully remembering
the window’s last size, you can disable resizing the window altogether.
In the document outline, select the SpeakLine window and reveal its attributes.
Find the checkbox labeled Resize and uncheck it (Figure 7.6).
Figure 7.6 Disabling window resizing
Now you will not be able to resize the window while the app is running, but you
can still adjust the window’s size in Interface Builder.
Before running, make any necessary adjustments to the width of the window and
reposition the buttons beneath the table view, as shown in Figure 7.7.
Figure 7.7 Layout for the SpeakLine window
Run the app to see the empty table view that is now a part of SpeakLine’s user
interface. That was the easy part. Getting it to display the right data will involve
several steps. It will help to know more about the objects inside the table view.
Tables, Cells, and Views
A table has columns and rows. The smallest unit in a table, formed by the
intersection of a row and a column, is a “cell.” The same term is also used to
refer to an instance of NSCell.
Historically, the difference between these two uses of “cell” was not a problem
because tables in Cocoa were always cell-based tables. In a cell-based table, a
cell is always an instance of NSCell or one of its subclasses.
Cell-based tables can technically still be used, but they have been deprecated in
favor of view-based tables. In a view-based table, a table cell is an instance of
NSView or one of its subclasses – most commonly NSTableCellView.
You can also create custom table cell views, although doing so is an advanced
technique beyond the scope of this book.
Your table view’s single column only needs to display a name, so a Text Table Cell
View is what you want. Fortunately, this is the type of table cell view that is
included by default in a table column. Figure 7.8 shows the objects that make up
the Voices column’s table cell view.
Figure 7.8 Object types within a Text Table Cell View
Within the text table cell view is a text field labeled Table View Cell. As an instance
of NSTextField, this text field comes with an actual NSCell – an NSTextFieldCell
that is also labeled Table View Cell. You can ignore the text field cell; you will only
need to think about the table cell view and the text field for this application.
(If you are unsure about the type of an object in the document outline, use its
icon as a hint. For instance, controls are always represented with slider icons.
You can also open the identity inspector and check the object’s class name.)
Now think back to the table view-data source conversation you learned about
earlier in the chapter. When the table view asks for data to display in a particular
cell, the data source returns an object. The table view responds by updating the
table cell view at the requested row and column. In particular, the table view sets
the objectValue property of the table cell view to the object that the data source
returned.
Setting the objectValue of the table cell view will not affect what the table
actually displays. The table displays the value of the text field of the table cell
view. This means you must do a little more work to ensure that the value of the
text field is updated when the objectValue of the table cell view is set. You will
do this shortly using Cocoa bindings, but first let’s set up the
MainWindowController to be the data source for the table view.
The NSTableViewDataSource Protocol
Setting up a data source for a table view follows the same pattern that you saw in
Chapter 6 when setting up a delegate for the speech synthesizer. There are three
steps: conforming to the protocol, implementing methods from the protocol, and
setting the dataSource property.
// MARK: - NSWindowDelegate
// MARK: - NSTableViewDataSource
func numberOfRowsInTableView(tableView:
NSTableView) -> Int {
return voices.count
}
}
Run the app. The table view is displaying something, but it is not the names of
the voices (Figure 7.10).
Figure 7.10 Table view not displaying names
(If your table view is not displaying anything at all, remember the classic
protocol blunders: check that the table view’s dataSource property has been
connected and check the spelling of the data source method names.) What is
going on with this table view? First, note that the table view-data source
conversation is working. For each cell, the data source is returning an object, and
the table view is setting that object to the objectValue of the table cell view.
However, remember that updating the table cell view has no automatic effect on
the text field, so the text field in each table cell view defaults to displaying its
label – Table View Cell.
To fix this situation, you are going to use Cocoa bindings to bind the value of
each text field to the objectValue of its table cell view.
When you release the mouse, a pop-up will appear asking you to configure the
connection. Make it an outlet, name it tableView, and specify the type as
NSTableView (Figure 7.14). Then click Connect.
(You can also connect actions this way. When the pop-up for the new connection
appears, you change Outlet to Action, name the action, and specify the control
type.) Making connections using the assistant editor is faster than switching
between files, but faster is not necessarily better. For one thing, many
programmers are more comfortable writing code than dragging and dropping. Or
you may want to define your outlets and action methods before starting work on
the interface. When you are learning, switching between files can help cement
the idea of what is happening as you switch between your code and interface, but
feel free to use either process as you continue through the book.
To close the assistant editor and return to the standard editor, click the icon
on the Xcode toolbar.
func tableViewSelectionDidChange(notification:
NSNotification) {
let row = tableView.selectedRow
}
Build and run the application. Try out a new voice or two.
Pre-selecting the default voice
There is one nice feature left to add: when the application starts, the default
voice should be pre-selected in the table view so that the reader can see what
voice will be spoken in by default.
Within windowDidLoad(), add code to select the appropriate row and, if
necessary, scroll to it:
func windowDidLoad() {
super.windowDidLoad()
updateButtons()
speechSynth.delegate = self
for voice in voices {
println(voiceNameForIdentifier(voice)!)
}
let defaultVoice =
NSSpeechSynthesizer.defaultVoice()
if let defaultRow = find(voices, defaultVoice)
{
let indices = NSIndexSet(index:
defaultRow)
tableView.selectRowIndexes(indices,
byExtendingSelection: false)
tableView.scrollRowToVisible(defaultRow)
}
}
The find() function takes two parameters: a sequence and an element that is a
potential member of that sequence. It returns an optional index within the
sequence – or nil if the element is not in the sequence. It is most commonly
used to find the index of a value within an array, as you are doing here.
To test this new feature, you will need to change the default voice on your
system. You can do this in the Dictation & Speech preference pane in System Preferences.
In this chapter, you have implemented a simple table view – and it took a lot to
get there. Implementing table views is a complex topic. To learn more, visit
Apple’s Table View Programming Guide for Mac. You can find this or any other
Apple guide by opening the documentation (Window → Document and API Reference)
and searching for the guide’s title. As you will learn in Chapter 9, Cocoa
bindings can be used to avoid the table view data source code altogether.
However, manually implementing a data source provides a much greater degree
of control.
Challenge: Make a Data Source
Make a to-do list application. The user will type a task into the text field. When
the user clicks the Add button, you will add the string to an array and the new task
will appear at the end of the list (Figure 7.15).
Figure 7.15 Objects in to-do list app
How will you get the table view to fetch updated information? You tell the table
view to reload its data using the aptly-named method reloadData(). The table
view will then reload all the cells that the user can see.
You get extra points for making the table view editable.
8
KVC, KVO, and Bindings
Key-value coding (KVC) is a mechanism that allows you to get and set the value
of a property indirectly using a key. A key is the name of a property as a string.
KVC has been part of Cocoa for a long time, and important features of Cocoa,
like Core Data and Cocoa bindings, rely on KVC. To understand and use these
technologies, it helps to understand the basics of KVC.
To experiment with KVC, create a new playground. From Xcode’s File menu,
select New... → Playground. Name the playground KVC and save it with your
previous exercises.
At the top of the file, define a Student class as a subclass of NSObject. Give it
two variables with default values.
import Cocoa
let s = Student()
{__lldb_expr_...
s.setValue("Kelly", forKey:"name")
{__lldb_expr_...
s.setValue(3, forKey:"gradeLevel")
{__lldb_expr_...
To see the property values for the new instance, click the eye icon in the sidebar.
The KVC method for getting the value of a property with a key is
valueForKey(_:). Use this method to access the same properties.
s.name
"Kelly"
s.gradeLevel
3
s.valueForKey("name")
"Kelly"
s.valueForKey("gradeLevel")
3
Why are these abilities interesting? By themselves, they may seem like just an
interesting parlor trick, but many Cocoa features rely on the ability to get and set
values by key. Let’s take a look at one of those features – Cocoa bindings.
Bindings
Bindings use KVC as well as KVO (key-value observing, which you will learn
about shortly). Bindings can simplify and, in some cases, eliminate the code that
links the view layer and the model layer of an application. To see how this
works, you are going to create a new application named Thermostat.
Setting up Thermostat
In Xcode, create a new project (Command-Shift-N) that is a Cocoa Application. Name
the project Thermostat, set the language to Swift, and uncheck Use Storyboards,
Create Document-Based Application, and Use Core Data.
Finally, select the window and return to the attributes inspector. Change the
window’s title to Thermostat and uncheck the box labeled Resize to keep the window
from being resized when the application is running.
Using bindings
This application is similar enough to what you have done before that you can
probably imagine how you would write the code and wire up the interface: The
slider and label would display the default temperature on launch. The action
methods of the slider and the buttons would change the temperature and tell the
slider and label to update themselves according to the new value.
This would work, but bindings provide another route. You had a taste of
bindings in Chapter 7. Now you are going to use them to build Thermostat.
In MainWindowController.swift, add a temperature property and give it a
comfortable default value (in Fahrenheit).
class MainWindowController: NSWindowController {
var temperature = 68
}
The plan is to bind attributes of the slider and the label to this property. In
particular, you are going to bind the value attribute of the slider and the value
attribute of the label to the temperature key. Once bound, KVC will keep these
attributes in sync with the value of temperature.
To bind the slider’s value, select the slider and then click the icon to reveal
the bindings inspector. This inspector lists all of the attributes that you can bind.
Find and disclose the Value section. In the pop-up menu labeled Bind to, select File's
Owner. and then check the Bind to checkbox. This tells Interface Builder that you will
be binding the slider to a key found on MainWindowController.
Figure 8.2 Binding slider’s Value attribute to temperature key
In the Model Key Path field, delete the current value. When the field is blank, you
will be offered a drop-down menu of the properties available on File's Owner
(MainWindowController). Choose temperature.
The key path is a “model” key path because you typically bind a view object to a
piece of data in the application’s model. Indeed, the temperature property is the
model for this simple application.
Now bind the label’s value in the same way. Select the label and reveal the
bindings inspector. Bind to File's Owner and enter temperature in the Model Key Path
field.
(If you inadvertently check the Bind to checkbox before changing the value of the
pop-up to File's Owner, then a Shared User Defaults Controller object will be added to the
XIB. This is not a problem. You can simply select the object in the document
outline and delete it.) Run the application. The label and slider launch with the
default value displayed. When you move the slider, the label updates to display
the new value. All of this was built with no code other than declaring the
temperature property. (The buttons do not do anything yet, but you will fix that
soon.)
Figure 8.3 Label reports slider's new value
Behind the scenes, KVC is happening: the slider uses setValue(_:forKey:) to
change the value of temperature, and the label uses valueForKey(_:) to get the
new value.
How does the label know to call valueForKey(_:) to get an updated value? This
is thanks to another Cocoa technology – key-value observing.
Key-value observing
With KVO, an object can sign up to observe changes to the value of a key. When
the key’s value changes, the observer is notified. Bindings are an abstraction
layer on top of KVO and KVC which keep your Cocoa views in sync with the
model objects they are bound to. Here is how it works:
The label has an attribute bound to File's Owner.temperature. When the label is
created at runtime, it informs the MainWindowController (the File's Owner) that it is
observing the temperature key.
As an observer of temperature, the label is notified whenever the value of
temperature is changed by KVC. (What if the property is changed directly?
Hang on; you will see about that shortly.)
The slider is also bound to File's Owner.temperature, so it is also an observer of
temperature. At the moment, only the slider can change the value. Let’s hook up
the Warmer and Cooler buttons so that they can change the value and the slider can
respond to that change.
In MainWindowController.swift, add two action methods that use KVC to
change temperature.
...
}
Notice that these methods only contain code that updates the temperature; there
is nothing here that notifies the views to update themselves.
In MainWindowController.xib, connect the buttons to these actions. (Control-
drag from each button to File's Owner. Select the appropriate action from the list.)
Figure 8.4 Connecting buttons to actions
Run the application and click the buttons to adjust the temperature. Thanks to
KVO, the label and the slider both respond to changes in temperature.
}
If you like, run the application and confirm that clicking the buttons no longer
updates the slider or the label.
To explicitly trigger the notification of observers, you can use two KVO
methods inherited from NSObject. They are willChangeValueForKey(_:) and
didChangeValueForKey(_:). Try this with the Warmer button.
}
Now the slider and label will be notified before and after the value is changed.
Run the application and confirm that clicking the Warmer button now updates the
slider and the label.
The second way to ensure KVO compliance is using Swift’s dynamic keyword
on the property’s declaration.
In MainWindowController.swift, make the temperature property dynamic.
class MainWindowController: NSWindowController {
var temperature = 68
dynamic var temperature = 68
...
Now you can remove the KVO method calls from makeWarmer(_:):
...
}
Run the application and confirm that both buttons now work as expected.
Most of the time you will use dynamic to make a property KVO-compliant.
However, remember that KVO is not magical: observers of KVO-compliant
properties are notified immediately after the property is changed. This can affect
performance or result in surprising behavior in multi-threaded apps. In these
cases you will want the control offered by the willChangeValueForKey(_:) and
didChangeValueForKey(_:) methods.
Note that the dynamic keyword is not specific to KVO; it makes the property
behave like an Objective-C property so that it can benefit from the runtime
flexibility that Objective-C offers. Using KVO with bindings is one of those
benefits.
There are benefits to runtime flexibility, and there are also dangers. When you
use KVC, you are abandoning Swift’s type safety, and if not used carefully this
can result in runtime errors that are hard to debug. There is more information on
debugging bindings later in the chapter.
Binding other attributes
To clarify the idea of bindings, let’s add the ability to turn the thermostat on and
off. When the thermostat is off, the Warmer and Cooler buttons and the slider will
be disabled. So instead of binding Value attributes to a key, you will bind Enabled
attributes to a key.
In MainWindowController.swift, add an isOn property that will know how to
work with KVO.
class MainWindowController: NSWindowController {
...
Now you have a key to which you can bind your attributes. In
MainWindowController.xib, select the slider and open the bindings inspector.
Find the Enabled attribute and disclose its contents. Bind this attribute to File's Owner
and set the model key path to isOn.
Figure 8.5 Binding slider’s Enabled attribute to temperature key
Repeat this process with the Warmer and Cooler buttons.
Finally, you will need something to trigger the change in isOn. Drag a button on
to the bottom righthand corner of the canvas. Change its title to Power. To give
this button a different look, open the attributes inspector and change the button
style to Recessed. Figure 8.6 shows the results.
Figure 8.6 Adding a power button
A recessed button’s type defaults to Push On Push Off (as opposed to the Momentary
Push In type of the push buttons you have been using). The value attribute of a
button with this type is a boolean value for whether the button is currently
“pushed on” or “pushed off.” You can bind this attribute to isOn to toggle the
value of the property.
Select the power button in the canvas and return to the bindings inspector.
Reveal the Value attribute. Bind this attribute to File's Owner and set the model key
path to isOn.
Run the app and turn the thermostat off.
Figure 8.7 Turning off the thermostat
Your Thermostat is now fully functional. Return to MainWindowController.swift,
and see how little window controller code was required to make this happen.
class MainWindowController: NSWindowController {
override var windowNibName: String? {
return "MainWindowController"
}
dynamic var temperature = 68
dynamic var isOn = true
@IBAction func makeWarmer(sender: NSButton) {
temperature++
}
@IBAction func makeCooler(sender: NSButton) {
temperature--
}
}
Figure 8.8 is an object diagram of what you have done.
Figure 8.8 Object diagram with bindings
You are now likely wondering when to use bindings and when to update the
view layer in your controller code. The answer, of course, is “it depends.” Some
Cocoa APIs require bindings, so you must use them there. If you have a
relatively straightforward UI, bindings make an excellent choice. However, as
you will see in a moment, bindings can be difficult to debug – there is no code to
step through! We suggest that for more complicated user interfaces, you do
things the hard way. As you gain more experience with bindings – and there is a
lot more beyond what this book can cover – try using them in more complicated
settings. One aspect of bindings that is easy to forget is that they are difficult to
understand when you are new to a code base. It is hard to see them all at once
(there are no diagrams like Figure 8.8), so learning what bindings are present can
take a lot of poking around.
KVC and Property Accessors
KVC will call setters and getters for properties, if they are present. To see this in
action, add accessors for temperature:
dynamic var temperature = 68
private var privateTemperature = 68
dynamic var temperature: Int {
set {
println("set temperature to \(newValue)")
privateTemperature = newValue
}
get {
println("get temperature")
return privateTemperature
}
}
Run the application and move the slider to change the temperature. Notice how
the setter is called, followed by the getter, the latter of which corresponds to
bindings updating the label in response to the KVO notification. If you use the
buttons to change the temperature you will see the setter (property access) and
two getter calls (updating the slider and label).
In this example you changed temperature to a computed property. In Swift,
computed properties have no storage, so an additional property was needed to
replicate the existing behavior of this application.
Why is privateTemperature marked private? Usually this pattern of a
computed property with separate backing storage (the privateTemperature
property) is used to control access to a value, so it makes sense to mark it as
private to make sure that external code uses the intended interface for changing
it – in this case the computed property.
KVC and nil
What do you think happens if you use KVC to set a numeric type, such as a
Float or Int, to nil?
setValue(nil, forKey: "temperature")
Instead of making an assumption, such as assigning the value 0, KVC’s
designers decided to let the object decide what to do. When nil is set for
numeric type, KVC calls the method setNilValueForKey(_:). The default
implementation on NSObject throws an exception.
Although it is not needed in this application – none of the controls will try to set
it to nil – you could implement setNilValueForKey(_:) on
MainWindowController to handle this situation:
override func setNilValueForKey(key: String) {
switch key {
case "temperature":
temperature = 68
default:
super.setNilValueForKey(key)
}
}
Under what circumstances could this be an issue? One case is a text field bound
to a numeric property. If the user deletes all of the text and hits Return, the text
field interprets this as nil. You will see another solution to this problem, as well
as learn about a related topic, Key-Value Validation, in Chapter 10.
What about setting nil for other types? Because KVC depends on the Objective-
C runtime, this can be tricky. Optional reference types will work as expected: the
property will be set to nil. Some optional value types will work as well, such as
String?, array, set, and dictionary optionals. This works because all of these
value types have Objective-C object counterparts. Although this may change in
future versions of Swift and OS X, setting a numeric optional, Int? for example,
to nil via KVC presently results in a crash. You can work around this by using
the optional Objective-C counterpart, NSNumber?.
Debugging Bindings
Bindings are a powerful feature of Cocoa, but they are notoriously difficult to
debug. To a new Cocoa developer (and indeed even to the experienced), they
can be quite mysterious. Because they are generally configured in the XIB file it
can be difficult to track down the source of a problematic binding. Additionally,
because they use strings, the compiler cannot help you by checking that the key
actually exists on the object you are attempting to bind to.
The most common problem in using bindings is a key name typo, followed
closely by binding to the wrong object. You will see this error at runtime when
the related NIB file is opened: an exception will be logged to the console. There
will be a couple dozen lines of output, but the most interesting line will be at the
top of the output and look something like this:
[<Thermostat.MainWindowController 0x610000023260>
valueForUndefinedKey:]:
this class is not key value coding-compliant for
the key temperatur.
By pulling this line apart you can learn a few things about the error:
1. A binding was made to a non-existent key (valueForUndefinedKey:).
2. The target of the binding is MainWindowController.
3. The key string is "temperatur".
When you see an exception like this, think through the components of the
exception message. Did you expect to be binding to that key on that particular
object? Is the object or the key wrong? Comparing the answers to those
questions with your intentions will lead you to the source of the problem.
To see a list of all the bindings for a particular object, reveal its connections
panel by Control-clicking it (or its placeholder) in the document outline. The
bindings are listed at the bottom with the key in the left column and the object
binding to it in the right column (Figure 8.9). Think about each of the bindings
and make sure that it is what you expect.
Figure 8.9 Connections panel for MainWindowController
Using the Debugger
When an application is launched from Xcode, the debugger is attached to it. The
debugger monitors the current state of the application, like what method it is
currently executing and the values of the variables that are accessible from that
method. Using the debugger can help you understand what an application is
actually doing, which, in turn, helps you find and fix bugs.
Using breakpoints
One way to use the debugger is to set a breakpoint. Setting a breakpoint on a line
of code pauses the execution of the application at that line (before it executes).
Then you can execute the subsequent code line by line. This is useful when your
application is not doing what you expected and you need to isolate the problem.
Open AppDelegate.swift and find the applicationDidFinishLaunching(_:)
method. Set a breakpoint by clicking in the gutter (the lightly shaded bar on the
left side of the editor area) next to the first line of code in the method
(Figure 8.10). The blue indicator shows where the application will “break” the
next time you run it.
Figure 8.10 A breakpoint
Run the application. The application will stop execution before the line of code
where you put the breakpoint is executed. Notice the light green indicator and
shading that appear on that line to show the current point of execution.
Now your application is temporarily frozen in time, and you can examine it more
closely. In the navigator area, click the tab to open the debug navigator. This
navigator shows a stack trace of where the breakpoint stopped execution
(Figure 8.11). A stack trace shows you the methods and functions whose frames
were in the stack when the application halted.
Figure 8.11 The debug navigator and debug area
The method where execution stopped is at the top of the stack trace. It was called
by the method just below it, which was called by the method just below it, and
so on. Notice that the methods that you have written code for are in black text
while the methods belonging to Apple are in gray.
Select the method at the top of the stack, applicationDidFinishLaunching(_:).
In the debug area below the editor area, check out the variables view to the left
of the console. This area shows the variables within the scope of
MainWindowController’s makeWarmer(_:) method along with their current values.
(Figure 8.12).
Figure 8.12 Debug area with variables view
(If you do not see the variables view, find the button in the bottom-right
corner of the debug area. Click the left icon to show the variables view.) In the
variables view you will see three variables: aNotification, self, and
mainWindowController. Reference types are typically shown with their address –
location in memory – while value types show a representation of their value.
You can click the disclosure triangle to see more about non-scalar types. In Auto
mode, the variables view attempts to show the most relevant variables. In this
case that is the parameter of this method (aNotification), a reference to the
current instance, self, since the debugger is in the context of an instance
method, and the local variable mainWindowController.
You may have noticed that the local variable mainWindowController already has
an address shown, but the line initializing it has not yet executed. This is simply
because the compiler did not clear the memory, as an optimization.
Click the disclosure button next to self. The first item under self is the
superclass, in this case NSObject. The Swift module name, ObjectiveC, is also
shown. Clicking the disclosure button next to a superclass will show the
properties inherited from the superclass.
AppDelegate has one property shown, mainWindowController. Its value appears
as nil because it is an optional that has not been assigned.
mainWindowController will be set. Click the Step Over button on the debugger
bar. The debugger will stop again. Expand mainWindowController. The
internalTemperature is shown, as is isOn.
Click Step Over once again to step over the call to showWindow(_:). Note that
_window now has an address.
Back at the top level of the variables view, if you expand self you will see that
the mainWindowController property (which corresponds to
self.mainWindowController) is still nil. Step over the next line, the
assignment of that property, to see it change.
At this point, you could continue stepping through the code to see what happens.
Or you could click the Continue button to let your program run until the next
breakpoint. Or you could step into a method ( ). Stepping into a method takes
you to the method that is called by the line of code that currently has the green
execution indicator. Once you are in the method, you have the chance to step
through its code in the same way.
When you step out of a method, you are taken to the method that called it. To try
this, with Thermostat still stopped in the debugger, open
MainWindowController.swift and add a breakpoint in the setter for the computed
property temperature. Hit Continue . The app window will appear. Change the
temperature using the Warmer or Colder button, and execution will stop again, this
time in the setter. Click Step Out and you will find yourself in the action
method that called the setter.
Deleting breakpoints
To run your application normally again, you will be getting rid of both of the
breakpoints you set. However, since you are going to use the breakpoint in the
temperature setter in the next section, leave it there for now.
To find and delete the other breakpoint, switch to the breakpoint navigator by
clicking . Click on the MainWindowController.swift row in the breakpoint
navigator to jump to that line.
There are two ways to delete a breakpoint. The first is to right-click the blue
breakpoint in the gutter and select Delete Breakpoint. The second – and more fun –
way is to drag the breakpoint out of the gutter. It will change to a puff of smoke,
indicating that it has been removed.
Sometimes, a developer will set a breakpoint and forget about it. Then, when the
application is run, execution stops, and it looks like the application has crashed.
If an application of yours unexpectedly stops, make sure you are not halting on a
forgotten breakpoint.
You can also tell the debugger to break automatically on any line that causes
your application to crash or that causes an exception to be thrown.
Go to the breakpoint navigator, if it is not already shown. At the bottom of this
navigator, click the + icon and select Add Exception Breakpoint....
If your application is throwing exceptions and you are not sure why, adding an
exception breakpoint will help you pinpoint what is going on. Note that by
default this will stop on all exceptions, even normal exceptions thrown by C++
code deep within the frameworks. To only show ObjectiveC exceptions, right-
click on the All Exceptions row, select Edit Breakpoint, and in the popover that appears
change All to ObjectiveC.
temperature, and examine the stack shown in the debug navigator ( ). It will
look something like this:
#0 Thermostat.MainWindowController.temperature.setter
: Swift.Int
at MainWindowController.swift:20
#1 @objc
Thermostat.MainWindowController.temperature.setter :
Swift.Int
#2 NSSetLongLongValueAndNotify
#3 -[NSObject(NSKeyValueCoding) setValue:forKey:]
#4 -[NSObject(NSKeyValueCoding)
setValue:forKeyPath:]
#5 -[NSBinder
setValue:forKeyPath:ofObject:mode:validateImmediately:...
#6 -[NSBinder setValue:forBinding:error:]
#7 -[NSValueBinder
applyObjectValue:forBinding:canRecoverFromErrors:...
#8 -[NSValueBinder
applyDisplayedValueHandleErrors:typeOfAlert:canRec...
#9 -[NSValueBinder performAction:]
#10 -[NSBindingAdaptor
_objectDidTriggerAction:bindingAdaptor:]
#11 -[NSControl sendAction:to:] ()
... ...
There is a lot going on in there! You can trace it from frame #11, where the
control sends the action to its target – in this case the slider changing – which is
then captured by the bindings system. Bindings then converts this change into a
KVC call – notice the setValue:forKey: on frame #3, which corresponds to the
KVC methods you used earlier in this chapter. This finally results in a call to the
Swift setter.
All of the frames starting with -[ are ObjectiveC method calls. Methods
beginning with an underscore _ are private methods.
_NSSetLongLongValueAndNotify is a private C function.
Remove the setter breakpoint and place a breakpoint in the getter. When you run
the app it will stop immediately. The stack will show bindings setting the initial
values for the controls as the NIB is being loaded.
#0 Thermostat.MainWindowController.temperature.getter
: Swift.Int
at MainWindowController.swift
#1 @objc
Thermostat.MainWindowController.temperature.getter :
Swift.Int
#2 _NSGetLongLongValueWithMethod ()
#3 -[NSObject(NSKeyValueCoding) valueForKey:] ()
... ...
#7 -[NSValueBinder
observeValueForKeyPath:ofObject:context:] ()
#8 -[NSObject(NSKeyValueBindingCreation)
bind:toObject:withKeyPath:optio...
#9 -[NSIBObjectData
nibInstantiateWithOwner:options:topLevelObjects:]
#10 loadNib ()
#11 +[NSBundle(NSNibLoading)
loadNibFile:nameTable:options:withZone:owne...
#12 +[NSBundle(NSNibLoadingInternal)
_loadNibFile:externalNameTable:optio...
#13 -[NSWindowController loadWindow] ()
#14 -[NSWindowController window] ()
#15 -[NSWindowController showWindow:] ()
#16
Thermostat.AppDelegate.applicationDidFinishLaunching
at AppDelegate.swift
... ...
To get the selected person’s spouse’s scooter’s model name, you can use a key
path:
let modelName =
selectedPerson.valueForKeyPath("spouse.scooter.modelName")
as String
We would say that spouse and scooter are relationships of the Person class and
that modelName is an attribute of the Scooter class.
There are also operators that you can include in key paths. For example, if you
have an array of Person objects, you could get their average expectedRaise by
using key paths:
let average = (employees as
NSArray).valueForKeyPath("@avg.expectedRaise") as!
Float
Here are some commonly used operators:
@avg
@count
@max
@min
@sum
Now that you know about key paths, let’s see how you would create bindings
programmatically. If you had a text field in which you wanted to show the
average expected raise of the arranged objects of an array controller, you could
create a binding like this:
textField.bind(NSValueBinding, // "value"
toObject: employeeController,
withKeyPath: "[email protected]",
options: nil)
Of course, it is usually easier to create a binding in Interface Builder.
Use the unbind(_:) method to remove the binding:
textField.unbind(NSValueBinding)
For the More Curious: More on KeyValue
Observing
How did the label become an observer of the temperature key in the
MainWindowController object? The code to become an observer of this key might
look something like this:
let mainWindowController = ...
mainWindowController.addObserver(self,
forKeyPath: "temperature",
options:
NSKeyValueObservingOptions.Old,
context: &MyKVOContext)
This method is defined in NSObject. It is how you say, “Hey! Let me know
whenever temperature changes.” The options and context determine what extra
data is sent along when temperature changes. The method that is triggered looks
like this:
override func observeValueForKeyPath(keyPath: String!,
ofObject object:
AnyObject!,
change:
[NSObject : AnyObject]!,
context:
UnsafeMutablePointer<Void>) {
...
}
The keyPath, in this case, would be "temperature"; the object would be the
MainWindowController; context would be the pointer to MyKVOContext that was
supplied as the context when you became an observer; and change is a dictionary
(a collection of key-value pairs) that can hold the old value of temperature
and/or the new value.
You will see these APIs in action in Chapter 11.
For the More Curious: Dependent Keys
In some cases a value may be dependent on another. For example, if you have a
Person class with a computed property fullName that is dependent on the
properties firstName and lastName, wouldn’t it be nice if observers of fullName
could be notified when either firstName or lastName changes? KVO’s designers
thought so, too, which is why they implemented a convention for defining
dependent keys.
To define a key’s dependencies you must implement a specially named class
method which returns a Set of key paths. In the example above you would
implement keyPathsForValuesAffectingFullName():
class Person {
dynamic var firstName = ""
dynamic var lastName = ""
dynamic var fullName {
return "\(firstName) \(lastName)"
}
class func keyPathsForValuesAffectingFullName() ->
NSSet {
return Set(["firstName", "lastName"])
}
}
Note the naming of this method: keyPathsForValuesAffectingKey(). Do not
forget that it is a class method. KVO will not pick up on it if you forget and
make it an instance method.
Challenge: Convert RGBWell to Use Bindings
In Chapter 5 you used action methods to trigger manually updating the r, g, and
b properties. Convert RGBWell to use bindings instead.
Create a new project in Xcode. In the Application section, under the OS X header,
choose Cocoa Application for the type.
Configure the options for your project as shown in Figure 9.2: set Product Name to
RaiseMan and Language to Swift. Uncheck Use Storyboards and check Create
DocumentBased Application. Set the Document Extension to rsmn and uncheck Use Core Data.
An icon for the Array Controller will appear at the top level in Interface Builder’s Icon
View.
The array controller needs to know what kind of object will be stored in its
content array. Knowing the class allows the array controller to create new
objects. With the Array Controller still selected, switch to the attributes inspector.
Locate the Object Controller section. Set the Class Name to RaiseMan.Employee. Note
that you prefix the class’s name, Employee, with RaiseMan, the name of the
module where Employee lives. Unless otherwise specified, your classes will live
inside your product module, whose name by default matches your product name.
Note that if Employee were an Objective-C class, the prefix would be
unnecessary.
Xcode should look something like Figure 9.7.
This binding sets up the input of the array controller to be the employees array of
the Document. After you have finished configuring all the bindings, this input will
be transformed by the array controller and displayed in the table view.
Recall that an array controller’s Content Array is the array that the array controller
manages. The content array is not affected by any filtering or sorting that you
configure on the array controller. When you add or remove objects via the array
controller, however, those changes will be reflected in the content array.
Binding the Table View’s Content to the
Array Controller
In Chapter 7, you implemented a data source for the table view. The table view
asked the data source for the number of rows, then for the data for each cell. As
we mentioned earlier in this chapter, none of that code is necessary with
bindings and array controllers. When a table view has its Content binding set, it
does not ask the data source for cell data.
Select the table view. Remember, selecting the table view can be tricky; it is
easy to select the scroll view or clip view that contains the table view rather than
the table view itself. Use the document outline or the jump bar to make sure you
have selected the table view and not one of its superviews.
With the table view selected, switch to the bindings inspector and find the Table
Content section. Bind the table view’s Content to the Array Controller’s
arrangedObjects. Leave the Model Key Path empty. Do not panic if you see a red
error indicator at the right-hand side of the Model Key Path field; this is just Xcode
trying to be helpful.
Remember, the array controller’s arrangedObjects is the filtered and sorted
representation of its input – in this case the Document’s employees array.
Connecting the Add Employee Button
Now that you have set up bindings from the table view to the array controller,
the next step is to hook up the Add Employee button.
Unlike what you have done in previous exercises, in this exercise you will not
set the buttons’ target to be File's Owner (recall that in this case, File's Owner is
the document). Instead, NSArrayController is again going to come to your aid to
serve as a controller object and augment your Document class: The array
controller will be the target of the buttons, and their actions will be methods on
NSArrayController (Figure 9.9).
Figure 9.9 The wiring between the buttons and the array controller
Control-drag from the Add Employee button to the Array Controller to set the array
controller as the target of the button (Figure 9.10). Set the action to add:.
Figure 9.10 Setting the array controller as the Add Employee button’s
target
Run the app. When you click the Add Employee button, a new row is added to the
table view. This row corresponds to an Employee object in the array controller’s
content array, but you will have to take our word for it, since the cells still say
Table View Cell. You will fix that next.
Binding the Text Fields to the Table Cell
Views
Just like with a data source, when a table view’s Content is bound, each row in the
table view maps to an object in that array. Each cell in the row, an instance of
NSTableCellView, has its objectValue set to that object. Controls within those
cells, such as text fields, will typically display the value of a property on that
object. In RaiseMan, the objectValue of each cell will be the Employee object for
that row.
Therefore, you need to bind the text fields in RaiseMan’s columns to the cell
views’ objectValue. You will follow a similar approach to the one you used
when configuring the binding in SpeakLine.
The employee’s name will be displayed in the first column’s cell. To set this
binding up, select the appropriate text field (inside the first table column’s table
cell view) by expanding the document outline and drilling down into the view
hierarchy until you have gone a couple of levels beyond the Table View, as shown
in Figure 9.11.
Figure 9.11 Selecting the text field in the first column of the table view
Make sure you have selected the NSTextField and not the NSTextFieldCell! This
can be confusing because they have the same label in the document outline.
Remember, the text field is a control and so it has an icon resembling a slider.
In the bindings inspector, find the Value binding and expand it. Check Bind to and
select the Table Cell View in the pop up. Leave the Controller Key empty and set the
Model Key Path to objectValue.name (Figure 9.12). A Cocoa programmer would
say that you are binding the text field’s value to the table cell view’s
objectValue’s name.
In SpeakLine, you set the Model Key Path to be objectValue. Why did you not do that
this time? In SpeakLine, the table cell view’s objectValue was a String; in RaiseMan,
though, it is an Employee. This means that the text fields’ values cannot be bound
directly to the table cell views’ objectValue (a text field does not know how to
display an Employee). Instead, you need to bind each text field’s value to the
appropriate property on objectValue.
Figure 9.12 Binding the text field to the table cell view’s Employee’s name
With the text field still selected, switch to the attributes inspector. Find the pop-
up button labeled Behavior and change it to Editable. This will allow the user to
change the employees’ names. That is, when the user uses this text field to edit
an employee’s name, that change is reflected in the Employee object. Pretty
amazing, isn’t it? The bindings that you have set up allow user interactions in the
view layer to pass back via the array controller and update the model layer.
Try running the app now. You should be able to add employees and edit their
names in the first column of the table view. The second column will remain
fixed. Your next step is to display the employees’ raises there.
Select the text field within the Raise column’s table cell view. (As before, make
sure to select the NSTextField and not the NSTextFieldCell. Check that your
selection matches the selection shown in the document outline in Figure 9.13.) In
the bindings inspector, bind Value to the Table Cell View with a Model Key Path of
objectValue.raise, much as you did with the first column (Figure 9.13).
Figure 9.13 Binding the text field to the table cell view’s Employee’s raise
With the text field still selected, switch to the attributes inspector and change the
Behavior to Editable, as before. This will allow the user to change the employees’
raises.
Try running RaiseMan at this point. You should be able to give employees
raises. Since an Employee’s raise is a Float, raises will be displayed in decimal
format by default. This style of display is not great. It would be much better to
display the raises as percentages.
Formatting the Raise Text Field
To display the raises correctly, as percentages, you will use a number formatter.
Find the Number Formatter in the object library and drag it onto the second column’s
table cell view (Figure 9.14).
Figure 9.14 Add a number formatter
Notice the location of the number formatter in the document outline. It will show
up nested inside the text field’s cell (Figure 9.15). If you need to select it again,
you can do this most easily using the document outline.
Figure 9.15 The number formatter in the document outline
With the number formatter still selected, open the attributes inspector and
configure it to display the number as a percentage by selecting Percent from the
Style pop-up menu. Next, check the Lenient checkbox; this will make the formatter
less picky about input (Figure 9.16).
Figure 9.16 Number formatter attributes
Making the number formatter lenient stops your app from punishing your users
for what they are likely to consider reasonable input. For example, with lenient
unchecked, 5 will be rejected. In a non-lenient percent number formatter, all
entries must end with a percent sign, so your user would have to enter 5% instead
of 5. (Note that if you enter a decimal such as .42, the lenient formatter will
interpret it as 0.42%, round it down, and display it as 0%.)
Run the app. You should be able to add Employee objects. You should be able to
edit the attributes of the Employee objects using the table view. You cannot
remove them yet – that is next. Finally, you should be able to open multiple
untitled documents. (No, you cannot save those documents to a file. Soon,
Grasshopper.)
Connecting the Remove Button
Next, you will connect the Remove button to the remove(_:) method on the array
controller. This will be much the same as connecting the Add Employee button, but
there are some additional considerations, as you will see.
Control-drag from the Remove button to Array Controller and set the action to
remove:.
Run the application. Add two employees and give them pleasant names. Select
the first one and click Remove. Contrary to your notions of all that is good and
right in the world, the second employee has been removed! Now that nothing is
selected, try clicking Remove once more. Even though no rows are selected, a row
will be removed! What is going on here?
The array controller’s remove(_:) method removes selected objects in the
content array. Even though a row is selected in the table view, the array
controller does not know anything about it, which can lead to some surprising
results.
To fix this, you need to set up a binding to make the array controller’s selection
stay in sync with the table view’s selection.
Binding the Table View’s Selection to the
Array Controller
The selection indexes of an array controller are the indexes in arrangedObjects
of the items which are currently selected. Note that the selection indexes are not
indexes in the array controller’s content array: if you use a selection index to get
an object out of the content array, you will not get the selected object. The
selection in an array controller determines which items will be affected when the
array controller performs certain actions. For example, as you just saw, when
you trigger the remove(_:) action on an array controller, the items corresponding
to these indexes in arrangedObjects will be removed.
Bind the table view’s Selection Indexes to the Array Controller’s selectionIndexes.
Leave the Model Key Path empty.
Once you have done this, your bindings inspector should look something like
Figure 9.17.
Figure 9.17 Binding the table view to the array controller
Now the table view’s selection of rows will stay in sync with the array
controller’s selection of employees.
Run the app. Add an employee and then deselect it. Click the Remove button. The
table view will not be affected. Select the employee’s row and click the button
again. The employee will be removed.
Now that the table view’s selection mirrors the array controller’s selection, you
can see the reason for the unexpected behavior from earlier: When the array
controller creates a new object, by default it will select that new object. When
you clicked Remove, the array controller removed what it had selected: the last
employee that you had added.
The Remove button is almost working the way you want it to, but not quite. When
no employees are selected, the user should not be able to click the Remove button.
The button should be disabled whenever no employees are selected. To do this,
you will set up a binding between the button and the array controller.
Configuring RaiseMan’s Remove Button
Now that your array controller knows what the user has selected in the table
view, you can use the array controller to ensure that the Remove button is only
enabled when appropriate – when the array controller has an object selected.
This will prevent your users from clicking Remove when they have not selected an
employee to remove.
You will bind the button’s enabled property to the array controller’s canRemove
property. This property is true only when the array controller can actually do
removal. In other words, this property will be false when the array controller
does not have any objects selected.
To set up this binding, select the Remove button and switch to the bindings
inspector. In the Availability section, bind the button’s Enabled attribute to the
canRemove property of the NSArrayController, as shown in Figure 9.18.
The user will also want to be able to remove the selected employees by pressing
the Delete key on their keyboard. With the Remove button still selected, switch to
the attributes inspector. Set the Key Equivalent to be the Delete key (Figure 9.19).
To set the key equivalent, click in the text field and then press the key you wish
to associate with this button. Xcode will record the keystroke used and display it
in the text field. To remove a key equivalent, click the x on the right-hand side of
the text field.
Figure 9.19 Making the Delete key trigger the Remove button
Run your application. When you click the Add button, a new row should be added
to the table view. When you select a row in the table view and click the Remove
button, the selected row should be removed from the table view.
Sorting in RaiseMan
You are now ready to add sorting to RaiseMan. When you are done, the user will
be able to reorder the employees table by clicking on the table column headers.
This action translates to setting the criteria by which the rows will be sorted.
When a user clicks on a table column header, the table view responds by
changing its sort descriptors. Sort descriptors, represented by the
NSSortDescriptor class, describe how to sort an array of objects. Each sort
descriptor contains a key which is a KVC key path, whether it should be
ascending (as opposed to descending), and a comparison selector, or method
used to compare the two keys. Because sorting with NSSortDescriptor relies
upon KVC, it can only be used with NSObject-based classes.
In a table view, each column corresponds to a sort descriptor. Clicking on the
column will make that column’s sort descriptor take effect. Subsequent clicks
will toggle whether it is ascending or descending. As you will see later in the
chapter, multiple sort descriptors can be used to resolve ambiguity.
As you have done throughout the chapter, you will take advantage of
NSArrayController, this time to do the sorting for you. You have two steps to
complete: First, you will configure each table column’s sort descriptor
properties. The Name column will sort using Employee’s name key, and the Raise
column will use the raise key. Second, you will use bindings to tie the table’s
sort descriptors to the array controller. All of this will be configured in
Document.xib; you will not have to write any code at all.
Start out by configuring the Raise column. Open Document.xib and use the
document outline or the view hierarchy popover to select the Raise column.
Switch to the attributes inspector. Next to Sort Key, enter raise. This makes the
column sort the objects in the table view based on their values for the raise key.
When you press return to end editing, you should see the Selector change to
compare: (Figure 9.20). This selector specifies that the column will use that
NSNumber’s compare(_:) method to order the values for the raise key.
Figure 9.20 The Raise column configured to sort using the raise key
Next, configure the Name column. In Document.xib, select that column.
You are going to make this column sort by comparing the names of the
employees without concern for capitalization. In the attributes inspector, set the
Sort Key to be name and the Selector to be caseInsensitiveCompare:, as shown in
Figure 9.21. This column will now sort the objects displayed in the table view by
putting the values for their name key in the order determined by String’s
caseInsensitiveCompare(_:) method.
Figure 9.21 The Name column configured to sort using the name key
At this point, you have finished configuring the sort mechanism of the table
view. When the user clicks on the column headers, the table view will update its
sortDescriptors property. However, if you run the app now, nothing will
happen when column headers are clicked. That is because even though the table
view’s sort descriptors are updated, the array controller’s configuration is not
currently changing to match, and the array controller determines the order in
which objects are displayed in the table view.
To fix this, you will need to bind the table view’s sort descriptors to the array
controller’s sortDescriptors property, which determines the order of its
arrangedObjects array.
Select the Table View. Switch to the bindings inspector and expand the Sort
Descriptors binding in the Table Content section. Bind the Sort Descriptors to the Array
Controller. Set the Controller Key to sortDescriptors. Leave the Model Key Path blank.
Your binding should look like Figure 9.22.
Figure 9.22 Binding the table view’s sort descriptors to the array controller
Run your application. Try clicking on the Name and Raise column headers to sort
the data. Notice that if you click the same column twice in a row, you will
reverse the ordering from the first click. This corresponds to changing the sort
descriptor for to that column from ascending to descending.
How Sorting Works in RaiseMan
Often, a single sort descriptor will not completely sort a list of objects. To see
this in action, run RaiseMan and add three employees. Give them all different
names. Give one of them a larger raise (20%, say) and leave the other raises
alone. You should have a setup like Figure 9.23.
Figure 9.23 Three employees added in RaiseMan
Click on the column header of the Raise column. This will add a sort descriptor to
the table view’s sortDescriptors array. Bindings will keep the array controller’s
sortDescriptors in sync, so its arrangedObjects will be rearranged. The order of
the rows in the table view will now list the employees in ascending order of
raises: the two employees with smaller raises should appear first, followed by
the employee with the larger raise (Figure 9.24).
Figure 9.24 Three employees ordered by raise
The array controller has used the sort descriptor (which uses the raise key and
NSNumber’s compare(_:) method) to sort the employee with the larger raise below
the other employees, but it has not sorted the two employees relative to one
another. The two employees with the smaller raise are left in the same relative
order.
In situations like this, it makes sense to apply multiple sort descriptors at once.
Try this out by clicking on the Name column. You will see that the your
employees are ordered alphabetically. The array controller is now sorting first by
employee name and then (if there are two employees with identical names) by
raise. When you click on a table column header, the sort descriptor for that table
column is moved to the front of the array controller’s sortDescriptors array
(Figure 9.25).
Figure 9.25 Three employees sorted first by name, then by raise
In this case, unless you have given two employees the same name, only the name
sort descriptor is having any effect. Both sort descriptors are applied, but the
sort descriptor is having any effect. Both sort descriptors are applied, but the
employees are totally ordered by the name sort descriptor, so the raise sort
descriptor does not alter the order.
To see both two descriptors in action, you should sort first by raise and then by
name. To tell the array controller to do this, click again on the Raise column. This
will move the raise column’s sort descriptor from the back of the
sortDescriptors array to the front. As a result, the array controller will sort first
by raise and then by name. You will see the employee with the larger raise
remain at the bottom. Now, though, the two employees with smaller raises will
be sorted by name (Figure 9.26).
Figure 9.26 Three employees sorted first by raise, then by name
For the More Curious: The
caseInsensitiveCompare(_:) Method
In the previous section, you specified that the Selector used to sort the Name column
should be caseInsensitiveCompare:. The caseInsensitiveCompare: selector
corresponds to the method caseInsensitiveCompare(_:) on String. You can use
that method programmatically as follows:
let motorsickle: String = "Piaggio"
let motorsigh: String = "Italjet"
let order: NSComparisonResult =
motorsickle.caseInsensitiveCompare(motorsigh)
// Would motorsickle come first in the dictionary?
if order == .OrderedAscending {
}
Notice that caseInsensitiveCompare(_:) returns an NSComparisonResult. The
NSComparisonResult describes the how the first string (motorsickle in this
example) is ordered in relation to the second (motorsigh here) – in other words,
which comes first, if either.
An NSComparisonResult can take on one of three values:
OrderedAscending: The first value comes before the second value. For
example, if you compared 5 to 7, you would get this result—5 is less than 7.
OrderedSame: Both values are in the same position under the ordering. For
example, with caseInsensitiveCompare(_:), “PICKLE” and “pickle” are
ordered the same because you are not considering capitalization.
OrderedDescending: The first value comes after the second value. For
example, if you compared 7 to 5— 7 is greater than 5.
For the More Curious: Sorting Without
NSArrayController
In Chapter 7, you populated a table view by implementing the data source
methods. You might have wondered at that time how you could implement this
sorting behavior in SpeakLine.
Recall that clicking on the table column headers updates an array of
NSSortDescriptor objects, which the array controller then applies to reorder its
content. You can sort any array of NSObject-based objects yourself using the
following method on NSArray:
func sortedArrayUsingDescriptors(sortDescriptors:
[AnyObject]) -> [AnyObject]
When is the appropriate time to sort? When the user clicks on the header of a
column, an optional NSTableViewDataSource method will be called:
optional func tableView(tableView: NSTableView,
sortDescriptorsDidChange
oldDescriptors: [AnyObject])
Thus, if you have an array that holds the information for a table view, you can
implement the method like this:
@IBOutlet weak var tableView: NSTableView!
var objects: [SomeObject]
func tableView(tableView: NSTableView,
sortDescriptorsDidChange
oldDescriptors: [AnyObject])
{
let sortDescriptors =
tableView.sortDescriptors
let objectsArray = objects as NSArray
let sortedObjects =
objectsArray.sortedArrayUsingDescriptors(sortDescriptors)
objects = sortedObjects
objects = sortedObjects
tableView.reloadData()
}
And voilà!, sorting in your application.
For the More Curious: Filtering
As discussed earlier, NSArrayController arranges its objects by filtering and
sorting. These two lenses correspond to properties on NSArrayController:
filterPredicate, an NSPredicate, and sortDescriptors, an array of
NSSortDescriptor objects.
The document outline, as you have already seen, is displayed on the left-hand
side of the Interface Builder editor. You have already been using it very effectively
to select objects in the XIB. Its biggest advantage over the other mechanisms is
that it shows you the entire hierarchy of items in the XIB.
You have also had a few opportunities to use the jump bar. It is very nice to be
able to glance up at it to see what is currently selected. In Figure 9.28, the jump
bar shows that the Name table column is selected. You can also click on any of
the items in the jump bar to present a menu which will allow you to drill into
that item’s children.
Figure 9.28 The Name table column displayed in the jump bar
The third tool, which has not been discussed yet, is the view hierarchy popover.
This tool’s greatest strength is the speed with which it allows you to select
deeply nested objects.
Switch over to RaiseMan’s Document.xib to try it out. Hover your mouse
somewhere over the first column of the table view in Interface Builder’s canvas
(Figure 9.29).
Figure 9.29 Positioning the mouse pointer somewhere over the Name table
column
With your mouse still in that position, hold down Control and Shift and then
click. You will see a popover listing all of the objects under your mouse. You
can then select any of the objects from the list to jump to it. Try it out by
selecting the Name table column (Figure 9.30).
Figure 9.30 Selecting the Name table column from the view hierarchy
popover
After selecting the table column, you can double-check that you have the right
object selected by looking at the jump bar.
Challenge: Sorting Names by Length
Make the application sort people based on the number of characters in their
names. You can complete this challenge using only Interface Builder – the trick is to
use a key path. (Hint: Strings have a length method.)
10
Formatters and Validation
Users are complicated. They have strong opinions about how they want to input
and see data. As a developer, you want to support their whims, to get that I
thought it might do this and it does! reaction. With formatters your text fields
can present and accept data textually without cluttering your controller.
Formatters also provide validation at the controller layer, while Key-Value
Validation allows you to validate at the model layer.
Formatters
Formatters convert strings into data and data into strings. In the same way that
an array controller is a lens for an array, changing the model of your application
into a representation for the view layer, a formatter is a lens for one piece of data
that can be represented to the user as a string.
Formatters, programmatically
Each formatter works with a specific type of data. NSNumberFormatter works
with numeric types, while NSDateFormatter works with NSDate objects.
Suppose you had a date in a playground:
let date = NSDate() // "Feb
28, 2015, 8:07 PM"
You could get a string using description:
date.description // "2015-
03-01 01:07:59 +0000"
That is fine for debugging, but it does not provide much in the way of options.
Instead, you can use a date formatter to generate a string with a medium length
date and a short time:
let dateFormatter = NSDateFormatter() //
<NSDateFormatter: 0x7f8a558016c0>
dateFormatter.dateStyle = .MediumStyle
dateFormatter.timeStyle = .ShortStyle
dateFormatter.stringFromDate(date) // "Feb
28, 2015, 8:07:59 PM"
Note that the playground appears to be using the same settings when
representing NSDate objects. You can also generate an NSDate from a string, if the
string matches the expected format:
let destination: NSDate? =
dateFormatter.dateFromString("Oct 26, 1985, 01:21 AM")
If you have a specific format you need, specify a dateFormat string. You will use
this technique for parsing dates from JSON in Chapter 28. Otherwise, use the
preset styles in order to take advantage of the user’s locale settings.
Formatter styles set multiple properties at once. Therefore you will want to rely
on styles for course-grained settings, fine tuning with properties afterward. Here
is an example with NSNumberFormatter:
let ransom = 1_000_000 //
1000000
let numberFormatter = NSNumberFormatter()
numberFormatter.numberStyle = .CurrencyStyle
numberFormatter.stringFromNumber(ransom) //
"$1,000,000.00"
numberFormatter.maximumFractionDigits = 0
numberFormatter.stringFromNumber(ransom) //
"$1,000,000"
The formatter, however, will allow you to enter the empty string. This is a
problem for a couple of reasons. First of all, an “empty” raise is not meaningful.
More pressingly, this is a problem because it causes KVC to throw an exception.
Try this in RaiseMan: add an employee, click to select the raise, delete the text, and
press Return. Apparently, nothing happened: the Raise text field is still being
edited. But if you look in the Xcode console, in the debug area, you will see the
exception:
RaiseMan[13301:1762192] An uncaught exception was
raised
RaiseMan[13301:1762192] [<RaiseMan.Employee
0x60000004def0> setNilValueForKey]:
could not set nil as the value for the key raise.
This is what happens when an exception is thrown while handling an event: the
exception is logged to the console and processing that cycle of the event loop is
aborted. If you perform an action in your Cocoa app and nothing happens, check
the console for an exception.
Here is what is causing this exception:
1. You enter the empty string into the text field.
2. The number formatter transforms the string into an optional NSNumber. In
this case it is nil. This is the text field’s new objectValue.
3. The bindings system checks whether it needs to validate this value.
Validation is not enabled for the binding between the text field and the
employee, so none is done.
4. Bindings uses KVC to set this value for the Employee’s key "raise".
5. While KVC knows how to transform any NSNumber into a Float, it does not
know how to transform nil into a Float. So KVC throws an exception.
To prevent this exception from being thrown – and to prevent the user from
entering non-meaningful input – you will add validation for this binding. In
particular, you are going to make the Employee class provide validation for its
raise key.
Enabling validation for a particular binding from the object that is being bound
to – Employee in this case – requires two steps: First, you must make that binding
opt into validation. Second, you must actually write the method that validates the
input coming from the bindings system.
You will start out by enabling validation for the binding. Remember, the one you
are interested in here is the binding from the Value of the text field in the Raise
column to the objectValue.raise keypath of its Table Cell View.
Open Document.xib and select that text field using the document outline or using
the view hierarchy popover, as described in the section called “For the More
Curious: Using Interface Builder’s View Hierarchy Popover” . Switch to the
bindings inspector, expand the Value binding, and check Validates Immediately
(Figure 10.3).
Figure 10.3 Enabling validation for the Raise text field
With this option checked, the bindings system will try to ask the Employee object
to validate the value that the user has entered before updating Employee with the
new value.
validateKEY(_:error:) When you enter a new value into the text field, the
bindings system will look for a method named according to the following
scheme: validateKEY(_:error:). Since the key in this case is raise, the system
will look for a method called validateRaise(_:error:) on Employee. This
method, if it exists, should have the following signature:
func validateRaise(raiseNumberPointer:
AutoreleasingUnsafeMutablePointer<NSNumber?>,
error outError: NSErrorPointer)
-> Bool
The method takes a pointer to an optional NSNumber and a pointer to an NSError.
It takes pointer parameters because they allow pass-by-writeback. This means
you can use them to pass information back to the code that calls this method:
You can pass an optional NSNumber back to the calling code through the first
parameter and an NSError through the second parameter.
The first parameter also contains the value that bindings is trying to validate.
You access this value using the memory property: raiseNumberPointer.memory.
The method returns a Bool. If you return false, you are saying that the value in
the raiseNumberPointer is invalid. Typically, when the value is invalid, you will
want to pass an NSError describing why back to the calling code through the
error parameter.
If you return true from the method, you are saying that the value contained in
the raiseNumberPointer when the method returns is valid. Notice that you can
return true without saying that the value the method was called with was valid:
You can change the value in raiseNumberPointer’s memory property in your
method’s implementation.
Open Employee.swift and implement validateRaise(_:error:) to validate only
non-nil numbers:
func validateRaise(raiseNumberPointer:
AutoreleasingUnsafeMutablePointer<NSNumber?>,
error outError: NSErrorPointer)
-> Bool {
let raiseNumber = raiseNumberPointer.memory
if raiseNumber == nil {
let domain =
"UserInputValidationErrorDomain"
let code = 0
let userInfo =
[NSLocalizedDescriptionKey : "An
employee's raise must be a number."]
outError.memory = NSError(domain: domain,
code: code,
userInfo:
userInfo)
return false
} else {
return true
}
}
Be careful when typing this method name in. Xcode will not auto-complete its
signature for you.
In this implementation, you check whether raiseNumberPointer contains a nil
value. If it does, you indicate that the value is invalid by generating an error,
writing that error into the outError pointer’s memory, and returning false. If
raiseNumberPointer contains a non-nil value, you indicate that the value is
valid by returning true.
Run the app and add a new employee. Try entering the empty string into the Raise
text field. You should see an error as in Figure 10.4.
Figure 10.4 The error presented when entering the empty string
If the user now chooses the Undo menu item, the first invocation is taken off the
stack and invoked. This would change the person’s raise back to five percent. If
the user chooses the Undo menu item again, the person’s name would change
back to New Employee.
Each time an item is popped off the undo stack and invoked, the inverse of the
undo operation must be added to the redo stack. Thus, after undoing the two
operations described, the undo and redo stacks should look like Figure 11.2.
Figure 11.2 The revised undo stack
The undo manager is quite clever: When the user is doing edits, the undo
invocations go onto the undo stack. When the user is undoing edits, the undo
invocations go onto the redo stack. When the user is redoing edits, the undo
invocations go onto the undo stack. This stack management is handled
automatically for you; your only job is to give the undo manager the inverse
invocations that need to be added.
Using NSUndoManager
Now that you have a general idea of how your code will interact with the undo
manager, let’s look at a more concrete example. Suppose that you are writing a
thermostat application. It has a property, undoManager, an instance of
NSUndoManager, as well as a method, makeItHotter(), whose inverse is
makeItColder(). Here is how you would implement undo for it:
func makeItHotter() {
temperature += 10
undoManager.prepareWithInvocationTarget(self).makeItColder()
showTheTemperature()
}
Two steps are performed: the prepareWithInvocationTarget(_:) method is
called on the undo manager. It returns an object which you then call
makeItColder() on. The return type of prepareWithInvocationTarget(_:) is
AnyObject. The object it returns does not implement makeItColder(), however,
so forwardInvocation(_:) gets called with makeItColder: wrapped up in an
NSInvocation. This object returned by prepareWithInvocationTarget(_:) has a
clever override for forwardInvocation(_:) such that it adds the invocation for
makeItColder: to the undo stack.
Instead, you will need to hook into the key-value coding mechanism that the
NSArrayController is leveraging to add and remove employees. KVC defines a
number of optional methods by convention which you can implement to pull off
exactly this sort of task. You will implement a couple of these methods on
Document; KVC will call them when the array controller is changing the
employees array. In your implementations, you will add code to register inverse
operations with the undo manager when employees are added to or removed
from the array.
In RaiseMan, the employees property on Document represents a relationship between
Document and Employee of a particular type: it is an ordered to-many relationship.
There are four different categories of properties which KVC recognizes:
1. Simple attributes. Example: Each student has a first name. Simple attributes
are typically numbers or instances of String, NSDate, or NSData.
2. To-one relationships. Example: Each student has a school. It is like a
simple attribute, but the type is a complex object, not a simple one.
To-one relationships are implemented using references: An instance of
Student has a reference to an instance of School.
Note, then, countOfSongs is not just a nice name for a property which describes
the count of a songs property. Instead, this name is determined by convention to
be what the key-value coding mechanism goes looking for.
There are several similar conventions for method names which KVC will look
for when methods are called or properties are accessed on the mutable array
proxy:
let playlist: Playlist = Playlist()
let arrayProxy =
let arrayProxy =
playlist.mutableArrayValueForKey("songs")
let countFromProxy = arrayProxy.count // is the same
as
let countFromPlaylist = playlist.countOfSongs // if
countOfSongs exists
let fifthFromProxy = arrayProxy.objectAtIndex(5) as!
Song // is the same as
let fifthFromPlaylist =
playlist.objectInSongsAtIndex(5) // if the method
exists
let song = Song(name: "Your Favorite Music")
arrayProxy.insertObject(song, atIndex: 4) // is the
same as
playlist.insertObject(song, inSongsAtIndex: 4) // if
the method exists
arrayProxy.removeObjectAtIndex(3) // is the same as
playlist.removeObjectFromSongsAtIndex(3) // if the
method exists
There is a similar set of calls for unordered to-many relationships (Figure 11.4).
Figure 11.4 Key-value coding for unordered relationships
employees.append(employee)
}
At this point, you have made it possible to undo deletions and insertions.
Undoing edits will be a little trickier. Before tackling this task, build and run
your application. Test the undo capabilities that you have at this point. Note that
redo also works.
KeyValue Observing
You’ve now seen how key-value coding works, both for simple properties and
to-one relationships on the one hand, and for collection properties and to-many
relationships. Key-value observing (KVO), on the other hand, allows you to be
informed when an object’s properties are changed, whether by KVC or by direct
property access.
You saw in Chapter 8 how bindings rely on KVO: objects bound to a key are
automatically added as observers of that key and are then notified whenever its
value changes. To enable undo capabilities for edits, you will need to add an
observer manually. In particular, you want your document object to be an
observer for the keys raise and name for all the Employee objects in its employees
array.
The following method on NSObject allows you to register to be informed of these
changes:
func addObserver(observer: NSObject,
forKeyPath keyPath: String,
options: NSKeyValueObservingOptions,
context: UnsafeMutablePointer<Void>)
You call this method on the object whose property you want to observe. The first
argument that you supply is the observer, the object which should be informed
when the property changes. You pass the name of the property that you want to
observe as the second argument. (Notice that you specify a key path rather than
just a key. This means that, rather than simply observing an object’s property,
you can observe an object’s property’s property. For example, you could
observe a person’s mother’s age by specifying mother.age as the key path.) The
options argument specifies the information you would like to have handed to the
observer when it is informed about the changes. For example, it can be told
about the old value (before the change) and the new value (after the change).
The context variable is used to indicate what level of the class hierarchy is
observing the notification.
When a change occurs, the following method is called on the observer:
func observeValueForKeyPath(keyPath: String,
ofObject object: AnyObject,
change: [NSObject :
change: [NSObject :
AnyObject],
context:
UnsafeMutablePointer<Void>)
The observer is told which key path changed in which object. Here, change is a
dictionary that (depending on the options you asked for when you registered as
an observer) may contain the old value and/or the new value. The method is
called with the context pointer supplied when the observer started observing.
Using the Context Pointer Defensively
In order for any class to observe key-value changes, it must implement the
observeValueForKeyPath(_:ofObject:change:context:) method; it is not
possible to provide a different method to KVO. Consequently, it is possible to
mistakenly intercept notifications intended for a superclass.
Consider the case of some class Maple, which is a subclass of some other class
Tree. Both classes observe Environment’s key path season independently. Unless
the developer takes precautions, Maple will receive its key-value-observing
messages and those intended for Tree, because Maple has effectively overridden
Tree’s KVO method. To fix this, Maple must correctly distinguish between KVO
notifications intended for it from those intended for its superclass. If the
notification is not intended for Maple, it should pass the message on to its
superclass.
The way to pull this off is to use a class-specific pointer value as the context
argument. You will use this approach in the KVO code that you write to make
Document observe Employee’s name and raise properties. Although it is not
necessary within the conditions of this specific exercise, since Employee is not a
part of a complex class hierarchy with other classes that also do observing, this
should be your default approach.
Undo for Edits
It is time now for you to add undo support for editing of employees’s fields to
RaiseMan. The first step is to register Document to observe changes to the Employee
objects in its employees array. Add the following private variable near the top of
Document.swift:
import Cocoa
forKeyPath: keyPath)
}
Note that this method puts a call to setValue(_:forKeyPath:) on the undo stack
with the old value for the changed key.
What would happen if the Document is being deallocated (when the window is
closed, for example) and there are Employees in this array? Since the Document
owns the Employees, they will be deallocated. However, the Document will still be
observing these Employees when they are deallocated. Key-value observing will
raise an exception if this happens. It is your job to remove all observers of an
Employee (or any other object) before the Document gets deallocated.
In order to fix this, you need to make the Document stop observing all of its
Employees before the window closes. To do this, you will make the Document be
the delegate of the window and implement windowWillClose(_:). This will give
you a chance to remove the Document as an observer from each of the Employees.
First, make Document adopt the NSWindowDelegate protocol: class Document:
NSDocument, NSWindowDelegate {
Thanks to Xcode’s template for document-based apps, Document is already the
delegate of the Window in Document.xib. Verify that this is still the case by
opening Document.xib and Control-clicking Window in the document outline to
present the window’s connections panel. You should see that the delegate outlet
is set to File's Owner:
Figure 11.5 The window’s delegate outlet set to File's Owner
You are now ready to implement windowWillClose(_:) on Document. Open
Document.swift and, near the bottom of the class’s definition, add this
implementation for the delegate method:
// MARK: - NSWindowDelegate
}
You will write the implementation for this method in just a moment. For now,
save that file and open Document.xib.
Control-drag from the Add Employee button to the File's Owner (which is the instance
of Document). Set its action to addEmployee:.
Control-click on the File's Owner in the Icon View to present its connections panel.
Drag from it to connect the tableView outlet to the table view and the
arrayController outlet to the array controller (Figure 11.6).
let endedEditing =
window.makeFirstResponder(window)
if !endedEditing {
println("Unable to end editing.")
return
}
When you need to recreate the graph of objects from the stream of bytes, you
will unarchive it. When your application starts up, it unarchives the objects from
the NIB file.
Although objects have both properties and methods, only the properties and the
name of the class go into the archive. In other words, only data, not code, goes
into the archive. As a result, if one application archives an object and another
application unarchives the same object, both applications must have the code for
the class linked in. In the NIB file, for example, you have used classes like
NSWindow and NSButton from the AppKit framework. If you do not link your
application against the AppKit framework, it will be unable to create the
instances of NSWindow and NSButton that it finds in the NIB file.
There was once a shampoo ad that said, “I told two friends, and they told two
friends, and they told two friends, and so on, and so on, and so on.” The
implication was that as long as you told your friends about the shampoo,
everyone who matters would eventually wind up using the shampoo. Object
everyone who matters would eventually wind up using the shampoo. Object
archiving works in much the same way. You archive a root object, it archives the
objects which it has references to, they archive the objects that they have
references to, and so on, and so on, and so on. Eventually, every object that
matters will be in the archive.
Archiving involves two steps: teaching your objects how to archive themselves
and kicking off the archiving.
NSCoder and NSCoding
Archiving relies on the NSCoding protocol. If your class conforms to NSCoding, it
promises to implement the following methods:
init(coder aDecoder: NSCoder)
func encodeWithCoder(aCoder: NSCoder)
An NSCoder is an abstraction of a stream of bytes. You can write your data to a
coder or read your data from a coder. The init(coder:) method in your object
will read data from the coder and save that data to its properties. The
encodeWithCoder(_:) method in your object will read its properties and write
those values to the coder. In this chapter, you will implement both methods in
your Employee class.
NSCoder is like an abstract class. You will not ever create instances of an abstract
class. Instead, an abstract class has some capabilities that are intended to be
implemented by subclasses. You will create instances of the concrete subclasses.
Namely, you will use NSKeyedUnarchiver to read objects from a stream of data
and you will use NSKeyedArchiver to write objects to the stream of data.
Encoding
NSCoder has many methods, but most programmers find themselves using just a
few of them repeatedly. Here are the methods most commonly used when you
are encoding data onto the coder: func encodeObject(anObject: AnyObject?,
forKey aKey: String)
This method writes anObject to the coder and associates it with the key aKey.
This will cause anObject’s encodeWithCoder(_:) method to be called (and they
told two friends, and they told two friends…).
For each of the common value types (such as Bool and Float), NSCoder has an
encode method:
func encodeBool(boolv: Bool, forKey key: String)
func encodeDouble(realv: Double, forKey key: String)
func encodeFloat(realv: Float, forKey key: String)
func encodeInt(intv: Int32, forKey key: String)
func encodeInt(intv: Int32, forKey key: String)
In RaiseMan, you are going to implement file saving by teaching instances of
Employee how to turn themselves into data. You are going to make Employee
conform to the NSCoding protocol.
Open your RaiseMan project.
In Employee.swift, declare that Employee conforms to the NSCoding protocol:
import Foundation
Decoding
When decoding data from the coder, you will use the analogous decoding
methods:
func decodeObjectForKey(key: String) -> AnyObject?
func decodeBoolForKey(key: String) -> Bool
func decodeDoubleForKey(key: String) -> Double
func decodeFloatForKey(key: String) -> Float
func decodeIntForKey(key: String) -> Int32
If the object did not write out data for a key raise when the stream was first
written during encodeWithCoder(_:), the coder will return 0.0 from
decodeFloatForKey("raise"). Similarly, if the object did not call
encodeObject(name, forKey: "name") when encodeWithCoder(_:) was
executed, the coder will return nil if the coder is asked to decode an object for
the key name.
To add decoding to your Employee class, add the following method near the top
of Employee.swift, next to your implementation of encodeWithCoder(_:):
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as!
String?
raise = aDecoder.decodeFloatForKey("raise")
super.init()
}
Once again, you did not call the superclass’s implementation of init(coder:),
because NSObject does not have one. If Employee’s superclass had implemented
the NSCoding protocol, the method would have looked like this:
required init(coder aDecoder: NSCoder) {
name = aDecoder.decodeObjectForKey("name") as
String?
String?
raise = aDecoder.decodeFloatForKey("raise")
super.init(coder: aDecoder)
}
You will also need to add an implementation of init:
override init() {
super.init()
}
At this point, you may be saying, “But why do I need to implement init?
Wasn’t it being called before?” You are right. init was being called before
init(coder:) was added. However, now that init(coder:) has been added, init
is no longer available.
In Swift, initializers are not always inherited. They are only inherited in special
circumstances: a class’s designated initializers are only inherited by a subclass if
that subclass does not define any designated initializers of its own.
In this case, before you added init(coder:), Employee did not define any
designated initializers, so it inherited init from NSObject. Now that you have
added init(coder:), though, Employee defines a designated initializer, so in
order for init to be available, it must be defined in Employee.swift.
You have now implemented the methods required by the NSCoding protocol.
Now compile the project. Fix any errors. You could run the application at this
point, if you like. However, although you have taught Employee objects to
encode themselves, you have not asked them to do so. Thus, you will see no
change in the behavior of your application.
The Document Architecture
Applications that deal with multiple documents have a lot in common. Every
such application can create new documents, open existing documents, save or
print open documents, and remind the user to save edited documents when they
try to close a window or quit the application. AppKit supplies three classes that
take care of most of the details for you: NSDocumentController, NSDocument, and
NSWindowController. Together, these three classes constitute the document
architecture.
The purpose of the document architecture relates to the Model-View-Controller
design pattern discussed in Chapter 1. In RaiseMan, your subclass of NSDocument
acts as the window controller. It will have a reference to the model objects, and
will, directly or via the array controller, carry out the following duties:
saving the model data to a file
loading the model data from a file
giving the views the data to display
taking user input from the views and updating the model
NSDocument
The document objects are instances of a subclass of NSDocument. In your RaiseMan
application, for example, the document objects are instances of Document. For
many applications, you can simply subclass NSDocument and not interact with the
rest of the document architecture directly. As you will learn later in the chapter,
though, sometimes you will need to use one or more NSWindowController
subclasses as well.
Saving documents
The menu items Save, Save As..., Save All, and Close are all different, but all deal with
the same problem: getting the model into a file or file wrapper. (A file wrapper is
a directory that looks like a file to the user.) To handle these menu items, your
NSDocument subclass must implement one of three methods:
func dataOfType(typeName: String,
error outError: NSErrorPointer) ->
NSData?
func fileWrapperOfType(typeName: String!,
error outError: NSErrorPointer)
-> NSFileWrapper?
func writeToURL(url: NSURL,
func writeToURL(url: NSURL,
ofType typeName: String,
error outError: NSErrorPointer) ->
Bool
Understanding NSErrorPointer
NSErrorPointer can be a bit confusing. The idea is that if the method – whether
dataOfType(_:error:), fileWrapperOfType(_:error:), or
writeToURL(_:ofType:error:) – is unable to do its job, it will create an NSError
and store it in the NSErrorPointer’s memory field. For example, to read an NSData
from a file, you would supply an address where the error would be placed:
let url = NSURL(fileURLWithPath: "pathto/file.ext")
Loading documents
The Open..., Open Recent, and Revert To Saved menu items, although different, all deal
with the same basic problem: getting the model from a file or file wrapper. To
handle these menu items, your NSDocument subclass must implement one of three
methods:
func readFromData(data: NSData,
func readFromData(data: NSData,
ofType typeName: String,
error outError: NSErrorPointer) ->
Bool
func readFromFileWrapper(fileWrapper: NSFileWrapper,
ofType typeName: String,
error outError:
NSErrorPointer) -> Bool
func readFromURL(url: NSURL,
ofType typeName: String,
error outError: NSErrorPointer) ->
Bool
After implementing one save method and one load method, your NSDocument
subclass will know how to read from and write to files. When opening a file,
your NSDocument subclass will read the document file before loading a window
from the NIB file. As a consequence, you will not be able to call methods on the
user interface objects immediately after loading the file (because they will not
exist yet). To solve this problem, after the NIB file is read, the following method
is called on your NSDocument subclass:
func windowControllerDidLoadNib(windowController:
NSWindowController!)
In your NSDocument subclass, you will implement this method to update the user
interface objects.
If the user chooses Revert To Saved from the menu, the model is loaded, but
windowControllerDidLoadNib(_:) is not called. You will, therefore, also have to
update the user interface objects in the method that loads the data, just in case it
was a revert operation. One common way to deal with this possibility is to check
one of the outlets set in the NIB file. If it is nil, the NIB file has not been
loaded, and there is no need to update the user interface.
NSWindowController
The final class in the document architecture to discuss is NSWindowController,
though you will not initially need to worry about it. A document will have an
instance of NSWindowController for each window that it is displaying; these
instances are stored in NSDocument’s windowControllers property.
The default behavior, from Xcode’s template, is for the document to create an
instance of NSWindowController to partially control the window that is loaded
from the document’s XIB. As most applications have only one window per
document, the default behavior of the window controller is usually perfect.
Figure 12.3 shows how NSWindowController fits into the document architecture
in the default setup.
Figure 12.3 A document using an NSWindowController
Though the default setup is often exactly what you want, there are times when
you might want to create a custom subclass of NSWindowController:
You need to have more than one window on the same document. For
example, in a CAD program you might have a window of text that
describes the object being drawn and another window that shows a
rendering of it. The document architecture in such an app would look like
Figure 12.5.
Figure 12.4 A document using multiple window controllers
To do the unarchiving, you will use NSKeyedUnarchiver, which has the following
handy method:
class func unarchiveObjectWithData(data: NSData) ->
AnyObject?
In Document.swift, edit your readFromData(_:ofType:error:) method to look
like this:
override func readFromData(data: NSData,
ofType typeName: String,
error outError:
NSErrorPointer) -> Bool {
println("About to read data of type \
(typeName).");
employees =
NSKeyedUnarchiver.unarchiveObjectWithData(data) as!
[Employee]
return true
return false
}
You could update the user interface after the XIB file is loaded, but
NSArrayController will handle it for you: the windowControllerDidLoadNib(_:)
method does not need to do anything. Leave it here for now because you will
add to it in Chapter 14:
override func windowControllerDidLoadNib(aController:
NSWindowController) {
super.windowControllerDidLoadNib(aController)
}
Note that your document is asked which XIB file to load when a document is
opened or created. This method also needs no changing:
override var windowNibName: String? {
return "Document"
return "Document"
}
The window is automatically marked as edited when you make an edit, because
you have properly enabled the undo mechanism. When you register your
changes with the undo manager for this document, it will automatically mark the
document as edited.
At this point, your application can read and write to files. Compile your
application and try it out. Everything should work correctly.
When you browse to the saved files, Finder will display them with a generic icon
and list their Kind as Document Type. Let’s look at how to define our
application’s document type more fully.
Setting the Extension and Icon for the File
Type
RaiseMan files already have the extension .rsmn, which we configured when we
created the project. But .rsmn files need an icon.
You will need to find an .icns file and copy it into your project. A fine icon is
found at ApplicationsTextEdit.app/Contents/Resources/txt.icns. To navigate
to this directory in Finder, select Go to Folder... from the Go menu, and enter the path
ApplicationsTextEdit.app/Contents/Resources/.
Notice the path that you just accessed in Finder. The second component of the
path is TextEdit.app, the TextEdit application! How can that be? TextEdit.app is a
package, or more specifically a bundle. In OS X, packages are folders that
appear in Finder to be a single file. Bundles are packages with a specific structure
for the location of the binary code and application resources.
Take a look at TextEdit’s bundle. In Finder, go to the Applications directory. You
can do this by selecting Go to Folder... from the Go menu.
In the applications directory, locate TextEdit, and control-click on it. Select Show
Package Contents from the menu that is presented (Figure 12.7).
Xcode will bring up a sheet. Make sure that you check Copy items if needed
(Figure 12.9). This will copy the icon file into your project directory.
Figure 12.9 Make it a copy
To set the document type information, select the RaiseMan project in the project
navigator. With the RaiseMan target selected, under the Info tab, find the Document
Types heading and expand the existing document type. Set the name to be
RaiseMan Doc, the Icon to txt, and the identifier to com.bignerdranch.raiseman-
doc.
Next, configure an exported universal type identifier (UTI) for RaiseMan
documents. A UTI describes the type of data contained in the file; we will cover
UTIs in more detail later in this chapter.
Exported UTIs are found immediately below the Document Types heading in the
target info. If there is not already a blank exported UTI, add one by using the
Add button. Set the Description, Identifier, Icon, and Extensions to be identical to the
settings you made in the DocumentType. For Conforms To, type public.data
(Figure 12.10).
Figure 12.10 Configuring the document type and exported UTI
The changes that you just made are saved in the app’s Info.plist. OS X will
read this file to determine the document types and exported UTIs associated with
your app.
Run your app. You should be able to save data to a file and read it in again. In
Finder, the txt.icns icon will be used as the icon for your .rsmn files.
Application Data and URLs
This chapter has focused on persisting data associated with the user’s
documents, managed by Cocoa’s document architecture, but many applications
will need to store data that is managed directly by the application. Consider a
shoebox app (an app that stores collections of images, documents, notes, and so
forth). Where should it store its data? What about cached data?
Data that is not related to a document has a specific home on OS X: the library.
The library, or more appropriately ~/Library, is a (usually hidden) folder in the
user’s home folder, as the path suggests. The library contains folders for specific
purposes: Caches for cached data that can be deleted, Application Support for
arbitrary data that needs to be saved. The latter is where a shoebox application
would store its data.
You do not need to build these URLs yourself. In fact, you should not. Instead,
use NSFileManager’s URLsForDirectory(_:inDomains:).
let fileManager = NSFileManager()
let urls =
fileManager.URLsForDirectory(.ApplicationSupportDirectory,
inDomains:
.UserDomainMask) as! [NSURL]
if let url = urls.first {
let identifier =
NSBundle.mainBundle().bundleIdentifier!
let appSupport =
url.URLByAppendingPathComponent(identifier)
println(appSupport)
}
// Outputs:
file:///Users/nerd/Library/Application%20Support/com.bignerdran
This API can return URLs for numerous common folders. The first parameter of
URLsForDirectory(_:inDomains:) determines what the purpose of the directory
is. Its type is NSSearchPathDirectory; a few of the most commonly used values
are:
ApplicationSupportDirectory
CachesDirectory
DocumentDirectory
The second parameter, the domain, is of type NSSearchPathDomainMask. The user
domain (UserDomainMask) is by far the most commonly used, as it relates to
directories within the user’s home directory.
Note that the bundle identifier is used to create a unique folder within any of
these shared folders, such as Application Support and Caches, so that your
application’s data is not confused with another’s. (This is not the case with app
sandbox containers, as discussed in Chapter 36.)
Once you have gotten the URL for your application’s data, you will need to
create the directory, at least the first time. For that, use NSFileManager’s
createDirectoryAtURL(_:withIntermediateDirectories:attributes:error:).
For the More Curious: Preventing Infinite
Loops
The astute reader may be wondering, “If object A causes object B to be encoded,
and object B causes object C to be encoded, and then object C causes object A to
be encoded again, couldn’t it just go around and around in an infinite loop?” It
would, but NSKeyedArchiver was designed with this possibility in mind.
When an object is encoded, a unique token is also put onto the stream. Once
archived, the object is added to the table of encoded objects under that token.
When told to encode the same object again NSKeyedArchiver simply puts a token
in the stream.
When it decodes an object from the stream NSKeyedUnarchiver puts both the
object and its token in a table. If it finds a token with no associated data, the
unarchiver knows to look up the object in the table instead of creating a new
instance.
This idea led to the method in NSCoder that often confuses developers when they
read the documentation: func encodeConditionalObject(objv: AnyObject?,
forKey key: String)
This method is used when object A has a reference to object B, but object A
does not really care if B is archived. However, if another object has archived B,
A would like the token for B put into the stream. If no other object has archived
B, it will be treated like nil.
For example, if you were writing an encodeWithCoder(_:) method for an Engine
object (Figure 12.11), it might have a property called car that is a pointer to the
Car object that it is part of. If you are archiving only the Engine, you would not
want the entire Car archived. But if you were archiving the entire Car, you would
want the car pointer set. In this case, you would make the Engine object encode
the car pointer conditionally.
Figure 12.11 Conditional encoding example
For the More Curious: Creating a Protocol
Creating your own protocol is very simple. Here is a protocol with two methods.
protocol Encryptable {
init(encryptedData: NSData)
func encrypt() -> NSData
}
The protocol would typically be defined in a file called Encryptable.swift:
If your protocol is tagged @objc, it is possible to add optional methods.
@objc protocol Encryptable {
init(encryptedData: NSData)
func encrypt() -> NSData
optional func schemeName() -> String
}
In this example, encryptData(_:) and decryptData(_:) are required; schemeName
is optional.
If you wanted to make a class conform to both the Encryptable protocol and the
NSCoding protocol, the class would begin like this:
class SerializableObject: NSCoding, Encryptable {
For the More Curious: Automatic Document
Saving
In Mac OS X Lion (10.7), Apple introduced automatic document-saving support
to Cocoa. With automatic document saving, your users will no longer need to be
concerned with manually saving their documents. By monitoring the change
count (described later), Cocoa will cue your document to save itself at
appropriate times. When the user does manually save the document, a new
version will be created. The user can then browse past versions by using an
interface similar to Time Machine. Untitled documents (documents not explicitly
saved by the user) will even be preserved between runs of your application.
In order to support automatic document saving, your NSDocument subclass must
opt in by overriding the class method autosavesInPlace to return true:
override class func autosavesInPlace() -> Bool {
return true
}
If your document saves its data quickly, opting in is probably an easy choice.
Otherwise, autosaving in place may not be appropriate for your application, as it
will cause the interface to block until the save is completed. Refer to Apple’s
Mac App Programming Guide for a more in-depth discussion.
The Xcode document-based app template includes an NSDocument subclass which
enables this feature. NSDocument itself, however, disables this feature; its
implementation of this method returns false.
For the More Curious: Document-Based
Applications Without Undo
The NSUndoManager for your application knows when unsaved edits have
occurred. Also, the window is automatically marked as edited. But what if you
have written an application and are not registering your changes with the undo
manager?
NSDocument keeps track of how many changes have been made. It has a method
for this purpose: func updateChangeCount(change: NSDocumentChangeType)
The NSDocumentChangeType can be one of the following: ChangeDone,
ChangeUndone, or ChangeCleared. ChangeDone increments the change count,
ChangeUndone decrements the change count, and ChangeCleared sets the change
count to 0. The window is marked as dirty unless the change count is 0.
For the More Curious: Universal Type
Identifiers
One of the enduring problems in working with computers is embodied in the
question: What does this data represent? On the Mac, this question gets asked in
several places: when a file is opened from Finder, when data is copied off the
pasteboard, when a file is indexed by Spotlight, and when a file is viewed through
Quicklook. Thus far, there have been several answers: file extensions, creator
codes, MIME types.
Apple has decided that the long-term solution to the problem is universal type
identifiers (UTIs). A UTI is a string that identifies the type of data. This data
may be in a file or in a memory buffer. UTIs are organized hierarchically. For
example, the UTI public.image conforms to public.data.
Your application tells Mac OS X what UTIs your application can read and write,
including new, custom UTIs, by setting values in its Info.plist. The Info.plist
is an XML file that has a dictionary of key-value pairs. Exported UTIs are stored
under the key UTExportedTypeDeclarations. The steps you followed earlier to
add an exported UTI created this key. The pasteboard, which we will cover in
Chapter 21, also uses UTIs to identify data types.
There is a large set of system-defined UTIs. You can find the entire list in
Apple’s Uniform Type Identifiers Reference document.
13
Basic Core Data
At this point, you have implemented an application that keeps track of an array
of objects, takes care of undo, and handles saving and loading from a file. As
you can imagine, there are an awful lot of applications like the one you just
wrote.
Apple has made this type of application very easy to write. You have already
seen how bindings and NSArrayController allow you to skip writing a lot of
tedious, uninteresting code. With Core Data, you can even skip writing the data
objects, saving and loading, and even undo.
That is right: using Core Data and bindings, the RaiseMan application you have
written can be created with no code at all. In this chapter, you are going to create
a simple Core Data application (not unlike RaiseMan) that has no code.
Defining the Object Model
Core Data is all about managing data objects. However, before it can manage
anything it needs to know what it is managing. One of the first steps with a Core
Data application is defining the managed object model.
The managed object model is composed of entities. Entities are similar to classes
in that they define data-bearing objects. An entity has attributes and
relationships. Both of these are similar to properties on regular objects, except
that they have different types: Attributes are value-type data such as strings and
integers, while relationships are references to other entities.
You will use a graphical editor in Xcode to define the managed object model,
which is stored in a .xcdatamodeld file. This file is processed when your project
is built and included in the app bundle. At runtime, Core Data loads the object
model to create an instance of the NSManagedObjectModel class.
For example, suppose you were writing an app to track employees and
departments within a company. The Employee entity would have attributes such
as name and dateOfBirth. The Department entity would have similar attributes:
name and dateOfEstablishment, perhaps. The Employee entity would also have a
department relationship pointing to the Department entity. This relationship
would reference the department a given employee was a member of. A one-to-
many relationship, employees, on the Department, would form the inverse
relationship.
The RaiseMan application used a subclass of NSDocument. In this application, you
will have a subclass of NSPersistentDocument. NSPersistentDocument, a subclass
of NSDocument, automatically reads in the model and creates an
NSManagedObjectContext. NSPersistentDocument will eliminate the need for
many lines of code.
There are two more important classes in Core Data to mention:
NSManagedObjectContext is the class that manages all of the entities, so this is the
class that you will use to create, fetch, and delete them. NSPersistentDocument
provides the context via its managedObjectContext property. The other class is
NSManagedObject, which is the base class for model objects managed by Core
Data. For example, if you fetched all of the Employee entities from the managed
object context, the return value would be an array of NSManagedObject instances.
You can also subclass NSManagedObject.
In this chapter, you will create an app that might be used by folks who own
several used-car lots. This application will enable your users to keep track of the
cars that they wish to sell. When the application is done, it will look like
Figure 13.1.
Figure 13.1 Completed application
Start Xcode and create a new Cocoa Application project. Name the project CarLot.
This will be a document-based application, so check Create Document-Based
Application. Do not worry about the Document Extension, it will be ignored by the
project template. You will want Xcode to set your app up for Core Data, so check
Use Core Data.
In the new project, open Document.xcdatamodeld. Click the Add Entity button at the
bottom left of the editor to create a new entity. Name the entity Car.
While the Car entity is selected, you can add an attribute to it by clicking the +
button at the bottom of the editor’s Attributes section. Add six attributes and give
them the following names and types:
Name Type
condition Integer 16
datePurchased Date
makeModel String
onSpecial Boolean
photo Transformable
price Decimal
Figure 13.2 shows what Car looks like in the modeler. You could put a lot of
other things in the model, but that is enough for this exercise.
Figure 13.2 Completed model
Configure the Array Controller
Open Document.xib. Delete the text field that says Your document contents here. Drag
an array controller onto Interface Builder’s dock. The array controller will be using
the document object’s NSManagedObjectContext to fetch and store data. Use the
bindings inspector to bind the array controller’s managedObjectContext to File's Owner’s
managedObjectContext (Figure 13.3).
With the array controller still selected, switch to the attributes inspector. Under
the Object Controller heading, set Mode to Entity Name. Then set the Entity Name to be Car.
Finally, turn on the Prepares Content option, so that the array controller will fetch
immediately after it is created (Figure 13.4).
Figure 13.4 Configuring the attributes of the cars array controller
Each object in Interface Builder can have a label which identifies it within
Interface Builder. With the NSArrayController still selected, change to the identity
inspector and set its Label to Cars. When you have several array controllers in a
XIB, the labels help to eliminate a lot of confusion.
Add the Views
Drag out a table view from the object library. Select the table view, switch to the
attributes inspector, and change the number of Columns to 3. Name the columns
Make/Model, Price, and Special.
The first column will display a picture of the car as well as its make and model.
However, currently the Table Cell View only contains a text field. In order to display
an image, you need a Table Cell View which contains both an image view and a text
field. The easiest way to do this is to delete the current Table Cell View and replace
it with one that contains both of these views.
Use the document outline to select the column’s Table Cell View, the first child of
the Make/Model table column (Figure 13.5). Once you have selected it, delete it.
Figure 13.5 Selecting the Make/Model column’s table cell view
Now drag an Image & Text Table Cell View from the object library onto the column
(Figure 13.6).
Figure 13.6 Dropping an Image & Text Table Cell View
Every column should now contain a Table Cell View (NSTableCellView), and within
it a text field. The first column’s cell view contains an image view as well.
The second column will display the price of the car. But currently, when the text
field in that column’s Table Cell View displays a number, it will not format the
number correctly as a dollar amount. Fix that by adding a formatter to the text
field.
Drop a number formatter from the object library onto the NSTextField in the
second column. In the attributes inspector, set the Style to Currency (Figure 13.7).
Figure 13.7 Configuring the price number formatter
The third column will indicate whether a car is currently on special. Your users
should be able to easily toggle this setting. A natural choice, then, is to use a
checkbox in this column. But at the moment, this column’s Table Cell View contains
a text field rather than a checkbox. Fix that by replacing the text field with a
checkbox.
Use the document outline to select the text field inside this column’s Table Cell
View. The text field will be labeled Table View Cell in the document outline
(Figure 13.8). Once you have selected the text field, delete it.
Figure 13.8 Selecting the text field within the Special column’s table cell
view
Now drop a Check Box (not a Check Box Cell!) from the object library onto the Table
Cell View in the Special column. Use the snap guides to vertically center the Check
Box as you drag it in (Figure 13.9). After you have dropped it in, select the Check
Box and erase its title.
Figure 13.9 Dropping a Check Box into the table cell view
Before continuing, use the attributes inspector to set the Behavior of the two text
fields (the one in the Make/Model column and the one in the Price column) to Editable.
At this point, you have set up a table where your users’ cars will be displayed.
However, you still have not given them a way to add or remove cars from the
table. Add two buttons right below the table view to enable this (Figure 13.10).
Figure 13.10 Adding buttons to add and remove cars
With the interface as it is, the user will be able to modify some of the fields of
Car: makeModel in the first column, price in the second column, and onSpecial in
the third column. However, there are other fields which the current interface
does not provide a way of modifying: datePurchased, condition, and photo
(which can be viewed, but not modified). To support editing these fields, add a
few more controls beneath the buttons you already added.
From the object library, drag the following onto the window, beneath the Add and
Remove buttons: an NSDatePicker, an NSImageView (the object library refers to
these as Image Wells), an NSLevelIndicator, and a couple of Labels. Change the
labels to display Date Purchased and Condition. Arrange all of these views on the
window something like Figure 13.11, but do not be too particular about the
arrangement quite yet – you will be moving them around before you are done.
Figure 13.11 Arranging the car detail views
Now you need to configure these controls. First, select the NSImageView and
make it Editable using the attributes inspector. Second, select the
NSLevelIndicator. At the top of the Level Indicator section of the attributes
inspector, change the Style to Rating and toggle the State to be Editable. Then, in the
Value subsection, set the Minimum to 0 and the Maximum to 5 (Figure 13.12).
Start by binding the table view’s Content binding to Cars’s arrangedObjects. This
binding makes the array controller’s arranged objects be displayed in the table
view. Leave the Model Key Path empty. Remember, Cars is the NSArrayController.
Next, bind the Selection Indexes to Cars’s selectionIndexes. This binding makes the
user’s selection in the table view be reflected in the array controller, allowing
removal to work correctly. See Figure 13.15.
Figure 13.15 Table view bindings
Now bind the Value of the control(s) in each column’s Table Cell View as described
in the following table.
Controller
Column View Binding Bind to Key Path
Key
Image Table Cell
First View
Value
View
Empty objectValue.photo
Table Cell
First Text Field Value
View
Empty objectValue.makeModel
Table Cell
Second Text Field Value
View
Empty objectValue.price
Table Cell
Third Check Box Value
View
Empty objectValue.onSpecial
Note the pattern in the bindings. Each control’s Value is bound to a particular
property of its Table Cell View’s objectValue. Recall that the table’s Content is bound
to the NSArrayController’s arrangedObjects. Each of those objects is an
instance of your Car entity. When the table view’s contents are bound, the
objectValue property of the table cell view (NSTableCellView) is populated with
the entity instance for that row. Once you understand this, you can configure
very straightforward bindings, such as those in the table.
Now set the two buttons’ targets and actions. First, make the Add button trigger
the add: action of the array controller. Next, make the Remove button trigger the
remove: action of the array controller. Connect the Remove button’s Enabled binding
to the Cars array controller: set the Controller Key to canRemove and leave the Model
Key Path blank.
At this point it is time to set up the bindings for the detail controls at the bottom
of the screen. Bind these controls to the selection of the array controller as
follows:
View Binding Bind to Controller Key Key Path
Textual Date Picker With Stepper Value Cars selection datePurchased
Rating Level Indicator Value Cars selection condition
Image Well Value Cars selection photo
When configuring the binding of the Image Well’s Value, also check the box that
says Conditionally Sets Editable. See Figure 13.16.
Figure 13.16 Image view binding
Next, you will set up the binding for the title of the NSBox. Select the box. In the
bindings inspector, under the Title With Pattern heading, bind Display Pattern Title1 to Cars
(your custom name for the array controller). Set the Controller Key to selection and
the Model Key Path to makeModel. Set the Display Pattern to Details for %{title1}@.
Set the No Selection Placeholder to <no selection>. Set the Null Placeholder to <no Make/Model>.
See Figure 13.17.
Figure 13.17 Box binding
Let’s also make the text of the first two columns appear in bold if the car is on
special. Configure the Font Bold binding of the text fields in each of the first two
columns as follows: Bind the Value to the Table Cell View. Leave the Controller Key
blank. Set the Model Key Path to objectValue.onSpecial (Figure 13.18).
Figure 13.18 Specials appear in bold
You are done. Run the application. Saving and loading should work. Undo
should work. Magic, eh?
How Core Data Works
Although you have written no code, many objects have been created to make this
work. Figure 13.19 is a diagram of some of them.
Figure 13.19 Objects in CarLot created by Core Data
The NSPersistentDocument reads in the model you created and uses it to create
an instance of NSManagedObjectModel. In this case, the managed object model has
one NSEntityDescription (which describes the Car entity). That entity
description has several instances of NSAttributeDescription.
Once it has the model, the persistent document creates an instance of
NSPersistentStoreCoordinator and an instance of NSManagedObjectContext. The
NSManagedObjectContext fetches instances of NSManagedObject from the object
store. While those managed objects are in memory, the managed object context
observes them. Whenever the data inside the managed objects is changed, the
managed object context registers the undo action with the document’s
NSUndoManager. The managed object context also knows which objects have been
changed and need to be saved.
Among the classes in the Core Data framework, you will find yourself
interacting with NSManagedObjectContext the most. You will use it to fetch
objects and to save changes to your object graph.
Fetching Objects from the
NSManagedObjectContext
While this chapter’s exercise demonstrates the power of bindings combined with
Core Data, it is not uncommon to use Core Data programmatically.
In order to fetch objects from the NSManagedObjectContext you will first need to
create an NSFetchRequest. A fetch has a number of similarities to
NSArrayController, except that it is typically only used once, while an array
controller is continuously updated.
At a minimum you will need the name of the entity you want to fetch. Without
anything more, the fetch request will return all of the objects for that entity
name. You will usually want to filter and order the results, however. For that you
will provide an NSPredicate and/or an array of NSSortDescriptor objects, just
like with NSArrayController. The following code demonstrates fetching Car
entities from the managed object context:
// Build the fetch request:
let fetchRequest = NSFetchRequest(entityName: "Car")
fetchRequest.predicate = NSPredicate(format: "price >=
1000")
fetchRequest.sortDescriptors = [NSSortDescriptor(key:
"makeModel", ascending: true)]
// Execute the fetch request:
var error: NSError?
let fetchResult: [AnyObject]? =
managedObjectContext.executeFetchRequest(fetchRequest,
error: &error)
Think back to how you configured the array controller in the CarLot exercise: you
bound it to the document’s managedObjectContext, set its Mode to Entity Name, and
supplied the name. Familiar, no?
The return type of executeFetchRequest(_:error:) is [AnyObject]?. To safely
access the results of the fetch you must use optional binding. The as? cast to
[NSManagedObject] is not necessary, but serves to document your expectations:
if let cars = fetchResult as? [NSManagedObject] {
for car in cars {
if let makeModel =
car.valueForKey("makeModel") as? String {
println("Fetched car: \(makeModel)")
}
}
}
If you configure a custom subclass for this entity (in the Data Model inspector while
editing the .xcdatamodeld file), the returned object instances would be of that
class. Instead, you can use KVC to access the attributes and relationships of the
managed object. You can also use KVC to change these values.
Persistent Store Types
Core Data supports several store types: three for saving and loading data
(SQLite, XML, and Binary), and a fourth for caching applications, In-Memory.
The Xcode Core Data application template allows the user to choose between the
persistence formats in the File Format pop-up button on the save panel.
Which should you use? Each of these types has their strengths, but we
recommend SQLite for most applications: it is fast, thanks to its support for
incremental updates, and moderately readable – you can open the file in the
sqlite3 command-line utility should you need to peek inside the store. The
XML format can be inspected too, but it is significantly slower than SQLite.
Note that Core Data is not a database adapter: you will not be able to use your
own SQLite databases with Core Data, and Apple reserves the right to change
the schema at any time in the future. Here is the schema of a Core Data SQLite
store:
$ sqlite3 ~/Desktop/CarLot.sqlite
SQLite version 3.8.5 2014-08-15 22:37:57
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE ZCAR ( Z_PK INTEGER PRIMARY KEY, Z_ENT
INTEGER, Z_OPT INTEGER,
ZCONDITION INTEGER, ZONSPECIAL INTEGER,
ZDATEPURCHASED TIMESTAMP, ZPRICE DECIMAL,
ZMAKEMODEL VARCHAR, ZPHOTO BLOB );
CREATE TABLE Z_PRIMARYKEY (Z_ENT INTEGER PRIMARY KEY,
Z_NAME VARCHAR,
Z_SUPER INTEGER, Z_MAX
INTEGER);
CREATE TABLE Z_METADATA (Z_VERSION INTEGER PRIMARY
KEY, Z_UUID VARCHAR(255),
Z_PLIST BLOB);
This is a database meant for consumption by computers, not developers.
If you have special requirements you can even implement your own persistent
store. For more on persistent stores, see the Core Data Programming Guide.
Choosing a Cocoa Persistence Technology
Core Data is perhaps the most polarizing of the Cocoa frameworks. It has a long
history and there is a lot we simply do not have time to cover here – entire books
have been written on Core Data! Core Data is very sophisticated about handling
data, using a mechanism called faulting to only load fetched objects when
needed. It handles a great deal of non-trivial work for you: tracking changes,
helping to manage schema versions, and so forth. It would take you a long, long
time to duplicate what it offers.
At the same time, even as the Cocoa community has gathered more
understanding of Core Data over the years, it is still a black box. If you
encounter a performance problem it can be very difficult to determine the cause.
Furthermore, when you are using Core Data it is critical to keep in mind that
NSManagedObject instances are closely tied to the NSManagedObjectContext that
they were fetched from. They are not simply model objects; they are Core Data
model objects, which requires careful consideration of how their references are
passed around in your application.
Although archiving is trivial by comparison to Core Data, they both accomplish
a similar goal for many developers: saving and loading objects. But archiving
has a significant downside: the entire object graph must be saved and loaded at
once. This is where Core Data tends to shine, in dealing with medium-sized data
sets.
Unsatisfying though it may be, the best answer to the question of whether you
should use Core Data is “maybe.” For applications with small data sets with very
simple feature sets, archiving will be fine. If your application resembles RaiseMan
or CarLot, you should seriously consider Core Data.
Applications working with very large data sets would likely benefit from a
custom persistence implementation. Core Data’s reliance on KVC carries with it
a significant overhead. We expect that pure Swift objects would offer the best
performance in terms of low overhead property access.
Customizing Objects Created by
NSArrayController
Given that your users are probably going to add cars to the system when they
purchase them, it would be nice if the datePurchased attribute were set to the
current date. One good way to do this is to subclass NSArrayController and
override its newObject() method.
Create a new file of type Cocoa Class. It will be a subclass of NSArrayController.
Name it CarArrayController. In CarArrayController.swift, override newObject:
override func newObject() -> AnyObject {
let newObj = super.newObject() as! NSObject
let now = NSDate()
newObj.setValue(now, forKey:"datePurchased")
return newObj
}
Open Document.xib in Interface Builder and select the array controller. In the identity
inspector, set the class to be CarArrayController. (Be sure that you are in the
identity inspector, not the attributes inspector; this array controller is still holding
on to an array of instances of the Car entity.) See Figure 13.20.
Figure 13.20 Changing the class of array controller
Run your application. When new cars are added, their datePurchased attribute
should be initialized to the current date.
Challenge: Begin Editing on Add
After studying the code for Document.addEmployee(_:) in the RaiseMan
application, make the CarArrayController begin editing the Make/Model column of
the table view when a new record is created. Hint: You will need to add an outlet
to CarArrayController.
Challenge: Implement RaiseMan Using Core
Data
Follow the steps early in this chapter to create a brand new RaiseMan project, but
this time check Use Core Data. Instead of the Employee class, create an Employee
entity, and so forth.
14
User Defaults
Many applications allow you, as the user, to configure their behavior or
appearance. Cocoa provides a simple means of storing such preferences in the
user’s defaults database.
NSUserDefaults
The NSUserDefaults class is your interface to the user’s defaults database. With
it you can register factory defaults, set the user’s defaults, and read the defaults
back out as needed. You can think of it as a very fancy dictionary.
Your application can register a set of defaults “from the factory.” These are the
defaults that your application uses when users have not made their own
configuration selections. These factory defaults are registered programmatically
every time your application is launched.
When users configure your application, you store their preferences as a default
in the NSUserDefaults object. Note that only the defaults your code sets are
saved in this way. Factory defaults are not persisted between runs.
As your application runs, it will ask the NSUserDefaults object for the value of
various defaults which correspond to behaviors in your application. The value
returned may come from the factory defaults, previously written defaults, or
elsewhere. These sources are checked in a set search order.
The terminology when working with NSUserDefaults can be confusing. At an
application level, the user chooses preferences, but NSUserDefaults deals in
defaults. Remember that you register defaults, you write defaults to reflect the
user’s preferences, and you read defaults to inform your app’s behavior.
As an example, when you first launched Xcode, it had a factory-default code
highlighting preference chosen – it is even called Default. You can see this
preference by navigating to Xcode’s preferences and selecting the Fonts & Colors tab.
The Apple engineers responsible for Xcode registered this highlighting default as
the factory default with NSUserDefaults.
If you change Xcode’s code highlighting color scheme to Basic or Dusk – if you
select any highlighting scheme at all – your preference will be written as a
default in the user’s defaults database. If you never change the preference from
its default, the defaults database will not contain an entry for that default.
Note that if you change the color scheme to Basic and then change it back to
Default, the new default will be persisted, even though the new default is the same
as the factory default.
When Xcode is running, it reads the code highlighting color scheme default from
NSUserDefaults and uses the result to determine how to highlight your code.
Here are some commonly used methods on NSUserDefaults. Many of them take
a defaultName parameter. This parameter is a String that identifies which default
is being read or written, much like a key in a dictionary.
Shared defaults
class func standardUserDefaults() -> NSUserDefaults
Returns the shared defaults object.
Registering defaults
func registerDefaults(registrationDictionary:
[NSObject : AnyObject])
Registers the factory defaults for the application.
Setting defaults
func setBool(value: Bool, forKey defaultName: String)
func setFloat(value: Float, forKey defaultName:
String)
func setInteger(value: Int, forKey defaultName:
String)
func setObject(value: AnyObject?, forKey defaultName:
String)
Set the value for the defaultName default. Use the method that is correct for the
type of value that is being stored.
Removing defaults
func removeObjectForKey(defaultName: String)
Removes the value for the defaultName default. The application will return to
using the value given by the factory defaults or elsewhere.
Reading defaults
func boolForKey(defaultName: String) -> Bool
func floatForKey(defaultName: String) -> Float
func integerForKey(defaultName: String) -> Int
func objectForKey(defaultName: String) -> AnyObject?
Read the value for the defaultName default. Usually this will be the user’s
previously stored default value, or, if it has not been set, the factory default.
Adding User Defaults to SpeakLine
Over the rest of this chapter, you are going to add user defaults to your SpeakLine
app. These changes will allow the user’s configuration of the text to be spoken
and the voice that it will be spoken in to be persisted across runs of the app.
Rather than using NSUserDefaults directly to configure SpeakLine’s user interface
and store the user’s defaults from within AppDelegate, you will write a
PreferenceManager wrapper class. This class will make use of NSUserDefaults to
persist preferences. AppDelegate, in turn, will use this wrapper class to access
and change preferences.
Create a new Swift file, PreferenceManager.swift, in the SpeakLine group in the
project navigator. In PreferenceManager.swift, create a new class
PreferenceManager:
import Foundation
class PreferenceManager {
}
Right now, the class does not do anything, but it has a lot of potential! You gave
the class a constant property outlet to the standard user defaults, which will make
it more convenient to call methods on the user defaults object later when you add
methods to PreferenceManager.
Create Names for the Defaults
You will be registering factory defaults, writing user preferences for defaults,
and reading values for defaults, all based on a string that names each default.
You will define a constant string for each of the default names.
At the top of PreferenceManager.swift, above the PreferenceManager, add the
following constants:
import Foundation
class PreferenceManager {
Why define constants that simply contain a fixed string? After all, you could just
remember what the string was and type it in whenever you need it. The danger is
that you might misspell the string. If the string is surrounded by quotes, the
compiler will accept the misspelled string. In contrast, if you misspell the name
of a global variable, the compiler will catch your error.
Register Factory Defaults for the Preferences
When your application is first launched, your PreferenceManager object will
need to register the factory defaults. Add a method which registers the defaults
in PreferenceManager.swift:
class PreferenceManager {
func registerDefaultPreferences() {
let defaults =
[ activeVoiceKey :
NSSpeechSynthesizer.defaultVoice() ,
activeTextKey : "Able was I ere I saw
Elba." ]
userDefaults.registerDefaults(defaults)
}
}
If you try to run the app at this point, you will get a compiler error: Use of
unresolved identifier 'NSSpeechSynthesizer'. This means that the
compiler does not know what NSSpeechSynthesizer is. This is because
NSSpeechSynthesizer is in the AppKit framework. To fix this, you need to
import not just Foundation but all of Cocoa into PreferenceManager.swift:
import Foundation
import Cocoa
By the time that any code using PreferenceManager has a chance to use the class,
these factory defaults should have been registered. No code should ever be able
to retrieve the value for a default before the factory default value has been
registered. To prevent this from happening, make PreferenceManager implement
init to call this method:
class PreferenceManager {
private let userDefaults =
NSUserDefaults.standardUserDefaults()
init() {
registerDefaultPreferences()
}
func registerDefaultPreferences() {
The factory defaults should be registered when the app launches. Open
MainWindowController.swift, add a property for storing a PreferenceManager,
and default initialize it:
...
@IBOutlet weak var tableView: NSTableView!
}
Reflecting the Preferences in the UI
Now that values for the activeVoice and activeText properties can be read from
the PreferenceManager, make MainWindowController read from them when its
window loads and update its window’s UI accordingly:
func windowDidLoad() {
super.windowDidLoad()
updateButtons()
speechSynth.delegate = self
for voice in voices {
println(voiceNameForIdentifier(voice)!)
}
let defaultVoice =
NSSpeechSynthesizer.defaultVoice()
let defaultVoice =
preferenceManager.activeVoice!
if let defaultRow = find(voices, defaultVoice)
{
let indices = NSIndexSet(index:
defaultRow)
tableView.selectRowIndexes(indices,
byExtendingSelection: false)
tableView.scrollRowToVisible(defaultRow)
}
textField.stringValue =
preferenceManager.activeText!
}
At this point, the app will read from the user defaults, but not write to them, so it
will behave exactly as before. To improve this, you need to save the text that the
user enters and the voice that the user selects into the preferences manager.
Writing the Preferences to User Defaults
Now, add setters for the activeVoice and activeText properties. You will use
each setter to store the user’s preferred value for that preference.
var activeVoice: String? {
set (newActiveVoice) {
userDefaults.setObject(newActiveVoice,
forKey: activeVoiceKey)
}
get {
return
userDefaults.objectForKey(activeVoiceKey) as? String
}
}
// MARK: - NSTextFieldDelegate
}
Run the app and enter some text. Quit and run it again. The text you entered
should persist across runs.
What Can Be Stored in NSUserDefaults?
You may have noticed that, aside from the scalar types, NSUserDefaults provides
APIs for setting and getting objects. What kind of objects? Property list objects.
Property list objects are instances of the Objective-C classes NSDictionary,
NSArray, NSString, and NSNumber. A plist data structure is composed entirely of
these objects. As such you can store any data structure in NSUserDefaults that
you like, as long as you can represent it as a plist. Since the Swift basic types are
bridged to these types, you can store them without any issues. An example:
let userDefaults =
NSUserDefaults.standardUserDefaults()
let plistKey = "plist"
if let plist: AnyObject =
userDefaults.objectForKey(plistKey) {
println("plist = \(plist)")
}
let plist = ["favoriteNumbers": [77, 82]]
userDefaults.setObject(plist, forKey: plistKey)
Precedence of Types of Defaults
We have already mentioned that the user’s defaults take precedence over the
factory defaults. In fact, several more levels of precedence exist. These levels of
defaults are known as domains. Here are the domains used by an application,
from highest to lowest priority:
Arguments
Passed on the command line. Most people start applications by double-clicking
an icon instead of from the command line, so this feature is seldom used in a
production app.
Application
What comes from the user’s defaults database.
Global
Defaults that the user has set for his or her entire system.
Language
Defaults based on the user’s preferred language.
Registered defaults
The factory defaults registered at launch.
What is the User’s Defaults Database?
The NSUserDefaults object relies on the user’s defaults database to persist the
defaults that are written to it. The database itself is persisted as .plist files in
~/Library/Preferences. You can open this folder in Finder by using Go → Go to
Folder... and entering the path. These files are binary plists; you can view their
contents in Xcode.
Each application’s preferences are stored in a file using its bundle identifier as
the base name. SpeakLine’s bundle identifier is com.bignerdranch.SpeakLine,
unless you used a different organization identifier or project name when you
created the application in Chapter 6.
In older versions of OS X you could edit these files directly, but in OS X
Mavericks and later a caching system was added. A daemon – a process that
runs in the background – manages the database and all of the .plist files. If you
edit the file your changes will likely be overwritten. Instead, you must use the
defaults command-line tool, which is described in the next section.
Figure 14.2 NSUserDefaults, the user defaults database, and the filesystem
For the More Curious: Reading/Writing
Defaults from the Command Line
You can use the defaults command-line tool to safely read and write defaults.
For example, to see your defaults for Xcode, you can bring up the Terminal and
enter the following command:
defaults read com.apple.dt.Xcode
You should see all your defaults for Xcode. There are a lot of them! A few lines of
ours look like this:
IDEDisableGitSupportForNewProjects = 1;
IDEEnableLiveIssues = 0;
IDEFileTemplateChooserAssistantSelectedTemplateCategory
= Source;
IDEFileTemplateChooserAssistantSelectedTemplateName =
"Cocoa Class";
IDEFileTemplateChooserAssistantSelectedTemplateSection
= "com.apple.platform.macosx";
IDEHasConvertedXcode3BuildPrefs = 1;
IDEHexEditorByteGrouping = 1;
IDEHexEditorLineNumberFormat = 0;
You can also write to the user preferences. To set Xcode’s default directory in the
NSOpenPanel to the /Users directory, you could enter this:
defaults write com.apple.Xcode
NSNavLastRootDirectoryForOpen /Users
Try this:
defaults read com.bignerdranch.SpeakLine
If you entered text and selected a voice, you will see it in the output.
To see your global defaults, enter this:
defaults read NSGlobalDomain
For the More Curious:
NSUserDefaultsController
Sometimes you will want to bind to a value from the NSUserDefaults object. An
NSUserDefaultsController makes this possible. All the NIBs in your application
will use a single shared instance of NSUserDefaultsController.
For example, if you wanted to use bindings to deal with SpeakLine’s text field, you
would bind it to the shared NSUserDefaultsController with a Controller Key of
values and a Model Key Path of activeTextKey (Figure 14.3).
Figure 14.3 Binding to the NSUserDefaultsController
Challenge: Reset Preferences
Add a button that will remove all the user’s defaults. Label the button Reset
Preferences. Do not forget to update the window to reflect the new defaults.
15
Alerts and Closures
Occasionally, you will want to warn the user about something by presenting an
alert. To do this, you use the NSAlert class. NSAlert saves you the work of
designing a custom user interface for a simple and common user interaction,
while also providing the user with a consistent experience.
NSAlert
Figure 15.1 shows a basic modal alert:
Figure 15.1 A modal alert
One last thing: if you reference any properties or methods on self, Swift
requires you to use the self constant to make the fact that it will be captured
clear. You can include self in the capture list if you need to.
Now that you have some background on closures, let’s put that knowledge to
work.
Make the User Confirm the Deletion
In RaiseMan, if the user clicks the Remove button, an alert panel should appear to
confirm the deletion (Figure 15.5). Of course, the user should also be able to
remove multiple employees at once, rather than tediously removing them one by
one.
Figure 15.5 Completed application
To enable this behavior, open Document.xib, select the table view, and open the
attributes inspector. Allow the user to select multiple rows by checking Multiple
next to Selection (Figure 15.6).
Figure 15.6 Enabling multiple selection in the table view’s attributes
inspector
You now want to create an action method in Document that will display the alert
as a sheet when the Remove button is clicked. If the user confirms the deletion,
Document will call the remove(_:) method on the array controller to remove the
selected Employee objects.
In Xcode, open the Document.swift file and add the removeEmployees(_:) method:
@IBAction func removeEmployees(sender: NSButton) {
let selectedPeople: [Employee] =
arrayController.selectedObjects as! [Employee]
let alert = NSAlert()
alert.messageText = "Do you really want to remove
these people?"
alert.informativeText = "\(selectedPeople.count)
people will be removed."
alert.addButtonWithTitle("Remove")
alert.addButtonWithTitle("Cancel")
let window = sender.window!
alert.beginSheetModalForWindow(window,
completionHandler: { (response) -> Void in
// If the user chose "Remove", tell the array
controller to delete the people
switch response {
case NSAlertFirstButtonReturn:
// The array controller will delete the
selected objects
// The argument to remove() is ignored
self.arrayController.remove(nil)
default: break
}
})
}
Note that beginSheetModalForWindow(_:completionHandler:) will return
immediately. This allows the event loop to continue processing events for the
sheet and for the other windows (but not the window the sheet was presented
from). Once the user clicks a button, the completion handler will be called upon
to react. If the user selects “Remove”, the array controller will remove the
selected employees. Otherwise, the employee roster will be unchanged.
However, as it stands the alert sheet is never presented because
removeEmployees(_:) never gets called. Right now, when the user clicks the
Remove button, the selected employees will be immediately deleted. To change
this, you need to change the target and action of the Remove button.
Open Document.xib. Control-drag from the Remove button to the File's Owner icon to
make the File's Owner be the new target (Figure 15.7).
Figure 15.7 Setting File's Owner as the Remove button’s target
Set the action to removeEmployees: (Figure 15.8).
Figure 15.8 Setting removeEmployees: as the Remove button’s action
This is happening because the same string is being used for the alert’s
informativeText regardless of the number of employees that may be removed.
When the user attempts to remove a single employee, make the alert’s
informative text be “1 person will be removed.” While making this change, be
sure to preserve the informative text that is displayed when the user attempts to
remove several employees.
Next, change the messageText of the alert. When the user attempts to remove a
single employee, it should read “Do you really want to remove this person?”
Finally, improve the informativeText of the alert again. When the user tries to
remove one employee named “Jane Smith”, the alert’s informative text should
be “Jane Smith will be removed.”
16
Using Notifications
What Notifications Are
Every running application has an instance of NSNotificationCenter, which
functions much like a bulletin board. Objects register themselves as being
interested in certain notifications (“Please write me if anyone finds a lost dog”).
We call the registered object an observer. Other objects can then post
notifications to the center (“I have found a lost dog”). That notification is
subsequently forwarded to all objects that are registered as interested. We call
the object that posted the notification a poster.
Many standard Cocoa classes post notifications: Windows send notifications that
they have changed size. When the selection of a table view changes, the table
view sends a notification. The notifications sent by standard Cocoa objects are
listed in the online documentation.
What Notifications Are Not
A notification center allows objects in an application to send notifications to
other objects in that same application. When programmers first hear about the
notification center, they sometimes think that it is a form of interprocess
communication – that they can create an observer in one application and post
notifications from an object in another.
Notifications do not travel between applications, at least not via
NSNotificationCenter. (Look into NSDistributedNotificationCenter if you
need to pass notifications between applications.)
NSNotification
Notification objects are very simple. A notification is like an envelope into
which the poster places information for the observers. A notification has two
important properties: name and object.
var name: String
var object: AnyObject?
Nearly always, object is a reference to the object that posted the notification. (It
is analogous to a return address.)
NSNotificationCenter
The NSNotificationCenter is the brains of the operation. It allows you to do
three things: register observer objects, post notifications, and unregister
observers.
Here are some commonly used methods implemented by NSNotificationCenter:
class func defaultCenter() -> NSNotificationCenter
func addObserver(observer: AnyObject,
selector aSelector: Selector,
name aName: String?,
object anObject: AnyObject?)
func postNotification(notification: NSNotification)
func postNotificationName(aName: String, object
anObject: AnyObject?)
func removeObserver(observer: AnyObject)
For example, suppose you have an app with a class DatabaseManager dedicated
to interacting with a database. That class needs to make sure its file handle to the
database is closed before your app terminates. To be informed when the
application will terminate, it registers itself as an observer of
NSApplicationWillTerminateNotification with the selector
receiveWillTerminateNotification: (Figure 16.1).
First, lay the groundwork. Create a new project in Xcode called Chatter. Make sure
that Use Storyboards, Create Document-Based Application, and Use Core Data are all unchecked.
Before you get started, you need to remove the cruft that is provided in Xcode’s
template. First, open MainMenu.xib and remove the window. Then open
AppDelegate.swift and remove the outlet to the window.
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification:
NSNotification) {
You are now ready to get started on the project. In Xcode create a new Cocoa Class
called ChatWindowController, a subclass of NSWindowController. While creating
it, be sure to check Also create XIB file for user interface. This class will do the lion’s
share of the work.
Open ChatWindowController.xib, select the window, and, in the attributes
inspector, uncheck Visible At Launch. This prevents a window from being put on the
screen until showWindow(_:) is called. If you left this checked, when creating a
large number of windows one after another in your app you would see each
window appear briefly at a default location before being moved to the correct
position.
In ChatWindowController.swift, implement windowNibName so that
ChatWindowController knows the name of its NIB file:
class ChatWindowController: NSWindowController {
// MARK: - Lifecycle
func applicationDidFinishLaunching(aNotification:
NSNotification) {
Next, create a function which adds a window controller to the array and shows
its window:
func applicationWillTerminate(aNotification:
NSNotification) {
}
// MARK: - Helpers
func addWindowController() {
let windowController = ChatWindowController()
windowController.showWindow(self)
windowControllers.append(windowController)
}
This method will need to be called from two places: first, when the application
launches, and second, when the user selects New from the File menu. Add a call to
this method in applicationDidFinishLaunching(_:):
func applicationDidFinishLaunching(aNotification:
NSNotification) {
addWindowController()
}
Now, add an action method which will be triggered when the user selects the New
menu item. This action method will just call addWindowController:
func applicationWillTerminate(aNotification:
NSNotification) {
// MARK: - Actions
// MARK: - Helpers
It is time to hook up the New menu item. Open MainMenu.xib and find the menu
item inside the File menu. Control-drag from New to App Delegate to set its target to
be the app delegate; then set its action to be displayNewWindow: (Figure 16.6).
Figure 16.6 Setting the New menu item’s action
If you run your app now, you will be able to put a lot of windows on the screen.
It is time to make those windows do something.
First, open ChatWindowController.swift and declare a few properties.
class ChatWindowController: NSWindowController {
// MARK: - Lifecycle
You will use the first two of these properties to bind the views (which you will
set up in a moment) to the window controller, which is why they must be marked
dynamic. The third property you will use as an outlet to the text view you will be
adding. Notice that unlike the other outlets you have seen so far, this outlet is not
marked weak.
While you have ChatWindowController.swift open, declare an empty action
method send(_:).
override func windowDidLoad() {
super.windowDidLoad()
// MARK: - Actions
@IBAction func send(sender: AnyObject) {
}
This method will be triggered when the user sends a chat message to the other
window controllers.
Now, open ChatWindowController.xib. Drag out a text view, a text field, and a
button. Lay out these views as shown in Figure 16.7.
Figure 16.7 Laying out the window’s subviews
private let
ChatWindowControllerDidSendMessageNotification
=
"com.bignerdranch.chatter.ChatWindowControllerDidSendMessageNot
// ChatWindowControllerDidSendMessageNotification
func receiveDidSendMessageNotification(note:
NSNotification) {
}
Now, make the ChatWindowController add itself to the default notification center
as an observer of ChatWindowControllerDidSendMessageNotification whenever
any object posts it:
override func windowDidLoad() {
super.windowDidLoad()
let notificationCenter =
NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(self,
selector:
Selector("receiveDidSendMessageNotification:"),
name:
ChatWindowControllerDidSendMessageNotification,
object: nil)
}
Now, when any window controller posts
ChatWindowControllerDidSendMessageNotification, every window controller
will have receiveDidSendMessageNotification(_:) called on it.
When a window controller observes this notification, it should add the newly
received text at the bottom of the text displayed in the text view.
// ChatWindowControllerDidSendMessageNotification
func receiveDidSendMessageNotification(note:
NSNotification) {
let mutableLog = log.mutableCopy() as!
NSMutableAttributedString
if log.length > 0 {
mutableLog.appendAttributedString(NSAttributedString(string:
"\n"))
}
textView.scrollRangeToVisible(NSRange(location:
log.length, length: 0))
}
Notice the last line of code: it makes the text view display the latest message by
scrolling to ensure that the very last character is visible.
scrolling to ensure that the very last character is visible.
At this point, you are nearly done. When you send a notification from one
window controller, all the window controllers will receive that notification and
update their text views accordingly.
Note that each window controller is observing notifications from its own window
as well as any others. This means that the text view in a given window will
include the messages sent from that same window.
The only thing left to do is a bit of cleanup. You need to make each
ChatWindowController remove itself as an observer when it is deallocated.
override func windowDidLoad() {
...
}
deinit {
let notificationCenter =
NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self)
}
// MARK: - Actions
This is necessary because the notification center has an unowned reference to the
window controller. This means that if you forget to remove the window
controller as an observer and it gets deallocated, when the notification is next
posted, the notification center will try to call
receiveDidSendMessageNotification(_:) on the deallocated window controller.
At best, this will lead to a crash. At worst, this will cause unexpected behavior.
For the More Curious: Delegates and
Notifications
An object that has made itself the delegate of a standard Cocoa object is
probably interested in receiving notifications from that object as well. For
example, if you have implemented a delegate to handle the
windowShouldClose(_:) delegate method for a window, that same object is likely
to be interested in the NSWindowDidResizeNotification from that same window.
If a standard Cocoa object has a delegate and posts notifications, the delegate is
automatically registered as an observer for the methods it implements. If you are
implementing such a delegate, how would you know what to call the method?
The naming convention is simple: Start with the name of the notification.
Remove the NS from the beginning and make the first letter lowercase. Remove
the Notification from the end. Add a colon. For example, to be notified that
the window has posted an NSWindowDidResizeNotification, the delegate would
implement the following method:
optional func windowDidResize(notification:
NSNotification)
This method will be called automatically after the window resizes. You can also
find this method listed in the documentation and header files for NSWindow.
Challenge: Beep-beep!
Make your application beep when it gives up its active status. NSApplication
posts an NSApplicationDidResignActiveNotification notification. Your
AppDelegate is a delegate of NSApplication. NSBeep() will cause a system beep.
Challenge: Add Usernames
In your current Chatter app, there is no way to tell who sent what message. Add a
text field to the top of the ChatWindowController where the user can enter a
username. Send the username – or, if none has been entered, a good default
value – with the notification when they send a message. Display the username in
the text view.
Challenge: Colored Text
Now that you have completed the last challenge, your users will be able to tell
by reading who sent which message. However, it is still not immediately
apparent visibly which messages were sent from this window controller. In
receiveDidSendMessageNotification(_:), if the notification was posted by self,
add an attribute to the logLine attributed string to make that line blue. You will
want to use NSForegroundColorAttributeName.
Challenge: Disabling the Send Button
As it stands, the Send button never gets disabled. But it should: when there is no
message text entered, no action is taken when the user clicks the button. Disable
the button at the appropriate time using bindings. In particular, bind the button’s
Enabled value to File's Owner’s message property.
var mainWindowController:
MainWindowController?
func
applicationDidFinishLaunching(aNotification:
NSNotification) {
// Create a window controller with a XIB
file of the same name
let mainWindowController =
MainWindowController()
// Put the window of the window controller
on screen
mainWindowController.showWindow(self)
}
Create the MainWindowController class: Create a new Cocoa class (Command-
N). Name it MainWindowController and make it a subclass of
NSWindowController. Check the Also create XIB file for user interface box.
}
Run the application to confirm that everything is set up correctly. You should
see an empty Window.
Creating a view subclass
Create a new Swift File and name it DieView. In DieView.swift, provide a stub
implementation of DieView:
import Foundation
import Cocoa
}
Open MainWindowController.xib and click the window in the editor dock. Drag
a Custom View from the object library and drop it onto the window (Figure 17.3).
Resize the view to fill most of the window.
Figure 17.3 Dropping a view onto the window
Dragging from the object library to the content view creates a view object and
adds it as a subview of the content view. However, the view is not yet an
instance of DieView. Open the identity inspector and set the view’s class to
DieView (Figure 17.4).
frame
The position and size of the view are specified by its frame property, which is an
NSRect.
struct NSRect {
var origin: NSPoint
var size: NSSize
}
The origin of the frame specifies the position of the lower-left corner as the
offset from the superview’s lower-left corner. It is a coordinate in 2D space,
represented by an NSPoint with x and y components.
struct NSPoint {
var x: CGFloat
var y: CGFloat
}
The size of the frame specifies the size of the view’s rectangle. The size is
represented by an NSSize, which has a width and height.
struct NSSize {
var width: CGFloat
var height: CGFloat
}
Why are NSRect, NSSize, and NSPoint structs and not classes? Historically, the
reason is performance, but structs are also inherently safer from bugs related to
mutability because they are passed by value rather than by reference.
Visually, a view’s frame resembles Figure 17.5.
Figure 17.5 NSRect, NSSize, and NSPoint
The members of NSPoint and NSSize are of the type CGFloat, which is a floating
point type. What, then, are the units for NSPoint and NSSize?
You might expect that NSPoint and NSSize would be measured in pixels, but they
are instead measured in points. These points have nothing to do with the NSPoint
struct; they are borrowed from typography where a point is 1/72 of an inch. The
original Macintosh display had 72 pixels per inch - one point was the width of a
pixel. OS X and iOS drawing commands (and PDF, not coincidentally) use
points as their unit of measurement.
The power of points is that they are resolution independent. The idea is that a
point should represent some fairly consistent size, independent of the screen
resolution. On a standard display points correspond 1:1 to pixels, just like they
did on the original Macintosh. On a Retina display, however, a point is two
pixels wide. Because drawing and view coordinates are in points, supporting
pixels wide. Because drawing and view coordinates are in points, supporting
both display types is remarkably uncomplicated for most applications.
NSRect, NSPoint, and NSSize are actually type aliases for the Core Graphics types
CGRect, CGPoint, and CGSize. For legacy reasons the AppKit APIs still use the
NS-prefixed types, but behind the scenes they are aliased to the Core Graphics
types. We will discuss Core Graphics more at the end of this chapter.
bounds
There is another NSRect you use when drawing in Cocoa – the view’s bounds
property. While its type is the same as frame, they have different uses: You use a
view’s frame rectangle to position subviews of the view. For instance, to center a
button in a view, you would use that view’s frame to determine where the button
should go.
You use a view’s bounds rectangle to draw the view itself. For instance, you
would use bounds to tell a button how to draw its rounded rectangle shape.
Custom Drawing
For your first foray into custom drawing, you will start small by drawing a gray
background for the DieView and a green line that bisects the view diagonally.
(Figure 17.6).
Figure 17.6 Drawing with NSColor and NSBezierPath
}
First, observe that drawRect(_:) takes a single parameter, dirtyRect, which is
the rectangle that needs to be drawn.
Within the body of drawRect(_:), you set the color for drawing using a class
method on NSColor, lightGrayColor. This method returns an instance of NSColor.
Then you call the class method fillRect(_:) on NSBezierPath to fill in the
bounds rectangle with the current color.
Note that because you are drawing the background, you simply pass the bounds
rectangle to the fillRect(_:) method. This method accepts a rectangle, creates a
path based on that rectangle, and fills it in with the current color.
Run the application. Your view should now be visible and filled with gray.
Figure 17.7 DieView with light gray background
Issue a second set of drawing commands to draw the diagonal line:
override func drawRect(dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGrayColor()
backgroundColor.set()
NSBezierPath.fillRect(bounds)
NSColor.greenColor().set()
let path = NSBezierPath()
path.moveToPoint(NSPoint(x: 0, y: 0))
path.lineToPoint(NSPoint(x: bounds.width, y:
bounds.height))
path.stroke()
}
In this code, you use two NSBezierPath methods to construct the path of the line.
moveToPoint() sets the starting point for drawing. lineToPoint() maps out the
line from the starting point to the passed-in point. Finally, calling stroke() on
the path draws the line.
Notice that you are accessing bounds.width and bounds.height, yet the structure
definition for NSRect (CGRect) only has origin and size fields. These are
computed properties provided for convenience by means of a Swift extension on
CGRect. The width computed property is equivalent to size.width, and height is
equivalent to size.height.
Run the application and confirm that you have a green diagonal line as shown in
Figure 17.6.
The profile of the die is a square, and it should take up most of the DieView.
Thus, you need to find the smaller of the DieView’s width and height. With this
information, you can define the largest possible square in the view and then
subtract a little padding to get the size of the die profile (Figure 17.8).
In DieView, implement metricsForSize(_:).
func metricsForSize(size: CGSize) -> (edgeLength:
CGFloat, dieFrame: CGRect) {
let edgeLength = min(size.width, size.height)
let padding = edgeLength/10.0
let drawingBounds = CGRect(x: 0, y: 0, width:
edgeLength, height: edgeLength)
let dieFrame =
drawingBounds.rectByInsetting(dx: padding, dy:
padding)
return (edgeLength, dieFrame)
}
These metrics are diagrammed in Figure 17.8. Notice how the dieFrame
rectangle is created from the drawingBounds rectangle – the
rectByInsetting(dx:dy:) method returns a rectangle similar to the one it is
called upon, but inset by dx points on the left and right and by dy points on the
top and button.
Figure 17.8 Metrics for drawing die face
Drawing the dots is more complicated than drawing the profile. First, the
DieView needs to know how many dots to display. Add an intValue property to
represent the number of dots, but make it an optional because there will be times
when the die should not be drawn at all. And for now, give it a hardcoded value
for easy testing.
class DieView: NSView {
}
}
Now, if intValue does not have a value, then the view will draw only its light
gray background.
Also note that because the dotRect rectangle passed into the NSBezierPath
initializer is a square, the oval will be a circle.
Finally, use the generic function find() to determine which positions should
have dots.
// Ready to draw the dots.
// The dots will be black:
NSColor.blackColor().set()
// If intValue is in range...
if find(1...6, intValue) != nil {
// Draw the dots:
if find([1, 3, 5], intValue) != nil {
drawDot(0.5, 0.5) // Center dot
}
if find(2...6, intValue) != nil {
drawDot(0, 1) // Upper left
drawDot(1, 0) // Lower right
}
if find(4...6, intValue) != nil {
drawDot(1, 1) // Upper right
drawDot(0, 0) // Lower left
}
if intValue == 6 {
drawDot(0, 0.5) // Mid left/right
drawDot(1, 0.5)
}
}
}
}
Run the application. You should see a die face with a single dot in the center, as
in Figure 17.9. Change the initial value of intValue to make sure that other
values work as expected. Do not forget to test for the absence of a value.
Figure 17.9 Snake eye
Saving and Restoring the Graphics State
Recall that the graphics context manages the graphics state, which includes the
current color, any transforms such as scaling or rotation, and so on. At times, it
is convenient to treat the graphics state like a stack: You make a copy of the
current state and push it onto the stack. Now the copy is the current state. You
can alter the state, run some drawing commands, and then pop the state to return
to the prior state.
To see this in action, let’s add a shadow to the die shown in DieView. AppKit
shadows are configured using the NSShadow class. Update drawDieWithSize(_:):
let dotRadius = edgeLength/12.0
let dotFrame = dieFrame.rectByInsetting(dx:
dotRadius 2.5,
dy:
dotRadius 2.5)
NSGraphicsContext.saveGraphicsState()
NSGraphicsContext.restoreGraphicsState()
// Shadow will not apply to subsequent drawing
commands
Run the application and confirm that the shadow is now being applied to the die
face, but not to the dots.
This technique is very powerful when drawing hierarchies of objects which, like
views, are positioned relative to the objects they are related to.
Cleaning up with Auto Layout
Your application has a usability issue: The user can resize the application
window, but the instance of DieView will stay its original size. What you want is
for the DieView to resize along with its superview. You can fix this with Auto
Layout.
Auto Layout is a sophisticated system for controlling the layout of views in a
window by applying constraints to views. You will learn more about Auto
Layout and constraints in Chapter 25.
In this section, you are going to skip ahead and apply a constraint to make
DieView resize with the window. The constraint will keep all four edges of the
DieView exactly 20 points away from the edges of its superview (the content
view of the window). Thus, when the window is resized, the DieView will be
resized to meet this constraint.
Open MainWindowController.xib and select the DieView in the window. In the
lower-right corner of the canvas, find the four Auto Layout buttons and click the
Pin button, which looks like .
In that popover that appears, find the Add New Constraints section and click each of
the four struts extending from the square. The struts will turn solid red. If
necessary, change the four text fields to all read 20. (20 points is the standard
spacing from the edges of the window, but you could enter another value.)
Finally, click the Add 4 constraints button at the bottom (Figure 17.10).
Figure 17.10 Adding constraints
If you make a mistake, click the Resolve Auto Layout Issues button, which looks like
, and select Clear Constraints. Note that these menu items are also available in
Xcode’s Editor menu.
Now, when you run the application you should see the DieView – and the die
itself – resize with the window.
If you resize the window to be small enough, the DieView is completely hidden.
You could fix this by adding more constraints, but a sensible solution for a
custom view is to change its intrinsicContentSize.
A view’s intrinsic content size is the natural size of its content, and by default
Auto Layout will not squish a view smaller than its intrinsic content size. In
DieView, override this property:
override var intrinsicContentSize: NSSize {
return NSSize(width: 20, height: 20)
}
Drawing Images
Often, as part of a custom view’s drawing, you will want to draw an image on
the screen. Cocoa makes this extremely simple.
To try it out, you are going to make a small Cocoa application which draws an
image. When you are done, the app will have a view that draws a regular grid of
images (Figure 17.11)
Figure 17.11 The finished ImageTiling project
In Xcode, create a new Cocoa Application project called ImageTiling (Figure 17.12).
Figure 17.12 Configuring the new ImageTiling project
As you did with Dice, set up the new project to use the single-window
architecture. Name the new window controller class MainWindowController. If
you are not completely confident about setting your app up like this, follow the
steps in the section called “Setting Up the Dice Application” .
To make sure that your window controller is set up correctly, run the app. You
should see a window appear on screen, and that is it. If that is not happening,
review the setup of your window controller to make sure that you are not
missing anything.
With your app setup completed, it is time to move on to drawing images. Create
a new Cocoa Class called TiledImageView, a subclass of NSView. This view will
draw a given image multiple times in a grid pattern. To begin with, give the class
an image property and a few constants to determine the characteristics of the
grid:
import Cocoa
}
In a moment, you are going to implement drawRect(_:) to draw the image a
number of times in a grid. But first, to help with implementing drawRect(_:),
you will write a helper method called frameForImageAtLogicalX(_:y:). You will
use this method to find out, for example, where to draw the image that is third
from the left and second from the bottom:
// MARK: - Drawing
Build and run your app. At the moment, even though your window now contains
a TiledImageView, it does not look any different from the way it did before,
because the tiled image view is not drawing anything. This is because you have
not set its image property yet.
There are a couple of ways to set the image view’s image property. You could
add an outlet to the image view from the window controller and set the outlet’s
image property in windowDidLoad(). Instead, you are going to follow a slicker
approach.
Open MainWindowController.xib again and select the tiled image view. Switch
to the attributes inspector. You will see a new section entitled Tiled Image View. In
that section, you will see a new field: Image. You can enter the name of any image
that your project or Cocoa provides. Try it out by entering NSComputer.
If you build and run the app now, you will see that image drawn in a grid.
Fantastic!
Wouldn’t it be nice to be able to see what it will look like from Interface Builder?
Xcode allows you to do this. Just open TiledImageView.swift and add the
@IBDesignable attribute to the class:
@IBDesignable class TiledImageView: NSView {
Now switch back to MainWindowController.xib. If you look at the title bar, you
will that Xcode is building your app. Once it has finished successfully,
Interface Builder will make your tiled image view draw itself with images
(Figure 17.14).
Figure 17.14 The @IBDesignable ImageTilingView with @IBInspectable
image in Xcode
Drawing images with finer control
The drawInRect(_:) method you used in this exercise draws the image as you
would expect, using the most common settings: the whole image at 100%
opacity, respecting the alpha channel (if present). If you need more control,
however, consider drawInRect(_:fromRect:operation:fraction:):
func drawInRect(rect: NSRect,
fromRect: NSRect,
operation op: NSCompositingOperation,
fraction delta: CGFloat)
The first parameter of this method is the same as the parameter to
drawInRect(_:): the rect that you want to draw into. The second parameter
allows you to specify a subregion of the image which should be drawn. You use
the third parameter to describe how the image drawing should be composited
with other drawing operations (to simply draw the image on top of everything
else, use NSCompositingOperation.CompositeSourceOver). You use the last
parameter to specify the opacity with which the image should be drawn.
Scroll Views
When displaying information to the user, you will often find that there is simply
not enough space to display all that you need to in your app’s view hierarchy.
For example, imagine that you were writing a web browser. Your app would
render HTML and CSS into graphical web pages to show to your users. Many
web pages have a great deal of content – too much to fit on your users’ screens,
let alone in your app’s views. How would you present the large graphical content
of the web pages to the user in the small views that your application has to work
with?
Cocoa provides the class NSScrollView for exactly this type of situation. You
have already seen a scroll view in RaiseMan. In that app, your table view was
embedded inside a scroll view.
A scroll view acts as a window from one of your views into a larger view,
known as the document view, which cannot fit completely within your view.
Only a portion of the document view is ever displayed within the scroll view.
That portion is known as the visible rect (Figure 17.15).
Figure 17.15 NSScrollView in context
Currently, in ImageTiling, the five rows and five columns of the image are not all
visible. To fix this, you will embed the TiledImageView inside a scroll view.
Open MainWindowController.xib, expand the document outline, and select the
Tiled Image View. Tell Interface Builder to embed the tiled image view inside a scroll
view by selecting Editor → Embed In → Scroll View.
At this point, you are almost done. The document outline should show that the
tiled image view is nested within a scroll view now (Figure 17.16).
Figure 17.16 The tiled image view embedded within a scroll view
Run the app. You will not see any images at all now! The only thing that will be
visible is the border of the scroll view (Figure 17.17). (Incidentally, you can edit
the scroll view’s border using the attributes inspector.)
Figure 17.17 No images appear in the running app!
Earlier in the chapter you dealt with a similar problem with DieView: with the
view pinned to the edges of the window, it could be forced down to nothing. The
same problem is occurring here: Auto Layout does not know how big to make
the scroll view’s document view, so it defaults to a size of zero. The best
solution is to give this view an intrinsic content size, too.
Override intrinsicContentSize in TiledImageView. You can calculate the size of
the entire grid by using the furthest image’s frame:
override func drawRect(dirtyRect: NSRect) {
if let image = image {
for x in 0..<columnCount {
for y in 0..<rowCount {
let frame =
frameForImageAtLogicalX(x, y: y)
image.drawInRect(frame)
}
}
}
}
}
Run your app. The scroll view should now contain your tiled image view. Use
your trackpad or mouse to scroll around inside it. You should be able to scroll to
all five rows and columns of the image, as shown in Figure 17.18.
Figure 17.18 Tiled image view now visible and scrollable
Creating Views Programmatically
You will usually create your views in Interface Builder, but occasionally you will
need to create views programmatically. If, for example, you have a pointer to a
window and want to put a button on it, this code would create a button and put it
on the window’s content view:
let superview = window.contentView
let frame = NSRect(x: 10, y: 10, width: 200, height:
100)
let button = NSButton(frame: frame)
button.title = "Click me!"
superview.addSubview(button)
Some of the Cocoa view classes are capable of a surprising variety of
appearances. For example, you have no doubt seen that there are a lot of buttons
in the object library. But there is only one NSButton class. All of the different
button appearances are created by setting various properties on instances of that
class.
To figure out how to programmatically recreate a style seen in Interface Builder,
drag the desired view onto the canvas, select it, and comb through the properties
in the attributes inspector. If you hover over one of the attributes in the inspector,
(Style, Type, and so on) Xcode will display a tooltip with a hint of the property you
can use to control this property. Two common properties for configuring an
NSButton are bezelStyle and bordered.
Be patient, it may take a number of tries before you find all of the properties that
need to be set. An Xcode playground can help to more rapidly iterate until you get
the appearance you are looking for (Figure 17.19).
Figure 17.19 Using a playground to prototype a button
For the More Curious: Core Graphics and
Quartz
The APIs for drawing used in this chapter are commonly referred to as AppKit
drawing APIs. Behind the scenes, the AppKit drawing APIs are implemented
using a framework called Core Graphics, which is colloquially referred to as
Quartz.
If you are serious about custom drawing you will want to look into Core
Graphics. The concepts are very similar, except it is a C API, and it is much
more explicit about the graphics context (almost every Core Graphics API takes
a CGContextRef, essentially a pointer to the context, as its first parameter). Core
Graphics provides somewhat lower-level access to drawing and, in some cases,
much finer control than is possible in AppKit.
For the More Curious: Dirty Rects
As mentioned previously, the NSRect that is passed as an argument to the view is
the region that is “dirty” and needs redrawing. It may be less than the entire
view. If you are doing very time-consuming drawing, redrawing only the dirty
rectangle may speed up your application considerably.
Note that setting needsDisplay to true will trigger the entire visible region of the
view to be redrawn. If you wanted to be more precise about which part of the
view needs redrawing, you would use setNeedsDisplayInRect(_:) instead:
let dirtyRect = NSRect(x: 0, y: 0, width: 50, height:
50)
myView.setNeedsDisplayInRect(dirtyRect)
For the More Curious: Flipped Views
Both PDF and PostScript use the standard Cartesian coordinate system, in which
y increases as you move up the page. AppKit views follow this model by default.
Thus, the origin is usually at the lower-left corner of the view.
For some types of drawing, the math is easier if the upper-left corner is the
origin and y increases as you move down the page. Such a view is said to be
flipped.
To flip a view, you override the flipped property to return true:
override var flipped: Bool {
return true
}
Challenge: Gradients
NSGradient can be used to draw gradients (ramps between two or more colors).
Use NSGradient and NSBezierPath to draw the die face with a gradient effect.
Hint: you will need to pass an instance of NSBezierPath describing the rounded
rectangle to one of the methods on NSGradient.
Challenge: Stroke
Instead of giving the die a more realistic look with a gradient, try to achieve a
more cell-shaded, cartoonish effect by adding a border to the die. This can be
done by stroke()-ing a NSBezierPath. For bonus points, add glints to the dots on
the die.
Challenge: Make DieView Configurable from
Interface Builder
Currently, it is not at all clear in Interface Builder what a DieView will look like when
it is drawn. Change this using the @IBDesignable attribute.
18
Mouse Events
In the previous chapter, you learned to use drawing commands to make a read-
only view. A more useful view would be able to accept user input, and what
better place to start than handling mouse events?
NSResponder
NSView inherits from NSResponder. All the event-handling methods are declared
in NSResponder. We will discuss keyboard events in the next chapter. For now,
we will focus on mouse events. NSResponder declares these methods:
func mouseDown(theEvent: NSEvent)
func rightMouseDown(theEvent: NSEvent)
func otherMouseDown(theEvent: NSEvent)
func mouseUp(theEvent: NSEvent)
func rightMouseUp(theEvent: NSEvent)
func otherMouseUp(theEvent: NSEvent)
func mouseDragged(theEvent: NSEvent)
func scrollWheel(theEvent: NSEvent)
func rightMouseDragged(theEvent: NSEvent)
func otherMouseDragged(theEvent: NSEvent)
Notice that the argument is always an NSEvent object.
NSEvent
An event object has all the information about what the user did to trigger the
event. When you are dealing with mouse events, you might be interested in the
following properties:
var locationInWindow: NSPoint { get }
var modifierFlags: NSEventModifierFlags { get }
var timestamp: NSTimeInterval { get }
unowned(unsafe) var window: NSWindow! { get }
var clickCount: Int { get }
var pressure: Float { get }
var deltaX: CGFloat { get }
var deltaY: CGFloat { get }
var deltaZ: CGFloat { get }
Getting Mouse Events
To get mouse events, you need to override the mouse event methods. Open
DieView.swift from Chapter 17 and override the methods in DieView:
// MARK: - Mouse Events
NSPanGestureRecognizer
NSMagnificationGestureRecognizer
NSPressGestureRecognizer
NSRotationGestureRecognizer
Gesture recognizers are added to a specific view and report the gestures using
target/action, just like controls:
func setupPan() {
let pan = NSPanGestureRecognizer(target: self,
action:
Selector("pan:"))
someView.addGestureRecognizer(pan)
}
func pan(pan: NSClickGestureRecognizer) {
let location: NSPoint =
pan.locationInView(someView)
switch pan.state {
case .Began:
println("Began panning at \(location)")
case .Changed:
println("Panning at \(location)")
case .Ended:
println("Ended panning at \(location)")
default:
break
break
}
}
Gesture recognizers are much richer than controls, however. The state property
indicates the phase of the recognition.
Multiple gesture recognizers can be used together, but at times you will need to
mediate between them. For example, you may need to differentiate a click from
a double-click. Mediation is performed by implementing a delegate protocol:
class MainWindowController: NSWindowController,
NSGestureRecognizerDelegate {
@IBOutlet weak var someView: NSView!
var click: NSClickGestureRecognizer?
var doubleClick: NSClickGestureRecognizer?
func setupClickAndDoubleClick() {
let click = NSClickGestureRecognizer(target:
self,
action:
Selector("click:"))
click.delegate = self
someView.addGestureRecognizer(click)
let doubleClick =
NSClickGestureRecognizer(target: self,
action: Selector("doubleClick:"))
doubleClick.numberOfClicksRequired = 2
someView.addGestureRecognizer(doubleClick)
self.click = click
self.doubleClick = doubleClick
}
func gestureRecognizer(gestureRecognizer:
NSGestureRecognizer,
shouldRequireFailureOfGestureRecognizer other:
NSGestureRecognizer) -> Bool {
NSGestureRecognizer) -> Bool {
if gestureRecognizer == click && other ==
doubleClick {
return true
}
return false
}
func click(click: NSClickGestureRecognizer) {
if click.state == .Ended {
println("Clicked!")
}
}
func doubleClick(doubleClick:
NSClickGestureRecognizer) {
if doubleClick.state == .Ended {
println("doubleClicked!")
}
}
}
Note that you will need to check the state of the gesture recognizer in the action
method, even for a simple gesture like a click, as it will first have the state
.Began and then .Ended.
Why would you use gesture recognizers over NSResponder’s mouse event
methods? In an application with rich mouse event handling, the mouse event
methods can become quite complicated. Gesture recognizers make it easier to
segment code for different kinds of gestures. Another strong advantage is that
gesture recognizers can be used in a controller – without subclassing NSView.
Challenge: NSBezierPath-based Hit Testing
The hit testing in this application is rather primitive. The user can click outside
the rounded rect, but if they are still inside the rectangle, it will still register a hit.
Use NSBezierPath’s containsPoint(_:) method to make the hit testing
completely accurate.
Challenge: A Drawing App
Create a new document-based application that allows the user to draw ovals in
arbitrary locations and sizes. NSBezierPath has the following initializer:
init(ovalInRect: NSRect) -> NSBezierPath
If you are feeling ambitious, add the ability to save and read files.
If you are feeling extra ambitious, add undo capabilities.
19
Keyboard Events
When the user types, where are the corresponding events sent? First, the window
manager gets the event and forwards it to the active application. The active
application forwards the keyboard events to the key window. (This window is
what most users call the “active window,” but Cocoa developers call it the key
window because it receives keyboard events.) The key window forwards the
events to the active view.
Which view is the active one? Each window has an outlet called firstResponder
that points to one view of that window. That view is the active view for that
window. For example, when you click on a text field, it becomes the
firstResponder of that window (Figure 19.1).
When the user tries to change the firstResponder to another view (by tabbing or
clicking the other view), the views go through a certain ritual before the
firstResponder outlet is changed. First, the view that may become the
firstResponder is asked whether it accepts first-responder status. A return of NO
means that the view is not interested in keyboard events. For example, you
cannot type into a slider, so it refuses to accept first-responder status.
If the view does accept first-responder status, the view that is currently the first
responder is asked whether it resigns its role as the first responder. If the editing
is not done, the view can refuse to give up first-responder status. For example, if
the user had not typed in his or her entire phone number, the text field could
refuse to resign this status. Finally, if the current first responder resigns, the view
that has accepted first-responder status is notified. Often, this triggers a change
in its appearance (Figure 19.2).
Figure 19.2 Becoming the first responder
Note that each window has its own first responder. Several windows may be
open, but only the first responder of the key window gets the keyboard events.
NSResponder
Let’s look at the following property and methods that are inherited from
NSResponder:
var acceptsFirstResponder: Bool { get }
func resignFirstResponder() -> Bool
func becomeFirstResponder() -> Bool
func keyDown(theEvent: NSEvent)
func keyUp(theEvent: NSEvent)
func flagsChanged(theEvent: NSEvent)
NSEvent
We discussed NSEvent in terms of mouse events in the previous chapter. Here are
some of the properties commonly used when getting information about a
keyboard event:
var characters: String! { get }
var ARepeat: Bool { get }
var keyCode: UInt16 { get }
var modifierFlags: NSEventModifierFlags { get }
Adding Keyboard Input to DieView
Open the Dice project from Chapter 18. Although it would be cheating, users
would like to be able to set the number on the face of the die by typing a number
from one to six.
Here are the names of the global variables for the most commonly used
attributes, the type of object they correspond to, and their default values. (A
complete list can be found in NSAttributedString.h.)
Table 20.1
Global Variable Name Corresponds to Default Value
NSFontAttributeName A font object 12-point Helvetica
NSForegroundColorAttributeName A color Black
An
NSParagraphStyleAttributeName NSParagraphStyle
Standard paragraph
object style
The same as the
NSUnderlineColorAttributeName A color
foreground
0 (which means no
NSUnderlineStyleAttributeName A number
underline)
0 (which means no
NSSuperscriptAttributeName A number superscripting or
subscripting)
An NSShadow
NSShadowAttributeName nil (no shadow)
object
The easiest way to create attributed strings is from a file. NSAttributedString
can read and write the following file formats:
A string: Typically from a plain-text file.
RTF: Rich Text Format is a standard for text with multiple fonts and colors.
In this case, you will read and set the contents of the attributed string with
an instance of NSData.
RTFD: This is RTF with attachments and images.
HTML: The attributed string can do basic HTML layout, but you probably
want to use the WebView for best quality.
Word: The attributed string can read and write simple .doc files.
OpenOffice: The attributed string can read and write simple .odt files.
When you read a document in, you may want to know some things about it, such
as the paper size. If you supply a place where the method can put a pointer to a
dictionary, the dictionary will have all the extra information that it could get
from the data. For example:
let rtfUrl = ... // Obtain an NSURL
var error: NSError?
let rtfData = NSData.dataWithContentsOfURL(rtfUrl,
options:
NSDataReadingOptions.DataReadingMappedIfSafe,
error: &error) // -> NSData!
if rtfData != nil {
var docAttrs: NSDictionary?
let rtfAttrStr = NSAttributedString(data: rtfData,
options: nil,
documentAttributes:
&docAttrs,
error: &error)
button.attributedTitle = rtfAttrStr
}
If you do not care about the document attributes, just supply nil.
Drawing Strings and Attributed Strings
Both NSString and NSAttributedString have methods that cause them to be
drawn onto a view. NSAttributedString has the following methods:
func drawAtPoint(point: NSPoint)
func drawInRect(rect: NSRect)
var size: NSSize { get }
NSString has analogous methods. With NSString, you need to supply a
dictionary of attributes to be applied for the entire string.
func drawAtPoint(point: NSPoint, withAttributes attrs:
[NSObject : AnyObject]!)
func drawInRect(rect: NSRect, withAttributes attrs:
[NSObject : AnyObject]!)
func sizeWithAttributes(attrs: [NSObject :
AnyObject]!) -> NSSize
Note that these are methods on NSString, not Swift’s String. To use them on
String objects you must simply cast to an NSString using as.
Drawing Text Die Faces
Open DieView.swift and jump to drawDieWithSize(_:). Add the following code
below the block that draws the die dots. The new code will run if intValue is not
in the range 1...6.
if intValue == 6 {
drawDot(0, 0.5) // Mid left/right
drawDot(1, 0.5)
}
}
else {
var paraStyle =
NSParagraphStyle.defaultParagraphStyle().mutableCopy()
as!
NSMutableParagraphStyle
paraStyle.alignment = .CenterTextAlignment
let font =
NSFont.systemFontOfSize(edgeLength * 0.5)
let attrs = [
NSForegroundColorAttributeName:
NSColor.blackColor(),
NSFontAttributeName: font,
NSParagraphStyleAttributeName:
paraStyle ]
let string = "\(intValue)" as NSString
string.drawInRect(dieFrame,
withAttributes: attrs)
}
}
}
Run the program and try typing in 7, 8, or 9. It works! But there is a minor issue:
the text is not perfectly centered within the die face. You could fix this by
offsetting dieFrame, but there is a better way: the size APIs on NSString.
Your goal is to find the actual size of the string before it is drawn and change the
positioning so it will be exactly in the center. As a software developer you have
a few different options for implementing this:
a few different options for implementing this:
1. Inline in the method where it is needed.
Effective, but it would clutter up the code in that method.
2. Add a method to the class to find the correct rect.
1 point for encapsulation, but your options for reuse are limited to instances
of this class.
3. Create a new method on NSString that draws the string centered.
Excellent solution! It sets you up perfectly for code reuse down the road.
But how can you add methods to NSString? The answer is extensions.
Extensions
Extensions are a Swift language feature that is based on categories in Objective-
C. Extensions allow you to add methods to any class within your application.
These methods are available to you alone.
Without extensions you might try to add a method to String by subclassing it.
The problem with subclassing is that you can only enjoy that method on
instances that you yourself create of your special subclass. Extensions are
available on all instances of the extended class.
The only significant limitation of extensions is that you cannot add stored
properties with them. Computed properties are fine, but it is not presently
possible to add new storage to a class using them.
Let’s create an extension for drawing text centered in a rectangle. In Xcode, right-
click on the yellow Dice folder and select New File. Choose Swift File from the list
and name it NSString+Drawing. Add an extension to the file as follows:
import Foundation
import Cocoa
extension NSString {
}
Now use the extension in DieView.swift:
let string = "\(intValue)" as NSString
string.drawInRect(dieFrame,
withAttributes: attrs)
string.drawCenteredInRect(dieFrame,
attributes: attrs)
Run the application and press 8. The text should be perfectly centered in the die
face. You now have a handy method that you can reuse anytime you need to
draw centered text. Extensions are a great way to add features to framework
classes that you wish the designers had thought of. You can even use them as a
way to group functionality within your own classes.
Getting Your View to Generate PDF Data
All the drawing commands can be converted into PDF by the AppKit
framework. The PDF data can be sent to a printer or to a file. Note that a PDF
will always look as good as possible on any device, because it is resolution
independent.
You have already created a view that knows how to generate PDF data to
describe how it is supposed to look. Getting the PDF data into a file is really
quite easy. NSView has the following method:
func dataWithPDFInsideRect(rect: NSRect) -> NSData
This method creates a data object and then calls drawRect(_:). The drawing
commands that would usually go to the screen instead go into the data object.
Once you have this data object, you simply save it to a file.
Open DieView.swift and add a method that will create a Save panel as a sheet.
@IBAction func savePDF(sender: AnyObject!) {
let savePanel = NSSavePanel()
savePanel.allowedFileTypes = ["pdf"]
savePanel.beginSheetModalForWindow(window!,
completionHandler: {
[unowned savePanel] (result) in
if result == NSModalResponseOK {
let data =
self.dataWithPDFInsideRect(self.bounds)
var error: NSError?
let ok = data.writeToURL(savePanel.URL!,
options:
NSDataWritingOptions.DataWritingAtomic,
error: &error)
if !ok {
let alert = NSAlert(error: error!)
alert.runModal()
}
}
})
}
Open MainMenu.xib. Drag a new Menu Item from the Library onto the File menu.
Relabel it Save To PDF.... (You may delete all the other menu items from the menu,
if you wish.) Control-drag from the Save To PDF... menu item to the First Responder
placeholder. In the connection panel that appears, select the savePDF: method
(Figure 20.3).
Figure 20.3 Connect menu item
Run the application. You should be able to generate a PDF file and view it in
Preview (Figure 20.4). Note that the focused DieView is printed. This works
automatically thanks to First Responder and the responder chain, which you will
learn about in Chapter 21.
Figure 20.4 Completed application
For the More Curious: NSFontManager
Sometimes, you will find a font that is good but would be perfect if it were bold
or italicized or condensed. NSFontManager can be used to make this sort of
conversion. You can also use a font manager to change the size of the font.
For example, imagine that you have a font and would like a similar font but
bold. Here is the code:
let fontManager = NSFontManager.sharedFontManager()
let boldFont = fontManager.convertFont(someFont,
toHaveTrait:
NSFontTraitMask.BoldFontMask)
Challenge: Color Text as SpeakLine Speaks It
In Chapter 6 and Chapter 7, you built SpeakLine. Revisit the app and make it give
visual feedback to the user. As the speech synthesizer speaks, colorize the word
that is being spoken.
You will need to implement the following method from
NSSpeechSynthesizerDelegate:
optional func speechSynthesizer(sender:
NSSpeechSynthesizer,
willSpeakWord
characterRange: NSRange,
ofString string:
String!)
The first argument, as it always is, is the object being helped – in this case, the
speech synthesizer. The third argument is the string the speech synthesizer is
speaking. The second argument is the one you will need to use. It is an NSRange
which describes which characters in the string are currently being spoken.
Use this range along with the NSAttributedString API to create an attributed
string in which the words in the string being spoken are changed to a different
color.
Here is how you create an attributed string in which some range of characters
from an existing string is green:
let range: NSRange = ...
let theString: String = ...
let attributedString =
NSMutableAttributedString(string: theString)
attributedString.addAttribute(NSForegroundColorAttributeName,
value:NSColor.greenColor(),
range: characterRange)
You will need to read and write the textField’s attributedStringValue property
(which it inherits from NSControl).
Finally, you will need to enable and disable the text field so that the user cannot
change the text while playback is happening. This allows text coloring to work
change the text while playback is happening. This allows text coloring to work
properly.
func updateTextField() {
if speechSynth.speaking {
textField.enabled = false
} else {
textField.enabled = true
}
}
21
Pasteboards and Nil-Targeted Actions
A process called the pasteboard server (/usr/sbin/pboard) runs on your Mac.
Applications use the NSPasteboard class to write data into that process and to
read data from that process. The pasteboard server makes copying, cutting, and
pasting between applications possible.
An application can copy the same data onto the pasteboard in several formats.
For example, an image can be copied onto the pasteboard as a PDF document
and as a PNG image. Then the application that reads the data can choose the
format that it likes most. The pasteboard uses UTIs to identify the various types
used on the pasteboard.
Multiple items can be written to the pasteboard at once, each with its own set of
representations, or types. For example, multiple URLs can be copied from Finder.
When putting data on the pasteboard, an application typically clears the
pasteboard and then writes one or more objects directly to the pasteboard. Each
of those objects forms an individual item on the pasteboard. The objects must
conform to a pasteboard-writing protocol, which supplies the data. The data for
those items is immediately copied to the pasteboard.
The receiving application will then ask the pasteboard for an array of objects. It
supplies an array of classes along with this request, which enables the pasteboard
to provide the richest representations available for each item.
Data can also be passed via the pasteboard lazily. To do so, a class declares the
data it will provide and then promises to provide that data when asked to do so
by means of a delegate method in the future. We will talk about lazy copying at
the end of the chapter.
Apple also provides APIs to work with the pasteboard on a per type basis, which
may be useful if your application requires very fine control of the pasteboard.
Multiple pasteboards are available. There is a pasteboard called the general
pasteboard, for copy and paste operations, and another for drag-and-drop tasks.
One pasteboard stores the last string that the user searched for, another copies
rulers, and another copies fonts.
In this chapter, you will add cut, copy, and paste capabilities to your DieView.
First, you will implement the methods that will read from and write to the
pasteboard. Then we will discuss how those methods get called.
NSPasteboard
The NSPasteboard class acts as an interface to the pasteboard server. Here are
some of the commonly used methods of NSPasteboard:
class func generalPasteboard() -> NSPasteboard!
init(name: String!) -> NSPasteboard
func clearContents() -> Int
func writeObjects(objects: [AnyObject]!) -> Bool
func readObjectsForClasses(classArray: [AnyObject]!,
options: [NSObject :
AnyObject]!) -> [AnyObject]!
Note that UTIs are not used directly in any of the preceding methods. Instead,
they are class focused.
NSPasteboardItem, which itself conforms to NSPasteboardReading and
NSPasteboardWriting, allows you to work much more closely with the
pasteboard contents using UTIs. Here are some of the more commonly used
methods on NSPasteboardItem:
func setDataProvider(dataProvider:
NSPasteboardItemDataProvider!,
forTypes types: [AnyObject]!) ->
Bool
func setData(data: NSData!, forType type: String!) ->
Bool
func setString(string: String!, forType type: String!)
-> Bool
func setPropertyList(propertyList: AnyObject!, forType
type: String!) -> Bool
var types: [AnyObject]! { get }
func availableTypeFromArray(types: [AnyObject]!) ->
String!
func dataForType(type: String!) -> NSData!
func stringForType(type: String!) -> String!
func propertyListForType(type: String!) -> AnyObject!
Add Cut, Copy, and Paste to Dice
Let’s add cut, copy, and paste capability to Dice. You will create methods named
cut:, copy:, and paste: in the DieView class. To make these methods easier to
write, you will first create methods for putting data onto and reading data off of a
pasteboard. Reopen your Dice project and in DieView.swift, add these methods to
DieView:
// MARK: - Pasteboard
4. If the main window is different from the key window, it then goes through
the same series for the main window: the firstResponder of the main
window and its responder chain (including the main window itself), the
main window’s delegate, and the NSWindowController and the NSDocument
object for the main window.
5. The instance of NSApplication.
6. The delegate of the NSApplication.
7. The NSDocumentController.
In OS X 10.10 and up, NSViewController also participates in the responder
chain. It is its view’s next responder.
This series of objects represents the responder chain, and Figure 21.1 presents an
example. The numbers indicate the order in which the objects would be asked
whether they respond to the nil-targeted action.
Figure 21.1 An example of the order in which responders get a chance to
respond
Note that in document-based applications (such as RaiseMan), the NSDocument
object gets a chance to respond to the nil-targeted action. The object receives
the messages from the following menu items: Save, Save As..., Revert To Saved, Print...,
and Page Setup....
Custom UTIs
At some point, you will want to use the pasteboard for custom, application-
specific data. In such cases, you can simply use your own UTI. Custom UTIs
should take the form of a reverse DNS name, such as
com.bignerdranch.raiseman.person. You would then implement
NSPasteboardWriting and NSPasteboardReading on your custom object or use
NSPasteboardItem as an abstraction layer.
Note that custom UTIs do not need to be exported (using Info.plist) unless
they are to be used by other applications. If they are exported, they must
conform to public.data.
For the More Curious: Lazy Copying
An application can implement copying to a pasteboard in a lazy manner. For
example, imagine a graphics application that copies large images to the
pasteboard in several formats: PNG, TIFF, PDF, and so on. You can imagine
that copying all these formats onto the pasteboard would be hard on the
application and the pasteboard server. Instead, such an application might do a
lazy copy. That is, the application will declare all the types that it could put on
the pasteboard but will put off copying the data until another application asks for
it.
Essentially, the application puts an “IOU” (instead of the data) on the pasteboard
and gives an object that will provide the data when it is needed. When another
application actually asks for the data, the pasteboard server calls back for the
data.
You use an NSPasteboardItem to create this IOU object:
let pboard = NSPasteboard.generalPasteboard()
pboard.clearContents()
let item = NSPasteboardItem()
item.setDataProvider(self, forTypes:...)
pboard.writeObjects( [item] )
Then implement pasteboard(_:item:provideDataForType:):
func pasteboard(_ pasteboard: NSPasteboard!, item
item: NSPasteboardItem!,
provideDataForType type: String!)
{
item.setData(..., forType:type)
}
When another application needs the data, this method is called. At that point, the
application must copy the data it promised to the supplied pasteboard item.
As you can imagine, a problem will arise if the pasteboard server asks for the
data after the application has terminated. When the application is terminating, if
it has an IOU currently on the pasteboard, it will be asked to supply all the data
promised before terminating. Thus, it is not uncommon for an IOU data provider
to be sent pasteboard(_:item:provideDataForType:) several times while the
application is in the process of terminating.
The trickiest part of lazy copies is that when users copy data to the pasteboard
and later paste it into another application, they do not want the most recent state
of the data. Rather, users wants the data the way it was when they copied it.
When implementing a lazy copy, most developers will take some sort of a
snapshot of the information when declaring the types. When providing the data,
the developer will copy the snapshot, instead of the current state, onto the
pasteboard.
Of course, when the user does a copy somewhere else, your object will no longer
be responsible for keeping the snapshot.
optional func pasteboardFinishedWithDataProvider(_
pasteboard: NSPasteboard!)
If you implement this method, it will be called when you are no longer
responsible for keeping the snapshot.
Challenge: Write Multiple Representations
You are putting the string onto the pasteboard. Create the PDF for the view and
put that on the pasteboard, too. Now you will be able to copy the image of the
die into graphics programs. Test it using Preview’s New from Clipboard menu item.
(Do not break the string’s copy and paste: Put both the string and the PDF onto
the pasteboard.) Hint: You will need to create an NSPasteboardItem.
Challenge: Menu Item
In the RaiseMan project, add a menu item that triggers the removeEmployee(_:)
method in Document.
22
Drag-and-Drop
Drag-and-drop is little more than a flashy copy-and-paste operation. When the
drag starts, some data is copied onto the dragging pasteboard. When the drop
occurs, the data is read off the dragging pasteboard. The only thing that makes
this technique trickier than copy-and-paste is that users need feedback: an image
that appears as they drag, a view that becomes highlighted when they drag into
it, and maybe a big gulping sound when they drop the image.
The user can perform several different operations by dragging from one
application to another, or even between views in the same application. These
operations are represented by the NSDragOperation type:
struct NSDragOperation : RawOptionSetType {
init(_ value: UInt)
static var None: NSDragOperation { get }
static var Copy: NSDragOperation { get }
static var Link: NSDragOperation { get }
static var Generic: NSDragOperation { get }
static var Private: NSDragOperation { get }
static var Move: NSDragOperation { get }
static var Delete: NSDragOperation { get }
static var Every: NSDragOperation { get }
}
For example, nothing may happen (.None), a copy of the data may be created
(.Copy), or the data may be moved (.Move).
Both the source and the destination must agree on the operation that will occur
when the user drops the image.
When you add drag-and-drop to a view, there are two distinct parts of the
change:
1. Make it a drag source.
2. Make it a drag destination.
Let’s take these steps separately. First, you will make your view a drag source.
When that is working, you will make it a drag destination.
Make DieView a Drag Source
When you finish this section, you will be able to drag the die face off the
DieView and drop it into another DieView, or even into a text editor. It will look
like Figure 22.1.
Figure 22.1 Completed application
Starting a drag
To start a drag operation, you will use a method on NSView:
func beginDraggingSessionWithItems(items: [AnyObject],
event: NSEvent,
source:
NSDraggingSource) -> NSDraggingSession
This method takes an array of NSDraggingItem objects as its first argument. The
dragging item knows how to write itself to the pasteboard, the location it is being
dragged from, as well as how to provide an image for the item being dragged.
The event argument will be the event that was supplied to mouseDown(_:). The
source will generally be the view itself.
let dieFrame =
metricsForSize(bounds.size).dieFrame
let pointInView =
convertPoint(theEvent.locationInWindow, fromView: nil)
pressed = dieFrame.contains(pointInView)
}
You will also need to create an image to drag. You can draw on an image just as
you can on a view. Recall from Chapter 17 that drawing in Cocoa requires
configuring the graphics context for the drawing destination. Therefore, in order
to draw into an image you will need an API that configures the drawing context
so that drawing appears on the image and not the screen.
NSImage provides an initializer for this purpose, which takes a closure as an
argument. When the image is asked to draw itself, the closure will be evaluated.
This means that the image can be appropriately sized for the particular drawing
context. As a simple example, here is how to create a solid green image:
let imageSize = ...
let greenImage = NSImage(size: imageSize, flipped:
false) { (imageBounds) in
NSColor.greenColor.set()
NSBezierPath.fillRect(imageBounds)
}
The most appropriate place to begin the dragging session is in mouseDragged(_:)
in DieView. Add the following code:
override func mouseDragged(theEvent: NSEvent) {
println("mouseDragged location: \
(theEvent.locationInWindow)")
let downPoint = mouseDownEvent!.locationInWindow
let dragPoint = theEvent.locationInWindow
pressed = false
let draggingFrameOrigin =
convertPoint(downPoint, fromView: nil)
let draggingFrame = NSRect(origin:
draggingFrameOrigin, size: imageSize)
.rectByOffsetting(dx: -
imageSize.width/2, dy: -imageSize.height/2)
beginDraggingSessionWithItems([item], event:
mouseDownEvent!, source: self)
}
}
First the magnitude of the drag is checked using hypot. This helps to prevent
accidental drags. Next, if the die has a non-nil intValue, the NSImage is created,
the dragging frame calculated, and the NSDraggingItem created as well. Note that
the NSDraggingItem has a closure property, imageComponentsProvider. This API
allows you to provide both image and text (label) components that will be drawn
by the system to represent the item as it is being dragged.
That is it. Run the application. You should be able to drag a die face off the view
and into any text editor, resulting in a number. (Try dragging it into Xcode.)
After the drop
When a drop occurs, the drag source will be notified if you implement the
following method:
func draggingSession(session: NSDraggingSession,
endedAtPoint screenPoint: NSPoint,
operation: NSDragOperation)
For example, to make it possible to clear the DieView by dragging the die face to
the trash can in the dock, update your implementation of this method:
func draggingSession(session: NSDraggingSession,
sourceOperationMaskForDraggingContext context:
NSDraggingContext)
-> NSDragOperation {
return .Copy
return .Copy | .Delete
}
Then implement draggedImage(_:endedAt:operation:)
func draggingSession(session: NSDraggingSession,
endedAtPoint screenPoint: NSPoint,
operation: NSDragOperation) {
if operation == .Delete {
intValue = nil
}
}
Run the application. Drag a die face into the trash. It should disappear from the
view.
Make DieView a Drag Destination
There are several parts to being a drag destination. First, you need to declare
your view a destination for the dragging of certain types. NSView has a method
for this purpose:
func registerForDraggedTypes(newTypes: [AnyObject])
You typically call this method in your initWithFrame: method.
Then you need to implement six methods. (Yes, six!) All six methods have the
same argument: an object that conforms to the NSDraggingInfo protocol. That
object provides the dragging pasteboard. The six methods are invoked as
follows:
1. As the image is dragged into the destination, the destination is sent a
draggingEntered(_:) message. Often, the destination view updates its
appearance. For example, it might highlight itself.
2. While the image remains within the destination, a series of
draggingUpdated(_:) messages are sent. Implementing
draggingUpdated(_:) is optional.
func commonInit() {
self.registerForDraggedTypes([NSPasteboardTypeString])
}
Add highlighting
To signal the user that the drop is acceptable, your view will highlight itself. Add
a highlightForDragging property to DieView:
var highlightForDragging: Bool = false {
didSet {
needsDisplay = true
}
}
Now you are going to add highlighting to drawRect(_:). The class NSGradient
makes it easy to draw with gradients. In this case, you are going to draw a radial
gradient: white in the center and fading into the backgroundColor.
override func drawRect(dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGrayColor()
backgroundColor.set()
NSBezierPath.fillRect(bounds)
drawDieWithSize(bounds.size)
if highlightForDragging {
let gradient = NSGradient(startingColor:
NSColor.whiteColor(),
endingColor:
backgroundColor)
gradient.drawInRect(bounds,
relativeCenterPosition: NSZeroPoint)
}
else {
drawDieWithSize(bounds.size)
}
}
To play with timers, you will modify the Dice application to “roll” in an animated
fashion, showing the various faces of the die before settling on one.
Note that this is a rather primitive form of animation; you will learn more
sophisticated animation APIs when we cover Core Animation in Chapter 33.
NSTimer-based Animation
Presently, when the user double-clicks on a DieView, the class’s implementation
of mouseUp() calls randomize() to randomly change its intValue. You will
change mouseUp() to call a new function, roll(), which will start an animation
by creating an instance of NSTimer. The timer will be repeating, randomizing the
die value each time it fires. Once a suitable number of randomizations have
occurred the timer will be stopped.
Here is the API to create a scheduled timer:
class func scheduledTimerWithTimeInterval(ti:
NSTimeInterval,
target aTarget: AnyObject,
selector aSelector: Selector,
userInfo: AnyObject?,
repeats yesOrNo: Bool) -> NSTimer
Notice that this is a class method on NSTimer. The initializers for NSTimer create a
timer that is not scheduled – and a timer does not fire unless it is scheduled.
Thus, scheduledTimerWithTimeInterval is easier to use an initializer, because
you do not need to separately schedule the timer.
Start by adding a property to DieView:
var rollsRemaining: Int = 0
This property will help determine when to stop animating the die. In order to
stop the timer, you will call invalidate() on it.
Next, implement the roll() method, which starts the timer, and rollTick(_:),
the timer’s action method.
func roll() {
rollsRemaining = 10
NSTimer.scheduledTimerWithTimeInterval(0.15,
target: self,
selector:
Selector("rollTick:"),
userInfo: nil,
repeats: true)
window?.makeFirstResponder(nil)
}
In order to get data in and out of the window being presented as a sheet, you will
create an NSWindowController subclass, along with a XIB to provide the window
itself. Bindings will make the task of tying the sheet’s contents to the window
controller fairly simple, and you will use target/action to react to the button
clicks.
Figure 24.2 Object diagram
Create the Window Controller
Create a new NSWindowController subclass called
ConfigurationWindowController along with a corresponding XIB. Navigate to
File → New → File.... In the presented sheet, select Source from the OS X section and
select Cocoa Class from the resulting options. Click Next. Enter
ConfigurationWindowController in the Class field, and enter
NSWindowController in the Subclass of field. Ensure that the checkbox labeled Also
create XIB file for user interface is checked, and that Swift is the selected Language.
class ConfigurationWindowController:
NSWindowController {
private dynamic var color: NSColor =
NSColor.whiteColor()
private dynamic var rolls: Int = 10
struct DieConfiguration {
let color: NSColor
let rolls: Int
class ConfigurationWindowController:
NSWindowController {
Change the title of the menu item to “Configure Die”. Control-drag from the
menu item to the First Responder. Set the action to be showDieConfiguration:
(Figure 24.4).
Figure 24.4 Connecting the menu item
Lay Out the Interface
Open ConfigurationWindowController.xib, select the window, and switch to
attributes inspector. Uncheck Resize to disable resizing for the window. Also
uncheck Visible At launch.
That last step is an important step to remember when working with sheets: if you
do not uncheck Visible At Launch, the window will appear before it is presented as a
sheet, which will make your application look buggy and broken.
Figure 24.5 Inspecting new window
Drag out onto the window two labels, two buttons, a color well, a text field, and
a stepper. Arrange everything as in Figure 24.6.
Figure 24.6 ConfigurationWindowController controls arranged
Bind the Value of the color well to the File's Owner’s color as shown in Figure 24.7.
Figure 24.7 Binding the color well’s Value to File's Owner’s color
Next bind the text field’s Value to File's Owner’s rolls (Figure 24.8).
Figure 24.8 Binding the text field’s Value to File's Owner’s rolls
Besides giving color a default value, there are two other uses of
NSColor.whiteColor(). You need to replace those uses with uses of the color
property.
First replace the use in drawRect(_:):
override func drawRect(dirtyRect: NSRect) {
let backgroundColor = NSColor.lightGrayColor()
backgroundColor.set()
NSBezierPath.fillRect(bounds)
if highlightForDragging {
let gradient = NSGradient(startingColor:
NSColor.whiteColor(),
endingColor:
backgroundColor)
let gradient = NSGradient(startingColor:
color,
endingColor:
backgroundColor)
gradient.drawInRect(bounds,
relativeCenterPosition: NSZeroPoint)
}
else {
drawDieWithSize(bounds.size)
}
}
Next, replace the use in drawDieWithSize(_:):
shadow.set()
NSColor.whiteColor().set()
color.set()
NSBezierPath(roundedRect: dieFrame,
xRadius: cornerRadius, yRadius:
cornerRadius).fill()
Finally, change the implementation of roll() to use numberOfTimesToRoll:
func roll() {
rollsRemaining = 10
rollsRemaining = numberOfTimesToRoll
NSTimer.scheduledTimerWithTimeInterval(0.15,
target: self,
selector:
Selector("rollTick:"),
userInfo: nil,
repeats: true)
window?.makeFirstResponder(nil)
}
Present the Sheet
When the user clicks the Configure Die menu item, the sheet should be presented in
order to configure the currently selected DieView.
How will you determine which DieView to change? Because DieView accepts first
responder, you can easily determine which, if any die view has the focus (and
blue highlight) by checking the window’s firstResponder.
Open MainWindowController.swift and fill out the stub for
showDieConfiguration(_:).
// MARK - Actions
var configurationWindowController:
ConfigurationWindowController?
window.beginSheet(windowController.window!,
completionHandler: { response in
// The sheet has finished. Did the user
click 'OK'?
if response == NSModalResponseOK {
let configuration =
self.configurationWindowController!.configuration
dieView.color = configuration.color
dieView.numberOfTimesToRoll =
configuration.rolls
}
// All done with the window controller.
self.configurationWindowController = nil
})
configurationWindowController =
windowController
}
}
Note that you are presenting the ConfigurationWindowController’s window on
top of the MainWindowController’s window.
The sheet should end when the user clicks on either button in the configuration
window. Fill out the stubs for okayButtonClicked(_:) and
cancelButtonClicked(_:) that you added to ConfigurationWindowController:
@IBAction func okayButtonClicked(button: NSButton) {
println("OK clicked")
window?.endEditingFor(nil)
dismissWithModalResponse(NSModalResponseOK)
}
func dismissWithModalResponse(response:
NSModalResponse) {
window!.sheetParent!.endSheet(window!,
returnCode: response)
}
When either button is clicked, dismissWithModalResponse(_:) will be called,
which will trigger MainWindowController to dismiss the sheet. Note, the
ConfigurationWindowController’s window’s sheetParent is the
MainWindowController’s window. Remember, the value for returnCode that is
passed to endSheet(_:returnCode:) is the value that the completion handler
(which you provided to beginSheet(_:completionHandler:)) will be called with.
Notice that okayButtonClicked(_:) calls endEditingFor(_:) on the window. By
default, Cocoa bindings do not update until editing ends for the control. In the
case of a text field, this is when it is no longer first responder, such as when the
user clicks on a different text field. In order to make sure that the user’s changes
are applied (and carried through to the properties on the window controller, in
this case rolls), you must call endEditingFor(_:).
Run your application and click on one of the dice. Bring up the sheet, adjust the
die, and dismiss the sheet. If none of the dice are first responder, the sheet will
not be presented because the optional binding (if let) and conditional casting
(as? DieView) will fail.
Modal Windows
When a sheet is active, the user is prevented from interacting with the window
from which it is presented. Modal windows are similar, except that they are
presented independently of the other windows and they block user input for the
rest of the application.
To present a window modally, use the runModalForWindow(_:) method on
NSApplication:
func showModalWindow() {
let windowController = CriticalWindowController()
let app = NSApplication.sharedApplication()
let returnCode =
app.runModalForWindow(windowController.window!)
if returnCode ==
CriticalWindowController.returnAccept {
...
}
}
This method will block, and only events destined for the modal window will be
processed; clicking on the menu and other windows will do nothing. When you
are ready to dismiss the modal window, call stopModalWithCode(_:), also on
NSApplication object, with a return code indicating the result of the modal
window:
class CriticalWindowController: NSWindowController {
static let returnAccept = 1
...
@IBAction func dismiss(sender: NSButton) {
let app = NSApplication.sharedApplication()
app.stopModalWithCode(CriticalWindowController.returnAccept)
}
}
}
At that point, runModalForWindow(_:) will return and the calling code will
continue execution. The value returned will be the return code passed to
stopModalWithCode(_:).
Encapsulating Presentation APIs
Cocoa’s sheet and modal window presentation APIs are very flexible: you can
supply your own Int as the modal response or return code. Unfortunately this
leaves room for error and confusion since it can be unclear how to map an Int
back to something meaningful. (Did the user click Accept or Cancel?) By
tucking the sheet or modal window API usage into your window controller you
can add some safety and grace to your use of them.
Consider the following code for the fictional CriticalWindowController:
class CriticalWindowController: NSWindowController {
enum ModalResult: Int {
case Accept
case Cancel
}
func runModal() -> ModalResult {
let app = NSApplication.sharedApplication()
let returnCode =
app.runModalForWindow(window!)
if let result = ModalResult(rawValue:
returnCode) {
return result
}
else {
fatalError("Failed to map \(returnCode) to
ModalResult")
}
}
@IBAction func dismiss(sender: NSButton) {
let app = NSApplication.sharedApplication()
app.stopModalWithCode(ModalResult.Accept.rawValue)
}
}
}
Now, presenting a CriticalWindowController modally is much more clear, with
no guesswork about the meaning of the Int return code:
func showModalWindow() {
let windowController = CriticalWindowController()
switch windowController.runModal() {
case .Accept:
...
case .Cancel:
break
}
}
You can use the same technique when presenting sheets.
Challenge: Encapsulate Sheet Presentation
In Dice, add a method to ConfigurationWindowController:
presentAsSheetOnWindow(_:completionHandler:). The method’s parameter
should be the window the configuration sheet should be displayed on, and the
completion handler will be a closure of type (DieConfiguration?)->(Void). If
the user clicks OK, the DieConfiguration will be passed to the completion
handler; otherwise it will be nil to signal that nothing changed.
Here is how your method will look when it is called:
windowController.presentAsSheetOnWindow(window,
completionHandler: {
configuration in
if let configuration = configuration {
dieView.color = configuration.color
dieView.numberOfTimesToRoll =
configuration.rolls
}
})
Challenge: Add Menu Item Validation
Add menu item validation to disable the Configure Die menu item if no DieView is
presently first responder. If you need to, refer back to the section in Chapter 21.
One hint: the is operator returns true if the left operand is of the same type as
the right operand.
25
Auto Layout
Have you tried resizing the RaiseMan window? The table view and buttons are
steadfast in the face of your pointer. While their confidence is inspiring, as a user
you want the window and its contents to match your needs. As a developer you
want to build an app whose interface gracefully responds to resizing,
accommodating the smallest and the largest of layouts without looking, well,
broken.
As you learned in Chapter 17, views are positioned by their frame, which sets the
view’s position within its superview. By default, when you place a view in Xcode,
the view’s frame is static.
You could change your application’s layout dynamically by observing window
resizing notifications and programmatically reposition your views by setting
their frames, but that would be very tedious. Instead, you will use Auto Layout.
What is Auto Layout?
Auto Layout uses constraints to determine where views within a window should
be positioned. Each view will have several constraints; those constraints describe
how the view should be positioned relative to other views or the window itself,
as well as the width and height of the view. For example:
The left edge of the scroll view should be the standard distance (20 points)
from the left edge of the window.
The Add Employee and Remove buttons should always be the same width.
The left edge of the Remove button should always match the left edge of the
Add Employee button.
The width of the scroll view should always be at least 200 points.
Start by adding a constraint from the bottom of the scroll view to the bottom of
the window: In the top section of the popover, select the disclosure arrow for the
bottom position constraint and select Use Standard Value as in Figure 25.2.
Figure 25.2 Adding the first constraint
Once you have told Interface Builder to use the Standard Value for the constraint from
the bottom of the scroll view to the bottom of the window, it toggles the I-beam
corresponding to the field enabled as in Figure 25.3.
Figure 25.3 The first constraint is ready to be added
Repeat this process for the left and top constraints of the scroll view, but not the
right. Your popover should look like Figure 25.4.
Figure 25.4 The three constraints are ready to be added
Click the Add 3 Constraints button at the bottom of the popover. The three
constraints that you added are now shown in the window on the canvas I-beams
extending from the scroll view to the edges of the window (Figure 25.5). A blue
I-beam indicates that the constraint is fine, while orange indicates that the
constraint is ambiguous. In Figure 25.5 the left I-beam is orange because Auto
Layout does not know how wide the scroll view should be. You will fix this
shortly.
Figure 25.5 Superview constraints added to the scroll view
In the canvas, select the vertical I-beam below the scroll view and open the
attributes inspector (Figure 25.6). The type of this constraint is shown at the top
of the inspector: Vertical Space Constraint. Below it are the two items that are being
constrained and information about their relationship. This tells you that the
vertical spacing between the bottom of the scroll view and its superview are
constrained to be equal to the standard spacing. In other words, it is pinned to the
bottom of the view. A priority of 1,000 indicates that this constraint is required.
Figure 25.6 Vertical space constraint attributes
If you planned to add constraints at runtime you might add a placeholder, design
time constraints, and check Remove at build time.
Try entering a value into the Constant text field (you will have to press Return to
end editing). Notice that the scroll view moves accordingly. Drag the scroll view
around and observe how Interface Builder changes how the constraints are displayed:
it is demonstrating how the layout of the scroll view in the XIB fails to match up
with the constraints, and how the layout will be altered when the app runs!
Before we go on, put the scroll view back in its place on the left hand side of the
window: Select the vertical I-beam below the scroll view again, switch to the
attributes inspector, and set the Constant back to Standard, using the disclosure
arrow.
Let’s finish by setting up constraints between the Add Employee button and the
content view of the window. Select the Add Employee button, click to summon
the Add New Constraints popover, and add two standard constraints: one on the top
and one on the right (Figure 25.7).
Figure 25.7 Adding constraints between the Add Employee button and its
superview
While you have not yet finished adding constraints to RaiseMan, you are done
adding constraints between the top-level controls and their superview, the
window’s content view (Figure 25.8). You might be wondering about the Remove
button. In the next section, you will add constraints between it and other controls
in the window. As you will see there, it is not necessary to constrain it to its
superview.
Figure 25.8 All superview constraints added
Next, you will constrain the text fields embedded in the table view to be
appropriately hugging their superviews, the table cell views. Select the text field
within the Name column using either the document outline or the view hierarchy
popover.
Click . In the Add New Constraints popover, click on each of the four of the I-
beams in turn to constrain the text field to its superview on all four sides. Make
sure that the top and bottom distances are both 0 and that the left and right
distances are both 2. Click Add 4 Constraints (Figure 25.9).
Figure 25.9 Adding superview constraints to Name text field
Repeat the same process for the text field inside the Raise column.
These constraints keep the edges of the text fields close to the bounds of their
containing table cell views. Later, when your table view is resizing, the columns
will resize, and the table cell views will resize accordingly. These constraints
make the text fields grow and shrink with their table cell views.
Run the app. Try resizing the window. When you resize the window, the views
adapt to the changes… somewhat. Let’s take stock of what is working:
The scroll view’s height increases and decreases with the window’s so that
its top and bottom edges are always the standard distance from the top and
bottom of the window, respectively.
The Add Employee button stays in the top right of the window so its top and
right edges are always the standard distance from the top and right of the
window.
A few things are not working as you would probably like, however:
The scroll view’s width does not change.
The Add Employee button slides beneath the scroll view when the window gets
too skinny.
The Remove button does not move.
It is as if each of the views does not know that the others are there. Actually, that
is exactly the problem: there are no constraints between the sibling subviews.
Adding constraints between the siblings will enable them to respect each other’s
space.
You can also add constraints between views by Control-dragging. This can be
used to constrain horizontal and vertical spacing, depending on the direction of
used to constrain horizontal and vertical spacing, depending on the direction of
the drag. It can also be used to set views to have equal widths or heights.
To constrain the buttons to have the same width, Control-drag from the Remove
button to the Add Employee button and release the mouse. You will be presented
with a constraint type panel. Select Equal Widths (Figure 25.11).
Figure 25.11 Constraining the Remove button to be as wide as the Add
Employee button
Note: If you do not want to Control-drag to make this kind of constraint, you can
also use the Add New Constraints popover. To accomplish that, you would select the
two views you wanted to have equal widths, click , check the Equal Widths
checkbox, and click Add 1 Constraint.
Next, add the standard left constraint to the Add Employee button. Select the button,
click to present the Add New Constraints popover, select the I-bar to the left of
the view, and make the constraint’s constant be Standard by selecting Use Standard
Value from the button’s menu. Finally, add the constraint by clicking Add 1
Constraint. This will add a horizontal constraint between the Add Employee button and
the scroll view.
Up until this point, the document outline has been displaying an Auto Layout
warning indicator. The warning associated with this indicator is that the scroll
view’s clip view is misplaced. After pinning the left side of the Add Employee
button to the right side of the scroll view, you should see the Auto Layout
warning indicator in the document outline disappear. Interface Builder is recognizing
that the clip view is not misplaced. This is good news! Unfortunately it may
return: this is a long-running Xcode bug related to IB’s interaction with Auto
Layout. As long as you have the scroll view constrained, you will not need to
worry about a warning on the clip view.
Run the app and resize the window. You should now see the scroll view’s width
change as the window grows and shrinks. You should also see the buttons
behave in the way you would expect.
Size constraints
You have most of the resizing behavior now. However, the scroll view will
shrink almost to vanishing. To stop that from happening, you will give it a
minimum size using Auto Layout. This is a two-step process. You will start by
adding fixed width and height constraints. Then you will change the relation of
each from equal to greater than or equal.
Select the scroll view and open the Add New Constraints popover. Check the boxes
for Width and Height. The specific values are not important, but the height should
be greater than 100 and the width greater than 200. Click Add 2 Constraints
(Figure 25.12).
Figure 25.12 Adding height and width constraints to the scroll view
Currently, these constraints give the scroll view a fixed size. Instead, you want to
prevent the scroll view from getting smaller than it is shown in Interface Builder. To
do this, you need to make these width and height constraints require that the
view’s dimensions be greater than or equal to the constant, rather than strictly
equal.
Select the width constraint. One way to do this is to first select the scroll view
and then select the constraint that is drawn across its bottom. Alternatively, you
can select it from the document outline as shown in Figure 25.13.
Figure 25.13 Selecting the width constraint of the scroll view
With the width constraint selected, switch to the attributes inspector. Change the
Relation from Equal to Greater Than or Equal.
Repeat the same process for the scroll view’s height constraint.
Once you are done, Interface Builder will show you that the constraints now use the
greater-than-or-equal relation in two places. First, the greater-than-or-equal
symbol (≥) is displayed in the constraint’s listing in the document outline.
Second, when the constraints are visible in the canvas, the greater-than-or-equal
symbol will be drawn over their tops (Figure 25.14).
Figure 25.14 The two greater-than-or-equal size constraints on the scroll
view
Run the app and resize the window. You should now see the scroll view grow
and shrink appropriately. It will never get too small. And the text fields inside
the table view resize to fit the width of the column they are in. Pretty neat, right?
And you did all of this without writing any code.
Intrinsic Content Size
In the constraints that you have added, you at no point specified a minimum
width for the Add Employee button. However, when you try to shrink the width of
the window, the scroll view reaches its minimum width, but the Add Employee
button’s width stays the same! This is happening because Auto Layout is taking
the button’s intrinsic content size into consideration. In the case of a button, its
intrinsic content size is determined by its title text.
Auto Layout handles changes to intrinsic content size as well. If you changed the
Add Employee button’s title to something longer, the window would be laid out
again so that the button’s new title could be displayed fully. It would first shrink
the scroll view, and, if necessary, expand the window.
This is a pretty powerful feature. It makes the job of localizing your application
much easier, too. If you were localizing your English application for German,
you might have to make a number of text fields much wider, or do this sort of
size calculation yourself. Auto Layout handles the task for you.
Creating Layout Constraints
Programmatically
You have seen how constraints can be created in Interface Builder, but they can also
be added programmatically using the NSLayoutConstraint class. Above, you
created the constraint pinning the left side of the scroll view the standard
distance from the left side of its superview, the window’s content view, using the
Add New Constraints popover in IB. You would create an equivalent constraint
programmatically like this:
let scrollView: NSScrollView = ...
let superview = scrollView.superview!
let constraint = NSLayoutConstraint(item: scrollView,
attribute: .Leading,
relatedBy: .Equal,
toItem: superview,
attribute: .Leading,
multiplier: 1.0,
constant: 20.0)
superview.addConstraint(constraint)
This constraint specifies that the leading edge of the scroll view be 20 points
from the leading edge of the superview.
This raises another interesting aspect of Auto Layout: it allows you to express
your layout in terms of leading and trailing edges so that your layout works in
languages that do not read left-to-right like English. In English, the above
constraint would tie the left-hand side of the scroll view to the left-hand side of
its superview. In a right-to-left language, such as Arabic, however, the constraint
would tie the right-hand side of the scroll view to the right-hand side of its
superview.
The initializer
init(item:attribute:relatedBy:toItem:attribute:multiplier:constant:) is
very precise, but it certainly does not help with keeping your code concise.
Fortunately, the Cocoa engineers came up with something more succinct: the
visual format language.
Auto Layout constraints can be added and removed as needed at runtime, but
you can also animate layout changes to achieve a smooth transition. See Apple’s
Auto Layout Guide; for more information.
Visual Format Language
Start a new Cocoa Application project and call it AutoLabelOut. Leave Document Based
Application unchecked.
// Create a label:
let label = NSTextField(frame:
NSRect.zeroRect)
label.translatesAutoresizingMaskIntoConstraints =
false
label.stringValue = "Label"
//Give this NSTextField the styling of a
label:
label.backgroundColor = NSColor.clearColor()
label.editable = false
label.selectable = false
label.bezeled = false
let verticalConstraints =
NSLayoutConstraint.constraintsWithVisualFormat("V:|-
[textField]-|",
options:.allZeros,
metrics:nil,
views: ["textField" : textField])
NSLayoutConstraint.activateConstraints(verticalConstraints)
}
Whew. Let’s discuss the code above. You start by programmatically creating a
label and text field and adding them to the window.
We will skip over translatesAutoresizingMaskIntoConstraints for now and
discuss it later in the chapter.
After that, the fun part: you create the constraints themselves using a class
method on NSLayoutConstraint,
constraintsWithVisualFormat(_:options:metrics:views:). The most
interesting parameters are the first and last: the visual format and the views
dictionary.
Run your new app. Enter text into the text field and press Enter. The label should
resize to be large enough to display all the text you entered.
The visual format language allows you to describe the layout as ASCII art. A
visual format string creates an array of constraints for the views along one axis,
horizontal or vertical. Let’s parse through the visual format string:
|-[label]-[textField(>=100)]-|
The pipes represent the edges of the superview, in this case the window’s
content view. The square brackets represent views, referenced by name
(remember the dictionary?). So you can surmise that this set of constraints will
keep the label and textField next to each other, while pinning their edges to the
window.
How far will they be from the window, and from each other? When a dash is
used, Auto Layout uses whatever the standard spacing is for that relationship,
which is different for view-window and view-view. If you wanted a specific
number of points between the two views, say 9, you could specify that:
[label]-9-[textField].
The final portion of this string ([textfield(>=100)]) means that the textField’s
width should be greater than or equal to 100 points. Rather than providing a
concrete width for the text field, you could also specify that it be at least as wide
as the label: |-[label]-[textField(>=label)]-|.
Let’s look quickly at the second visual format string, V:|-[textField]-|. The
V: prefix simply indicates that this format string positions objects vertically,
from top to bottom. The default axis is horizontal. So textField will be pinned
at the standard distance from the top and bottom of the window.
There is more to the visual format language. While it is a very powerful tool,
there are some relationships which cannot be expressed with it, such as aspect
ratios between views (e.g., specifying that one view be twice the width of
another).
init(item:attribute:relatedBy:toItem:attribute:multiplier:constant:) is
necessary to create such constraints.
Does Not Compute, Part 1: Unsatisfiable
Constraints
Because constraints are expressed as mathematical expressions and Auto
Layout’s job is to solve the entire system together, you may sometimes run into
situations where Auto Layout cannot solve the system. Generally this will occur
if there are too many or too few constraints.
If there are too many constraints, or if the constraints conflict with each other,
we say that the constraints are unsatisfiable. If this happens, the Auto Layout
system will recognize it at runtime and print debug information to the console,
providing you with a list of the constraints which are in conflict. (If you added
your unsatisfiable constraints in Interface Builder, it would inform you of this
immediately.) Auto Layout will then adjust the priority of conflicting constraints
in order to lay out the view, which will probably result in your user interface
looking off-kilter.
To debug unsatisfiable constraints, review the console output and determine
which constraints are truly in conflict. You may find that two constraints cannot
coexist, or that by reducing a constraint’s priority you can resolve the conflict.
Let’s see an example of unsatisfiable constraints. In the AutoLabelOut project, set
translatesAutoresizingMaskIntoConstraints to true (this is the default):
// Create a label:
let label = NSTextField(frame:
NSRect.zeroRect)
label.translatesAutoresizingMaskIntoConstraints =
false
label.translatesAutoresizingMaskIntoConstraints = true
label.stringValue = "Label"
// Give this NSTextField the styling of a
label:
label.backgroundColor = NSColor.clearColor()
label.editable = false
label.selectable = false
label.bezeled = false
If the layout is ambiguous, you will also see an Exercise Ambiguity button. Clicking
this button will demonstrate how the constraints are ambiguous, which can help
with debugging.
To display the constraints for your window, add the following to the bottom of
applicationDidFinishLaunching(_:).
superview.addConstraints(verticalConstraints)
window.visualizeConstraints(superview.constraints)
A more subtle approach is to query a view directly:
superview.addConstraints(verticalConstraints)
window.visualizeConstraints(superview.constraints)
superview.updateConstraintsForSubtreeIfNeeded()
if superview.hasAmbiguousLayout {
superview.exerciseAmbiguityInLayout()
}
You have to call updateConstraintsForSubtreeIfNeeded because constraints do
not get set up immediately. This way, the constraints will be set up before
checking whether the view has an ambiguous layout.
For the More Curious: Autoresizing Masks
Before Auto Layout, Cocoa developers used autoresizing masks to handle
dynamic layout. An autoresizing mask defines how a view resizes in response to
its superview resizing. Unlike Auto Layout, autoresizing masks do not provide
any means of defining a view’s relationship with its sibling views, changing
content as with the intrinsic content size, or animating changes.
An autoresizing mask is made up of springs and struts. Springs allow the view to
resize in the horizontal and/or vertical directions. Struts preserve the distance
between the sides of the view and its superview. It is called a mask because it is
expressed as a bit field. Figure 25.16 and Figure 25.17 demonstrate two typical
configurations of springs and struts.
Figure 25.16 View is anchored to the upper left of its superview
Figure 25.17 View is anchored on all sides and will resize with its superview
You will need to make sure that the constraints you are adding programmatically
do not just duplicate the constraints you already added in Interface Builder.
However, removing all constraints from IB will not work, because Interface Builder
will detect an ambiguous layout and add constraints to prevent it. Instead, select
each of the constraints in turn and, in the attributes inspector, check the Remove at
build time checkbox at the bottom, in the Placeholder section.
26
Localization and Bundles
If the application you create is useful, you will want to share it with all the
people of the world. Unfortunately, we do not all speak the same language.
Suppose that you wish to make your RaiseMan application available to French
speakers. We would say, “You need to localize RaiseMan for French speakers.”
If you are creating an application for the world, you should plan on localizing it
for at least the following languages: English, French, Spanish, German, Dutch,
Italian, Mandarin, and Japanese. Clearly, you do not want to have to rewrite the
entire app for each language. In fact, your goal should be to ensure that your
code will remain unchanged as you add support for new languages. That way, all
the nations of the world can use a single executable in peace and harmony.
Instead of creating multiple executables, you will localize resources and create
strings files. Inside the RaiseMan project directory, a Base.lproj directory holds all
the components of your app that are participating in localization: currently, it
holds XIB files and your document icon, but it can hold other resources as well.
When you localize your app for French speakers, Xcode will create a fr.lproj
directory. The XIBs, images, and sounds that Xcode puts in this directory will be
appropriate for French speakers. At runtime, the app will automatically use the
version of the resource appropriate to the user’s language preference.
Different Mechanisms for Localization
For each type of resource, there is one or more particular way that localization is
done.
User-facing string literals
These are strings that appear in your source files that will be presented to the
user. For example, the string that you set on a label, or the messageText you set
on an NSAlert.
Such strings are always localized using a strings file. Strings files are like
dictionaries. They allow you to associate a name or key for a string (for
example, “remove alert messageText”) with a string that should be used for a
particular language (for English, “Are you sure you want to remove these
employees?”).
You will take a closer look at a strings file later in the chapter.
General resources
This category includes all files that are bundled up as-is with your app: images,
sounds, video, even text documents that you display to the user such as a license.
Note, though, that this does not include XIBs.
A resources file is always localized by providing a language-specific version of
the file.
For example, suppose you have an image that embeds some text. To localize this
resource, you would need to generate an image with translated text for each of
the languages that you are localizing the app for. You would then put each of
those images in the .lproj folder corresponding to the language for which that
version of the image was localized.
XIB Files
XIB files can be localized in one of two ways, similar to the two localization
techniques above:
Localized XIB File
This approach is similar to what is done for general resource files.
Following this route, when you localize a XIB file, you create a copy of the
existing XIB file and change the strings in the file for the new localization.
During compilation, Xcode will create a NIB file for each localization and
copy that localized NIB into the app’s bundle. At runtime, your app will
use the NIB file localized for the language that the user has configured their
system with.
Strings File
This approach is similar to what is done for string literals.
When you follow this approach to localize a XIB into a new language, Xcode
generates a strings file for you which associates controls in the XIB with
the text that should appear in those controls. It is your task to edit those
generated strings files to contain the correct text for that language.
During compilation, Xcode will copy these strings files into your app bundle.
At runtime, when your app unpacks a NIB, it will query the corresponding
strings file for the user’s preferred language and update the controls in the
NIB to display the text from the strings file.
Localized XIB File
This approach is similar to what is done for general resource files.
Following this route, when you localize a XIB file, you create a copy of the
existing XIB file and change the strings in the file for the new localization.
During compilation, Xcode will create a NIB file for each localization and
copy that localized NIB into the app’s bundle. At runtime, your app will
use the NIB file localized for the language that the user has configured their
system with.
Strings File
This approach is similar to what is done for string literals.
When you follow this approach to localize a XIB into a new language, Xcode
generates a strings file for you which associates controls in the XIB with
the text that should appear in those controls. It is your task to edit those
generated strings files to contain the correct text for that language.
During compilation, Xcode will copy these strings files into your app bundle.
At runtime, when your app unpacks a NIB, it will query the corresponding
strings file for the user’s preferred language and update the controls in the
NIB to display the text from the strings file.
Each of the approaches to localizing XIB files has advantages and
disadvantages:
Creating a new XIB file for a language allows greater per-language tailoring.
You are free to rearrange controls to your heart’s content because you are using
an entirely different XIB. This can be useful with languages which tend to have
more characters, when you need to shuffle controls around to make space for the
longer text. (This tends to be unnecessary if you are using Auto Layout,
longer text. (This tends to be unnecessary if you are using Auto Layout,
however, thanks to intrinsic content size.) On the other hand, there is much more
maintenance involved. When you add a new button, for example, you will need
to manually add that button to each localized XIB.
In contrast, creating a strings file makes maintenance easier: you only need to
add a new entry to the strings file when adding a button. On the other hand, there
is much less room for customization, since you do not have a separate XIB to
arrange controls in.
When localizing a XIB, you can mix and match these approaches. For example,
you could use a strings file for localizing for French and use a localized XIB file
to localize for German.
In this chapter, you will use a couple of these techniques to localize RaiseMan for
French speakers. When you are done, the app will look something like
Figure 26.1:
Figure 26.1 Completed application
Localizing a XIB File
Before localizing any of RaiseMan’s resources to French, you need to tell Xcode that
you want the app to be localized for French.
In Xcode, click on the project in the project navigator and select the RaiseMan
project itself (not the target). On the Info tab, find the Localizations section and click
the + button. Choose French from the pop-up list (Figure 26.2).
Figure 26.2 Adding the French language to the RaiseMan project
You have told Xcode that you are going to localize your app to French. Now Xcode
wants to know what files you are going to localize.
You will start off the process of localizing RaiseMan’s resources to French by
localizing Document.xib.
In the sheet that has appeared (Figure 26.3), make sure that only Document.xib is
checked, since you are only ready to localize the document window at this point.
Xcode will create a file which you will need to modify in order to localize
Document.xib for the French language.
As you already saw, there are a couple of options when localizing a XIB file:
you can localize using a strings file or a whole new XIB. Your options for this
choice are displayed in the File Types pop-up menu. By default, Localizable Strings is
selected. When using this option, Xcode will create a strings file, a text file which
allows you to specify the French text to be displayed in the various controls in
the reference version of Document.xib. Using the other option, Interface Builder Cocoa
XIB, Xcode creates an entirely new XIB for French. In this exercise, you are going
to create a strings file.
Figure 26.3 Localizing Document.xib
When the localized file (either a strings file or a XIB) is created, it is pre-
populated with the data of another, already existing localization, which needs to
be overwritten. The localization that is used is called the Reference Language. At this
point, the only option for a Reference Language for Document.xib is Base – no other
localizations exist! If you were to add a German localization after you finished
the French localization, however, you would be able to base it on the French
localization.
Click Finish to create the localized Document.xib.
If you look in Finder, you will see that a new file, Document.strings, has been
created in fr.lproj. Let’s take a look at the file in Xcode. In the project navigator,
expand the disclosure arrow next to Document.xib, and select Document.strings
as in Figure 26.4.
Figure 26.4 Locating Document.strings
When you are done, this file will contain instructions used by Cocoa to change
the text on the various views in Document.xib to the appropriate French words
and phrases. Currently, though, since you used the base localization as the
reference language for this file, it contains instructions for English rather than
French.
You will “francophize” this file. To do this, you will translate a few of the
English phrases that appear in this file into French. First, find the line in the file
where “Raise” is specified to be the title of a NSTableColumn’s headerCell; the
line should look something like this:
/* Class = "NSTableColumn"; headerCell.title =
"Raise"; ObjectID = "hQP-gu-5md"; */
"hQP-gu-5md.headerCell.title" = "Raise";
In your file, the ObjectID will be different. This is to be expected, the ID is
generated randomly and is only used as a unique identifier for views in the XIB.
Change “Raise” to “Augmentation”:
/* Class = "NSTableColumn"; headerCell.title =
"Raise"; ObjectID = "hQP-gu-5md"; */
"hQP-gu-5md.headerCell.title" = "Raise";
"hQP-gu-5md.headerCell.title" = "Augmentation";
Repeat this same process, making the following changes:
Change “Name” to “Nom”.
Change “Add Employee” to “Ajouter Employé”.
Change “Remove” to “Supprimer”.
With those changes made, your file should look a little something like this,
though as before, the ObjectIDs and the order in which the objects appear may be
different:
/* Class = "NSTableColumn"; headerCell.title = "Name";
ObjectID = "7Sc-gL-qrn";
"7Sc-gL-qrn.headerCell.title" = "Nom";
alert.informativeText = "\(selectedPeople.count)
people will be removed."
let informativeFormat
= NSLocalizedString("REMOVE_INFORMATIVE %d",
comment: "The remove alert's
informativeText")
alert.informativeText
= String(format: informativeFormat,
selectedPeople.count)
alert.addButtonWithTitle("Remove")
let removeButtonTitle = NSLocalizedString("REMOVE_DO",
comment: "The remove
alert's remove button")
alert.addButtonWithTitle(removeButtonTitle)
alert.addButtonWithTitle("Cancel")
let removeCancelTitle =
NSLocalizedString("REMOVE_CANCEL",
comment: "The remove
alert's cancel button")
alert.addButtonWithTitle(removeCancelTitle)
In Terminal, type in cd followed by a space and then paste the path you just copied
from Xcode. You should have something like this at the command line in Terminal:
$ cd Usersnate/Desktop/RaiseMan/RaiseMan
If your path has spaces in it, you must surround it with double quotes.
$ cd "path with spacespath with
spaces/to/RaiseMan/RaiseMan"
Run that command. The working directory will now contain your
Document.swift file. (You can verify this by running ls in Terminal.) At this point,
you are going to use genstrings.
The command-line utility genstrings will generate a strings file for one or more
Swift source files. It does this by searching through each of the files that you
specified for occurrences of NSLocalizedString. It then creates a strings file with
one key-value pair for each occurrence of NSLocalizedString.
In this case, you only need genstrings to search through Document.swift for
occurrences of NSLocalizedString. To do that, type the following into Terminal: $
genstrings Document.swift -o Base.lproj
Run this command.
This command generated a file called Localizable.strings containing all the
localized strings from Document.swift in the Base.lproj directory of your
project.
You generated it into the base localization directory because the generated file is
suitable as a template for other localizations, but is not itself a localization. You
are going to create localizations from this file for English and French.
Use the cat command to make sure that the file was generated: $ cat
Base.lproj/Localizable.strings
The output should look something like this:
/* The remove alert's cancel button
"REMOVE_CANCEL" = "REMOVE_CANCEL";
Try building the app. You may get a warning: “The specified input encoding is
Unicode (UTF-8), but file contents appear to be Unicode (UTF-16); treating as
Unicode (UTF-16)”. If you are seeing this, you are experiencing an Xcode bug. To
fix it, you will need to select each of the localizations of Localizable.strings in
turn from the project navigator, and do the following: Open the file inspector.
You should see that the Text Encoding is set to Default - Unicode (UTF-16). Change the
encoding to Unicode (UTF-16). Xcode will present a sheet asking whether you want to
convert the text to UTF-16. Click the Convert button. After you have done this for
all three of the localizations, the warning should go away.
Expand the disclosure arrow for Localizable.strings in the project navigator
and select the English localization. Update the file to have English localized
strings:
/* The remove alert's cancel button */
"REMOVE_CANCEL" = "REMOVE_CANCEL";
"REMOVE_CANCEL" = "Cancel";
One thing to note here is that you have created a separate file for the English
localization. In the case of Document.xib, you used the Base localization XIB for
English, the development language of the project. Either of these approaches is
fine. Both the English localization (if English is the development language) and
the Base localization get used when the user’s preferred localization is not
present. You can read more about this in the section called “NSBundle’s role in
localization” .
Now edit the French localization of Localizable.strings:
/* The remove alert's cancel button */
"REMOVE_CANCEL" = "REMOVE_CANCEL";
"REMOVE_CANCEL" = "Annuler";
Now run your app. Try adding and removing some employees. The alert panel
will have text in French.
Demystifying NSLocalizedString and
genstrings
NSLocalizedString and genstrings make it very easy to generate a
Localizable.strings file for your app. However, the steps that they follow to
generate the file are easy and worthwhile to understand. You can do all of this
yourself.
A strings file is just a collection of key-value pairs. The key and the value are
strings surrounded by quotes, and the pair is terminated with a semicolon:
"Key1" = "Value1";
"Key2" = "Value2";
You can create a strings file yourself and add it to your project just by dragging
it into Xcode. Once you have added the file, you can create localizations of it for
other languages using the file inspector.
Once you have added the file to your project and created localizations, you can
use NSBundle to get the localized strings when your app is running:
let mainBundle: NSBundle = NSBundle.mainBundle()
let string: String =
mainBundle.localizedStringForKey("NO_RESULTS",
value:
"No results found",
table:
"Find")
When your app is running, this code would search in the localized versions of
the “Find.strings” file for the value corresponding to the "NO_RESULTS" key. If
no value is found "No results found" is returned. If you do not supply the
name of the table (file), Localizable.strings is used instead. Most simple
applications just have one strings file for each language: Localizable.strings.
Internally, NSLocalizedString just calls this method on NSBundle.mainBundle().
Explicit Ordering of Tokens in Format
Strings
As text is moved from language to language, both the words and the order of the
words change. For example, the words in one language may be laid out like this:
“Ted needs a scooter.” In another, the order might be “A scooter is what Ted
needs.” Suppose that you try to localize the format string to be used like this:
let format = NSLocalizedString("NEEDS", comment: "%@
needs a %@")
let string = String(format: format, "I", "Hero")
The following will work fine for the first language:
"NEEDS" = "%@ needs a %@";
For the second language, you would need to explicitly indicate the index of the
token you want to insert. This is done with a number and the dollar sign:
"NEEDS" = "A %2$@ is what %1$@ needs";
NSBundle
A bundle is a directory of resources that may be used by an application. A
resource can be any kind of file: images, sounds, and NIB files. Bundles can also
contain compiled code. The class NSBundle is a very elegant way of dealing with
bundles.
Your application is a bundle. In Finder, an application looks to the user like any
other file, but it is really a directory filled with NIB files, compiled code, and
other resources. We call this directory the main bundle of the application.
To get the main bundle of an application, use the following code:
let bundle = NSBundle.mainBundle()
This is the most commonly used bundle. If you need to access resources in
another bundle, however, you could ask for the bundle at a certain path:
let bundleURL: NSURL = ...
if let bundle = NSBundle(URL: bundleURL) {
...
}
Once you have an NSBundle object, you can ask it for its resources:
let url: NSURL? = bundle.URLForResource("Chapter",
withExtension: "xml")
This works well for general resources, but for images you should use NSImage’s
init(named:):
if let appIconImage = NSImage(named:
NSImageNameApplicationIcon),
let badgeImage = NSImage(named: "GreenBadge") {
...
}
It will load images from your application bundle by name, and even special
images such as shared AppKit images. See the class reference documentation for
this initializer for more.
NSBundle’s role in localization
NSBundle is the class that makes localization in Cocoa so seamless. For example,
when loading a file resource from a bundle you will first want the URL (or path)
for that resource:
let url: NSURL? = bundle.URLForResource("Tutorial",
withExtension: "mov")
URLForResource(_:withExtension:) transparently handles finding the most
appropriate localized resource given the user’s language preferences and returns
the URL. Once a resource is localized in Xcode there is no further need for special
handling at runtime. NSBundle does it for you.
Remember that these resources can be any file: NIBs, text files, JSON, audio,
video. If a resource (not source code) has the target checked in the file inspector,
it will be packaged up with the app bundle and can be located with this API.
NSBundle plays a more involved role when working with strings files. For
example, in the section called “Demystifying NSLocalizedString and genstrings”
, you saw that Cocoa would locate the string for the key "NO_RESULTS" in a
localized Find.strings file. In fact, it will search through a number of these
files.
Here is the order that the searching is done in:
Global resource
This is a resource that you have not localized. By default, it will be in the top
level of your project’s source code directory.
If this resource is present, no language/region specific version will ever be
returned. In particular, imagine you had a global Localizable.strings file and
also localized versions of Localizable.strings. If you ask NSBundle for a
localized string in the Localizable file, it will only search in the global file,
even if the string is not there.
Region-specific localized resource
In this chapter, you saw language-specific localized resources. You can also
provide finer-grained localization specific to the version of a language in a
particular region.
The user can specify their region in the Language & Region preference pane.
When NSBundle finds a localization which (1) is in the current language and (2)
matches the region the user has specified, that localization is preferred over the
generic (not region-specific) localization in that language.
Language-specific localized resource
These are the resources that you have worked with over the course of this
chapter.
The user can specify a number of preferred languages in the Language & Region
preference pane
If a localized resource is not found in the most preferred language’s lproj
directory, the second preferred language’s directory will be searched, and so on.
Development language resource
This is the localization corresponding to the development language. The
development language is specified in the Info.plist under the
CFBundleDevelopmentRegion key.
If no localization can be found matching any of the user’s preferred languages,
the localization for the language in which the app was developed will be used.
Base localization resource
This is the localization that your other localizations are based on.
If the resource cannot be found in any localization, the base localization will be
used.
NSBundle will search through each of these localization’s lproj directories in this
order, and return the first resource that it finds.
To do so, you will create a view that does the printing. Instead of making the
view big enough to display all the people simultaneously, you will simply note
which page the system is printing and draw only the names on that page in
drawRect(_:).
// MARK: - Lifecycle
init(employees: [Employee]) {
self.employees = employees
super.init(frame: NSRect())
}
Run the application. To have multiple pages of employees to print, you will need
a whole lot of them. You can simply click Add Employee repeatedly until you have
an adequate number. Once you have gotten a large number of employees into
your document, prepare to print by selecting the File → Print menu item.
When the print sheet appears, click the Show Details button. The sheet will expand
to show additional configuration options. Locate the pop-up button halfway
down the right hand side of the sheet which allows you to select which
configuration details are shown (Figure 27.3).
Figure 27.3 Expanded printing sheet
From the pop-up menu highlighted in Figure 27.3, select Layout and then change
the Pages per Sheet setting to 2. Note that this works just fine.
You can also change the paper size and more or fewer people subsequently
appear on each page. To change the paper size, cancel out of the print sheet,
selecting the File → Page Setup... menu item. In the resulting sheet, change the Paper
Size from US Letter to something else, such as A3.
For the More Curious: Are You Drawing to
the Screen?
In an application, you will often want to draw things differently on screen than
on the printer. For example, in a drawing program, the on-screen view might
show a grid on-screen but not when printed on paper.
In your drawRect(_:) method, you can ask the current graphics context if it is
currently drawing to the screen:
if NSGraphicsContext.currentContextDrawingToScreen() {
// ... draw the grid ...
}
Challenge: Add Page Numbers
Add page numbers to the printout.
Recall that drawRect(_:) is only printing a single page at a time. Use this
knowledge, and the fact that you are tracking the current page number in a
property, to complete the challenge.
Challenge: Persist Page Setup
The configuration changes made in the Page Setup sheet are document-specific.
Cocoa does not provide any automatic support for persisting these settings,
however. It is your responsibility to save and load them. Fortunately,
NSPrintInfo (stored in the document’s printInfo property) conforms to
NSCoding. Modify the Document class to archive the printInfo along with the
employees array when saving, and vice-versa when loading.
This is not quite as straightforward as it sounds. For one thing, the file format of
rsmn files is simply an archived array. It makes no allowances for additional
information. You will need to change the format. A popular pattern for this is to
use a dictionary as the top level object with keys for the high level components
of the file. As an example, consider the following dictionary pseudo-code:
[
"employees": [Employee],
"printInfo": NSPrintInfo
]
When loading, be sure to access the members safely using optional binding and
optional downcasting. This technique is also demonstrated in Chapter 28.
if let employees = topLevelDictionary["employees"] as?
[Employee] {
...
}
Reworking the layout of the RaiseMan document format will break loading of the
files your tens of users have already created with this app. While it would
complicate your deserialization code, it is possible to serialize a file format
version number along with your document data to support loading both the new
and old formats! If no version number is found, then you know the file is in the
old format. If the version number is found, then you know exactly what version
of the format the file is in.
When unarchiving the NSPrintInfo object, you will assign it to the printInfo
property. This property’s setter has a side effect: it adds a frame to the undo
stack, which is undesirable in that it would cause a freshly loaded document to
appear to be modified. To fix this, disable undo registration while setting the
property:
override func readFromData(data: NSData,
ofType typeName: String,
error outError: NSErrorPointer) -
> Bool {
...
...
if let unarchivedPrintInfo =
dictionary["printInfo"] as? NSPrintInfo {
undoManager.disableUndoRegistration()
printInfo = unarchivedPrintInfo
undoManager.enableUndoRegistration()
}
28
Web Services
It is common for an application to need information from somewhere beyond the
user’s computer. The easiest way to make that information available is to put it
on a web server and publish the URL that provides the desired information. Web
servers communicate using HTTP or HTTPS.
HTTP (Hypertext Transfer Protocol) was originally designed for serving HTML
documents for rendering in a web browser. As the web’s popularity soared, so
did creative uses of HTTP. Today, HTTP is commonly used not only to serve
content for web browsers, but also structured data for any software that speaks
HTTP. In this chapter you will see the latter, but the concepts will be applicable
to all web services.
Using a web service is simple:
Format a request as required by the web service
Send the request over HTTP/HTTPS
Receive the response
Parse the response and use that data in your application
In order to make a web service request you will need to describe the endpoint of
the request. That is, the scheme (usually HTTP or HTTPS), the host (the server’s
address), and the resource path. The NSURL class serves this purpose. You will
recall this versatile class from earlier in the book; it is frequently used in Cocoa
to describe the location of files as well as web services.
Once you have an NSURL you will wrap it in an additional class, NSURLRequest,
which offers additional configuration opportunities in the form of the HTTP
method (GET, PUT, POST, etc.), HTTP headers, cache policy, timeout, and
more. (To configure these you will want to create an instance of
NSMutableURLRequest, a subclass of NSURLRequest.) Once you have a configured
NSURLRequest object, you will ask an NSURLSession to create a connection from
it. An NSURLSession manages a number of individual HTTP connections with
common configurations. For example, all of the requests made with a particular
session will have the same timeout interval and cache policy. When you give
your NSURLSession the NSURLRequest, it will hand you back an NSURLSessionTask,
which wraps up the connection corresponding to the request. You can use the
task to start and stop the connection as well as to get information about it.
The NSURLSessionTask completes when you get the server’s response. At this
point, you will need to parse the response into structured data that your
application can work with. If you implement your own web service, you can
format the request and response data any way you like, but to use an existing
web service you have to work with its format. XML and JSON are commonly
used, and Cocoa provides classes for working with both of them.
RanchForecast Project
Big Nerd Ranch has a web service that delivers a JSON document of its
upcoming classes. The URL of this web service is
http://bookapi.bignerdranch.com/courses.json. In this exercise, you are going to write an
application that requests this data from the web service, parses it, and displays
the upcoming classes in a table view. The app will look like Figure 28.3. Before
you get started, try opening the URL above in your web browser to get a feel for
what your application will be dealing with.
Figure 28.3 Completed application
Create a new project of type Cocoa Application. Name the project RanchForecast. Set
the Language to Swift. Uncheck Create Document-Based Application, Use Core Data, and Use
Storyboards.
class ScheduleFetcher {
init() {
let config =
NSURLSessionConfiguration.defaultSessionConfiguration()
enum FetchCoursesResult {
case Success([Course])
case Failure(NSError)
}
let task =
session.dataTaskWithRequest(request,
completionHandler: {
(data, response, error) -> Void in
var result: FetchCoursesResult
if data == nil {
result = .Failure(error)
}
else {
println("Received \(data.length)
bytes.")
result = .Success([]) // Empty array
until parsing is added
}
NSOperationQueue.mainQueue().addOperationWithBlock({
completionHandler(result)
})
})
task.resume()
}
You will learn more about operation queues in Chapter 34. Until then, it is
enough to know that NSURLSessionDataTask calls its completion handler on a
background thread – that is, a thread other than the main thread, where all UI
updates should be performed. When implementing a class like ScheduleFetcher
it is a good idea to call completion handlers on the main thread so that the caller
can directly update the UI with the results. The call to NSOperationQueue you
used here runs the closure parameter on the main thread.
Let’s pause here to test fetchCoursesUsingCompletionHandler(_:). Update
MainWindowController.swift to call it from windowDidLoad() and store the
resulting courses array in a property. Later in this chapter you will bind an array
controller to this property, so the dynamic keyword is necessary to make the
property compatible with bindings.
import Cocoa
fetcher.fetchCoursesUsingCompletionHandler {
(result) in
switch result {
case .Success(let courses):
println("Got courses: \(courses)")
self.courses = courses
case .Failure(let error):
println("Got error: \(error)")
NSAlert(error: error).runModal()
self.courses = []
}
}
}
}
Build and run your application. You should see a log message indicating that
data was received. Even though data is received, you have not parsed it yet, so
the window controller’s courses array will remain empty. Resolve any problems
before continuing.
NSURLSession, HTTP status codes, and errors
Part of the response to an HTTP request is the status code, which is a number in
the range 100 to 599. Generally speaking, anything between 200 and 299
denotes success, while other values such as 404 and 500 indicate issues with the
request or server itself. You might expect to get an error condition from
NSURLSession for these error status codes, but NSURLSession works at a lower
level than that. If it is able to contact the server and receive a response, then, as
far as NSURLSession is concerned, the exchange was a complete success.
In other words, NSURLSession only reports an error if a problem occurred at the
network layer and it was unable to complete the request. In a serious application
you should check the status codes returned by the server. NSURLSession provides
the completion handler with a reference to an NSURLResponse. For HTTP
requests, this reference is actually an NSHTTPURLResponse, which is a subclass of
NSURLResponse. The status code is in a property on NSHTTPURLResponse, so to
access it you will need to cast the reference.
You will add handling of HTTP status codes to the NSURLSession completion
handler, but first you will create a helper method that creates NSError objects.
Implement this method in the ScheduleFetcher class:
func errorWithCode(code: Int, localizedDescription:
String) -> NSError {
return NSError(domain: "ScheduleFetcher",
code: code,
userInfo:
[NSLocalizedDescriptionKey: localizedDescription])
}
Now use this method to help flesh out the response checking and error reporting
in fetchCoursesUsingCompletionHandler(_:):
func
fetchCoursesUsingCompletionHandler(completionHandler:
(FetchCoursesResult) -> (Void)) {
let url = NSURL(string:
"http://bookapi.bignerdranch.com/courses.json")!
let req = NSURLRequest(URL: url)
let task = session.dataTaskWithRequest(req,
completionHandler: {
(data, response, error) -> Void in
var result: FetchCoursesResult
if data == nil {
result = .Failure(error)
}
else {
println("Received \(data.count)
bytes.")
result = .Success([]) // Empty array
until parsing is added
}
else if let response = response as?
NSHTTPURLResponse {
println("\(data.length) bytes, HTTP \
(response.statusCode).")
if response.statusCode == 200 {
result = .Success([]) // Empty
array until parsing is added
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Bad status code
\(response.statusCode)")
result = .Failure(error)
}
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Unexpected response object.")
result = .Failure(error)
}
NSOperationQueue.mainQueue().addOperationWithBlock({
completionHandler(result)
})
})
task.resume()
}
Run the application and ensure that the success path still works. Try adding
some gibberish characters to the URL and you should see a 404 status code. Do
not forget to change it back!
Now you are going to bind the table view’s content to the window controller’s
courses array via an array controller. When you are done, you will have added
all the bindings in Figure 28.6.
Figure 28.6 RanchForecast’s bindings
In a moment, we will walk you through setting up these bindings. (If you would
like more details, look back at the way you set up the bindings for RaiseMan in
Chapter 9. You will be using exactly the same approach here.) Remember that
within MainWindowController.xib, File's Owner represents the window controller.
Drag an Array Controller onto the canvas. Bind the array controller’s Content Array to
the File's Owner, with a Model Key Path of courses.
Select the Table View and bind its Content to the Array Controller, with a Controller Key of
arrangedObjects. Then bind the Selection Indexes to the Array Controller with a
Controller Key of selectionIndexes. (Don’t see these bindings? Did you select the
scroll view or clip view instead of the table view?)
Finally, bind the Value of the text field in each of the table view cells to its
respective key path. In view-based NSTableViews this can be confusing, as Xcode
defaults to giving the text fields a label of Table View Cell. You can differentiate
them in the document outline by looking for the control icon, which looks like a
very small slider.
In the Next Start Date column’s cell view, bind the text field’s Value to the Table
Cell View, with a Model Key Path of objectValue.nextStartDate. In the Title
column’s cell view, bind the text field’s Value to the Table Cell View, this time with a
Model Key Path of objectValue.title.
Run the application. After a short delay you should see the table view populated
with the data for the upcoming classes. If it did not work, set a breakpoint and
use the debugger to check the value of the courses in the completion handler
within MainWindowController.
Opening URLs
The data for each course contains the URL of a web page that has a description
of that class. The last functionality for this app is to open that web page in the
user’s browser if the user double-clicks on the course.
To get the table view to respond to a double-click you must set the doubleAction
and target of the table view. Unlike most target/action pairs, doubleAction must
be set programmatically. Open MainWindowController.swift and add an outlet to
the table view and the array controller:
class MainWindowController: NSWindowController {
tableView.target = self
tableView.doubleAction = Selector("openClass:")
fetcher.fetchCoursesUsingCompletionHandler {
(result) in
Finally, implement openClass(_:). You will use the NSWorkspace class.
NSWorkspace represents the Finder, which knows how to open URLs in the user’s
default web browser.
func openClass(sender: AnyObject!) {
if let course =
arrayController.selectedObjects.first as? Course {
NSWorkspace.sharedWorkspace().openURL(course.url)
}
}
Note that you use the array controller to provide the selected objects. One of the
advantages of array controllers is that they can provide objects for display in a
different sort order (arrangedObjects) from the model (courses). It is easy to
forget this and use NSTableView’s selectedRow as an index into courses, which
would result in the wrong object being returned. The selectedObjects property
will neatly avoid this.
Build and run the application. Double-click on a row and the page for the
selected class should open in your default browser. If it does not, check that you
configured the Selection Indexes binding on the array controller.
Safely Working with Untyped Data
Structures
Congratulations, it works! But are you finished? Your application already
detects errors from JSON parsing (converting the received JSON data into a tree
of NSDictionary, NSArray, and so forth), but it does not check that the structure
of this data satisfies the expectations of the app’s code.
As it stands, the code makes some assumptions about the structure of the JSON.
Consider these lines:
let title = courseDict["title"] as! String
let urlString = courseDict["url"] as! String
let upcomingArray = courseDict["upcoming"] as!
[NSDictionary]
let nextUpcomingDict = upcomingArray.first!
let nextStartDateString =
nextUpcomingDict["start_date"] as! String
This code makes several assumptions:
courseDict has a key named upcoming whose value is an array, and
You can then traverse the tree manually or use XPath queries to extract the
nodes you want. For example, the following listing prints all of the first names
from the given XML document:
var xmlString = "<?xml version=\"1.0\" encoding=\"UTF-
8\"?>"
+ "<people>"
+ " <person><first>Jimmy</first>
<last>Smith</last></person>"
+ " <person><first>Lonnie</first>
<last>Smith</last></person>"
+ "</people>"
var error: NSError?
if let doc = NSXMLDocument(XMLString: xmlString,
if let doc = NSXMLDocument(XMLString: xmlString,
options: 0, error: &error) {
if let people = doc.nodesForXPath("//person",
error: &error) as [NSXMLNode]? {
for person in people {
let firstNameNodes =
person.nodesForXPath("first", error: &error)
as [NSXMLNode]
if let firstNameNode =
firstNameNodes.first {
println(firstNameNode.stringValue!)
}
}
}
}
NSXMLDocument provides a convenient way to query a document, but it can be
memory-intensive for large XML documents as it allocates node objects for each
node of the document.
If you want something a lot leaner, you will want to consider NSXMLParser, an
event-driven (SAX) parser. Instead of creating a tree, NSXMLParser makes calls to
its delegate as it encounters XML elements and other structures. You will need
to write a state machine to deal with these events and extract the data you want.
Another consideration in choosing between the two is portability. Of the two,
only NSXMLParser is available on iOS.
Challenge: Improve Error Handling
Add error handling to courseFromDictionary(_:) (as discussed in the section
called “Safely Working with Untyped Data Structures” ) such that it returns nil
if any of the fields or structure are not as expected. Test the failure modes by
changing the key strings in the code.
Challenge: Add a Spinner
Add an indeterminate progress indicator to the window. Animate it while the
ScheduleFetcher fetches the courses so that the user knows it is working.
Challenge: Parse the XML Courses Feed
Big Nerd Ranch also has a web service that delivers the data for its upcoming
classes as an XML document. Adapt ScheduleFetcher to parse the course data
from http://bookapi.bignerdranch.com/courses.xml.
29
Unit Testing
As you implement a new feature in your application, you will want to ensure that
the feature works the way that you intend it to. The fastest and most reliable way
to verify that your code works is to write automated tests and run them against
your code often. As you are building a new feature, you can repeatedly run your
automated tests to guarantee that the code that you have written results in the
behavior that you want.
Unit testing is a particular kind of automated testing in which the individual
components, the units, of your application are tested. These units are the
building blocks of your application. Typically, they are your application’s
classes.
The advantage of testing units is that you are able to validate each building block
on its own. You do not have to hook the component up to the rest of your app to
determine that it is functioning properly. Instead, you can run your tests on the
component in isolation as you modify it. Once you finish working on a
component and your tests have validated it, you can confidently snap it onto
other validated components in your application.
There are many kinds of testing, but in this book we will only focus on unit
testing.
Testing in Xcode
Open the RanchForecast project from Chapter 28. In this chapter, you will be adding
tests to RanchForecast.
Every Cocoa project that you create comes equipped with a test target, the
container for all your tests. RanchForecast is no exception.
In Xcode, open the project navigator. At the top level, you should see three
groups: RanchForecast, RanchForecastTests, and Products (Figure 29.1).
Figure 29.1 Groups in RanchForecast
When you run your tests, all of the methods in your XCTestCase classes prefixed
with the string test will be executed. In the case of the auto-generated
RanchForecastTests class, both testExample() and testPerformanceExample()
will be called.
A test method makes assertions about the state of the component that is being
tested after the component has been made to do some work. For example,
imagine that you had a Car class in a game you were writing. How would you
test that it accelerates properly? You might write a test like this one:
func testAccelerateTo75() {
let speed = 75
let speed = 75
car.accelerateToSpeed(speed)
XCTAssertEqual(car.speed, speed)
}
First, you would tell an instance of Car to accelerate up to seventy-five miles per
hour – you would make the component do some work. Then, you would assert
that the Car’s speed had in fact changed to seventy-five miles per hour – you
would make an assertion about its state. If the Car’s velocity was not 75mph,
then the test would fail. In general, if the assertions that you make are not
satisfied, then the test that they are made in fails.
These assertions are made by way of the assert functions contained in the
XCTest framework. Here are a few of the assert functions that are commonly
used:
XCTAssertTrue(expression)
XCTAssertEqual(testResultValue, expectedValue)
XCTAssertNotNil(expression)
XCTFail()
When any of these assertions fail, a message describing what went wrong is
generated based on the kind of assertion. You can customize the message by
supplying your own as an additional parameter. This is especially important for
XCTFail. For example:
if let value = dictionary["key"] as? String {
XCTAssertEqual(value, "Value", "dictionary not
properly populated")
}
else {
XCTFail("key not found in dictionary, or value not
a String")
}
To see a complete listing of assertions in the XCTest framework, look in the
Assertions Listed by Category section of Apple’s Testing with Xcode guide.
Your First Test
It is time to start using these asserts.
You will be writing unit tests against RanchForecast, tests that verify that the classes
in the app work individually. If you were writing tests for app-wide
functionality, you might put them in RanchForecastTests.swift. However, as it
is you do not have any use for this file since all of your tests will be class-
specific. Go ahead and delete the file.
The first class that you will write tests for is Course. To make it obvious which
class you are testing in a given test, you will create a separate test file and
XCTestCase subclass for each of the classes in the project. In the project
navigator, right-click on the RanchForecastTests group, and select New File....
Create a new file using the Swift File template. Name the file CourseTests.swift.
At this point, Xcode may ask you whether you would like to configure an
Objective-C bridging header. Select No.
Open CourseTests.swift. At the moment, it contains almost no code. Add an
empty definition for a new subclass of XCTestCase called CourseTests. This will
require you to import XCTest, the framework where XCTestCase is defined.
import Foundation
import XCTest
}
The first test you will write will be very simple: You will create a course object
with a title, a URL, and a date. You will then check that the course’s fields
match the values that you created it with. Create a test called
testCourseInitialization():
class CourseTests: XCTestCase {
func testCourseInitialization() {
}
}
Try running your tests at this point. In Xcode, open the Product menu, and select
Test. (You can also press Command+u.) Your tests should run very quickly, and
you should see a grey overlay (Figure 29.2) indicating that your tests passed.
Figure 29.2 Success
In CourseTests, you should see two little green checkboxes, one indicating that
testCourseInitialization() passed and one indicating that all of the tests in
CourseTests passed.
Make sure the navigtor area is expanded and select the diamond to display the
test navigator. In the test navigator, you should see an entry for the test target,
RanchForecastTests, an entry for each of your XCTestCase subclasses – for now, just
CourseTests – and an entry for each of the tests inside each of the XCTestCase
subclasses – in this case, just testCourseInitialization():
Figure 29.3 Test navigator
Your test testCourseInitialization() passes right now because it does not
make any incorrect assertions. But that does not mean too much, because it does
not make any assertions at all! As a result, nothing is being tested currently. In a
moment, you will address this by giving the test an implementation and by
having it make some assertions.
First, though, add a new Swift file called Constants.swift to the RanchForecastTests
target. Create a struct named Constants. In that struct, define the following
constants:
import Foundation
class Constants {
static let urlString =
"http://training.bignerdranch.com/classes/test-course"
static let url = NSURL(string: urlString)!
static let title = "Test Course"
static let date = NSDate()
}
You will use these constants in your implementation of
testCourseInitialization(). You will learn more about using a constants file in
the section called “Sharing Constants” , later in the chapter. Switch to
CourseTests.swift and add the following lines:
func testCourseInitialization() {
let course = Course(title: Constants.title,
url: Constants.url,
nextStartDate: Constants.date)
XCTAssertEqual(course.title, Constants.title)
XCTAssertEqual(course.url, Constants.url)
XCTAssertEqual(course.nextStartDate,
Constants.date)
}
Your test first sets up an instance of Course, providing a title, URL, and date to
the initializer. It then asserts that each field that the Course object contains is
equal to the corresponding object you passed into the initializer.
Try running your tests now. Xcode will give you a build error: “Use of unresolved
identifier 'Course'”. The problem is that the Course class is not visible from the
CourseTests class. This is caused by two factors, both related to the fact that the
classes Course and CourseTests are in separate modules.
The first problem is that the RanchForecast module is not currently visible from
CourseTests.swift. This means that none of the classes that RanchForecast
exports can be seen or used from CourseTests.swift. To fix this, import the
RanchForecast module into CourseTests.swift:
import XCTest
import RanchForecast
Now all the classes exported by the RanchForecast module are visible from
CourseTests.swift. But this by itself is not enough to fix the error presented
when trying to run the tests.
The second problem is that the RanchForecast module does not currently export
the Course class; in other words, it does not currently make the Course class
accessible from other modules. By default, in Swift, all classes, functions,
methods, and properties are internally accessible; in other words, they implicitly
have the internal access modifier. The internal access modifier means
“accessible from within the same module”, so the default behavior is that every
class, function, method, and property is only accessible from within the same
module – unless specified otherwise. In particular, Course is currently only
accessible from within the RanchForecast.
To address this, you will mark the Course class – as well as the members you are
using in testCourseInitialization() – as public. Open Course.swift and
make the following changes:
public class Course: NSObject {
public let title: String
public let url: NSURL
public let nextStartDate: NSDate
public init(title: String, url: NSURL,
nextStartDate: NSDate) {
self.title = title
self.url = url
self.nextStartDate = nextStartDate
super.init()
}
}
Now try running your tests. Xcode should successfully build the test target and
present the “Test Succeeded” dialog.
Due to the very limited functionality of the Course class, you now have complete
test coverage for it. You have tested all of its methods with every input that
results in different behavior.
A Note on Literals in Testing
When writing tests, it can often at first seem more convenient to use literals
(such as "tomato" and 54). For example, rather than creating separate variables
for each of the arguments, it may at first seem easier to write the following:
let course = Course(title: "Name",
url: NSURL(string:
"http://bignerdranch.com/")!,
nextStartDate: NSDate())
The problem with this approach, of course, is that you have to use another literal
later on when you want to use the value again:
XCTAssertEqual(course.title, "Name")
This is a problem because there is no automated checking that you used the same
literal every time. When you use a constant, by contrast, the compiler will
complain if you misspell its name. For this reason, stylish Swift programmers
use named constants rather than literals in their tests.
Creating a Consistent Testing Environment
Next, you will write tests for ScheduleFetcher. To get started, create a new Swift
source file in the RanchForecastTests called ScheduleFetcherTests.swift. In the file,
add a new class ScheduleFetcherTests, a subclass of XCTestCase.
import Foundation
import XCTest
import RanchForecast
}
Before you can write any tests against ScheduleFetcher, you need to make sure
that the class itself is visible from the RanchForecastTests target. You already
imported the RanchForecast target into ScheduleFetcherTests.swift, but you still
need to mark the class and its initializer public. Edit ScheduleFetcher.swift as
follows:
public class ScheduleFetcher {
enum FetchCoursesResult {
case Success([Course])
case Failure(NSError)
}
public init() {
You will be writing a number of tests against ScheduleFetcher. Each of those
tests should have a fresh instance of ScheduleFetcher. Having a fresh instance of
ScheduleFetcher for each test guarantees that each test is independent from the
others.
XCTest provides a convenient mechanism for setting up and tearing down an
instance of the class being tested before and after each test in the form of the
methods setUp() and tearDown(). The setUp() method is called before each test
is run and tearDown() is called after. Add a ScheduleFetcher property to
ScheduleFetcherTests and implement setUp() and tearDown() to initialize and
nil out the property:
class ScheduleFetcherTests: XCTestCase {
}
At this point, you still have not added any tests to ScheduleFetcherTests. If you
run all your tests in Xcode now, no code in ScheduleFetcherTests.swift will be
executed. Shortly, though, you will add several tests to ScheduleFetcherTests.
When you do add tests, each will run against its own distinct instance of
ScheduleFetcher. Before each test runs, setUp() will be run and fetcher will be
initialized to a new instance of ScheduleFetcher. After each test runs and before
the next test runs, tearDown() will be run and fetcher will be nil’d out. So the
code that you just wrote gives each test a blank slate – a fresh instance of
ScheduleFetcher – to work with.
struct Constants {
XCTAssertNotNil(course)
XCTAssertEqual(course.title, Constants.title)
XCTAssertEqual(course.url, Constants.url)
XCTAssertEqual(course.nextStartDate,
Constants.date)
}
Run your tests. They should all pass, including the one you just added. Set
breakpoints in setUp(), testCreateCourseFromValidDictionary(), and
tearDown() and run the tests again. Notice that setUp() is called first, then
testCreateCourseFromValidDictionary(), and finally tearDown().
Sharing Constants
All of the constants that you have used so far you have put into Constants.swift.
It would have been technically possible to add each of these constants to the top
of the test file that you used them in. However, as you have already seen, it is
quite common to use the same constants in multiple tests and in multiple test
files. On the one hand, if you do not put constants in the files where they are first
used, they will be visible from other test files – so long as you do not mark them
private. On the other hand, if you follow this approach, your shared constants
will be scattered throughout your test files and harder to track down.
For this reason, stylish Swift programmers put their shared test constants into a
separate file (or files). If they need constants to be shared only among the tests in
a single file, they mark them private and put them inside that file.
These constants are test fixtures which are shared across a number of tests. A test
fixture is the context in which a test is run; it is made up of all the objects that
the test uses.
Besides these constants, you have already seen another kind of test fixture in
ScheduleFetcherTests.swift. The fetcher property is a test fixture which is set
up implicitly before each test in which ScheduleFetcherTests is run and torn
down (implicitly) afterwards.
Generally, it is very important for tests to operate on different test fixtures.
Otherwise configuration may persist from one test to another, making your tests
be dependent on the ones that were run beforehand rather than standalone.
Making sure your tests do not rely on one another is critical: If a test is
dependent on other tests, it will be unclear what state the fixtures are in when the
test runs. As a result, it will be unclear what exactly the test is testing. Instead,
your each of your tests should be independent, with their fixtures configured
either in setUp() or in the body of the test itself.
This rule only applies to shared fixtures which can be changed by the tests. If a
shared fixture is immutable, then of course it cannot be modified by the tests.
Shared immutable fixtures, such as the constants in Constants.swift, are very
helpful. They allow you to set up only once the components you need to use in
multiple tests that you always want to be configured in the same way.
Refactoring for Testing
When you write an application without testing in mind, you will often write code
that is difficult to test. In Chapter 28, you implemented ScheduleFetcher’s
method fetchCoursesUsingCompletionHandler(_:). In that implementation, you
call NSURLSession.dataTaskWithRequest(_:completionHandler:). and pass in a
complex completion handler which transforms the triple of data, response, and
error into a FetchCoursesResult.
return result
}
Currently, this method does not compile because result is never defined. In a
moment, you will cut the core of the completion handler from
fetchCoursesUsingCompletionHandler(_:) and paste it into this method. Once
you have done that, this method will contain the logic for transforming the result
of the attempted HTTP request into a FetchCoursesResult.
In the body of the fetchCoursesUsingCompletionHandler(_:) method, select the
whole conditional block, from if data == nil { to the closing right brace of
the final else block. Cut those lines by clicking Edit → Cut.
func
fetchCoursesUsingCompletionHandler(completionHandler:
(FetchCoursesResult) -> (Void)) {
let url = NSURL(string:
"http://bookapi.bignerdranch.com/courses.json")!
let request = NSURLRequest(URL: url)
let task = session.dataTaskWithRequest(request,
completionHandler: {
(data, response, error) -> Void in
var result: FetchCoursesResult
if data == nil {
result = .Failure(error)
}
else if let response = response as?
NSHTTPURLResponse {
println("\(data.length) bytes, HTTP \
(response.statusCode).")
if response.statusCode == 200 {
result = .Success([]) // Empty array
until parsing is added
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Bad status code \
(response.statusCode)")
result = .Failure(error)
}
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Unexpected response object.")
result = .Failure(error)
}
NSOperationQueue.mainQueue().addOperationWithBlock({
completionHandler(result)
})
})
task.resume()
}
With this code still in your clipboard, paste it into
resultFromData(_:response:error:).
public func resultFromData(data: NSData!, response:
NSURLResponse!, error: NSError!)
-> FetchCoursesResult {
var result: FetchCoursesResult
if data == nil {
result = .Failure(error)
}
else if let response = response as?
NSHTTPURLResponse {
println("\(data.length) bytes, HTTP \
(response.statusCode).")
if response.statusCode == 200 {
result = .Success([]) // Empty array
until parsing is added
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Bad status code \
(response.statusCode)")
result = .Failure(error)
}
}
else {
let error = self.errorWithCode(1,
localizedDescription:
"Unexpected
response object.")
result = .Failure(error)
}
return result
}
Now, call resultFromData(_:response:error:) from the completion handler in
fetchCoursesUsingCompletionHandler(_:):
func
fetchCoursesUsingCompletionHandler(completionHandler:
(FetchCoursesResult) -> (Void)) {
let url = NSURL(string:
"http://bookapi.bignerdranch.com/courses.json")!
let request = NSURLRequest(URL: url)
let task = session.dataTaskWithRequest(request,
completionHandler: {
(data, response, error) -> Void in
var result: FetchCoursesResult
= self.resultFromData(data, response:
response, error: error)
NSOperationQueue.mainQueue().addOperationWithBlock({
completionHandler(result)
})
})
task.resume()
}
Currently, this does not compile. The problem is that the method
resultFromData(_:response:error:) is public but returns a
FetchCoursesResult, whose visibility is currently internal. A method must not
be any more visible than the type of any of its parameters or its return type. To
fix this, mark FetchCoursesResult as public.
public enum FetchCoursesResult {
case Success([Course])
case Failure(NSError)
}
Now make sure that your app compiles and runs as before.
At this point, it is much easier to test the transformation from (data, response,
error) to FetchCoursesResult. You will do so by calling
resultFromData(_:response:error:) with valid JSON data, an ‘OK’ HTTP
response, and no error. To write this test, you will need to have JSON data and a
response to pass. Open Constants.swift and add three new immutable shared
fixtures for the JSON data and the HTTP response inside the Constants struct:
static let coursesDictionary = ["courses" :
[validCourseDict]]
static let okResponse = NSHTTPURLResponse(URL: url,
statusCode: 200,
HTTPVersion: nil,
headerFields: nil)
options: .allZeros,
error: nil)!
You can now implement the test using these fixtures. Open
ScheduleFetcherTests.swift and add a new test case:
func testResultFromValidHTTPResponseAndValidData() {
let result =
fetcher.resultFromData(Constants.jsonData,
response:
Constants.okResponse,
error: nil)
switch result {
case .Success(let courses):
XCTAssert(courses.count == 1)
let theCourse = courses[0]
XCTAssertEqual(theCourse.title,
Constants.title)
XCTAssertEqual(theCourse.url, Constants.url)
XCTAssertEqual(theCourse.nextStartDate,
Constants.date)
default:
XCTFail("Result contains Failure, but Success
was expected.")
}
}
The test uses resultFromData(_:response:error:), the method you added to
ScheduleFetcher, to create a FetchCoursesResult. It then verifies that the result
is as it should be: that all of its fields are equal to the appropriate values from
Constants.swift. If the result does not match FetchCoursesResult.Success,
then XCTFail is called.
Although you have not passed explicit error messages to the other asserts you
have used, you should always specify one when calling XCTFail. Other asserts
are able to provide some context about the reason for their failure, but XCTFail is
unable to.
You had to do a bit of refactoring to bring this section of code under test. Once
you get into the habit of writing tests for your code as or before you write your
code, you will be able to identify hard-to-test patterns quickly and use patterns
that better lend themselves to testing.
For the More Curious: Access Modifiers
As you have already seen in this chapter, Swift has a mechanism for controlling
access to a class, method, or property – among other things. This is done via
access modifiers, keywords which are applied to the entities whose visibility is
being configured.
In Swift, there are three levels of visibility and three corresponding keywords by
which the visibilities are specified:
internal
A class, method, or other entity which has internal visibility can be seen from
all files inside the same module as the class. An entity with internal visibility
cannot, however, be seen from files in a different module.
internal is the default level of visibility for an entity. When you first add a
class to your app target in a new file, all other files in your app target will be
able to use the class – until you change the visibility.
Since it is the default visibility, the main usage of the internal keyword is to be
explicit about an entity’s visibility:
internal struct Constants { ... }
internal struct Constants { ... }
public
An entity with public visibility can be seen from any module which imports the
module where the public entity is defined.
Consider the places you have used the public keyword over the course of this
chapter. In almost all of those usages, you added the public keyword to make
classes, methods, and properties from the RanchForecast app target visible from the
RanchForecastTests test target. For example, you marked the Course class public:
public class Course: NSObject { ... }
Marking the class public allowed it to be seen from the test target. This in turn
allowed you to write tests against it.
public class Course: NSObject { ... }
private
Entities with private visibility can only be seen from within the same file where
the entity is defined.
This can be useful to ensure that client code does not muck about with your
class’s internals. When you mark methods and properties private, you clarify
which methods and properties are part of your class’s API (those which are not
marked private).
There are a number of subtle constraints which are imposed on an entity’s
visibility depending on the visibility of the entities it is related to. For example, a
method or function cannot be more visible than the least visible parameter or
return type. This prevents issues such as a public function returning a private
type. There are several more constraints, and they are discussed in detail in
Apple’s Swift book.
Swift’s access control (or at least its current implementation) puts testing and
API design in tension. Typically, when designing a class, you want to be very
selective about which methods it exposes to its users through its API. This
means marking many things private. On the other hand, when testing that class,
you often want to test that the implementation details of your class function
properly. This means that you must mark those implementation details public so
that they are visible from the test target.
The approach adopted in this book, which you used over the course of this
chapter, is to sacrifice interface purity for practicality and to mark methods
public whenever you want to test them. The downside of this approach is that
you do not get to make use of different visibility levels to control which of your
class’s methods and properties should be used by client code. The upside of this
approach is that you are testing the code in your app module, and that all of that
code can be tested.
For the More Curious: Asynchronous Testing
Suppose that you want to test a method like the following:
func
doWorkInBackgroundWithCompletion(completionHandler:
(Bool, NSError?) -> (Void))
This method takes a completion handler which returns asynchronously,
sometime after the function returns. You would like to test that this function
does eventually call its completion handler, and that when the completion
handler is called, it is passed true for its first argument, and nil for its second.
The techniques that have been discussed so far in the chapter do not allow for
testing this method. For testing an asynchronous method, XCTest provides
XCTestExpectation.
In addition to notifications, you can also create expectations for KVO using
keyValueObservingExpectationForObject(_:keyPath:expectedValue:). This
method creates an expectation which will be automatically fulfilled when the
specified object changes the value at the specified keyPath.
Challenge: Make Course Implement
Equatable
You have implemented several tests (testCourseInitialization(),
testCreateCourseFromValidDictionary(),
testResultFromValidHTTPResponseAndValidData(_:), testFetcherSuccess())
that compare the properties of Course objects to determine that they are equal.
Make Course adopt the Equatable protocol allowing instances of Course to be
compared. Take advantage of this by using XCTAssertEqual in your tests.
To make Course adopt Equatable you will need to implement a free function
with this signature: public func ==(lhs: Course, rhs: Course) -> Bool
Challenge: Improve Test Coverage of Web
Service Responses
In ScheduleFetcherTests, you currently only have a single test
(testResultFromValidHTTPResponseAndValidData(_:)) against
ScheduleFetcher’s method resultFromData(_:response:error:). This test
verifies the behavior of the method when it is passed valid JSON data and an OK
(200) HTTP response.
Write two more tests against resultFromData(_:response:error:). In the first
test, call the method with a 404 response and no data. In the second test, call the
method with a 200 response and invalid JSON data.
Invalid JSON data here means data that cannot be used by NSJSONSerialization.
One way to create invalid JSON data is by turning a string into data:
let invalidJsonDataString = "This string will not
transform into valid JSON data"
let invalidJsonData =
invalidJsonDataString.dataUsingEncoding(NSUTF8StringEncoding,
allowLossyConversion: true)!
Challenge: Test Invalid JSON Dictionary
The first challenge in Chapter 28 was to improve the handling of faulty JSON by
ScheduleFetcher’s courseFromDictionary(_:) method.
In this chapter, you only tested that the method behaved correctly when it was
provided with valid JSON. Create another test where you pass invalid JSON to
the method. Assert that the Course object that is returned is nil.
Think about how much easier it would have been to complete the original
challenge if you were able to run your new unit test that passes invalid JSON to
courseFromDictionary(_:) as you were implementing the method. Having tests
makes writing code easier!
30
View Controllers
Cocoa user interfaces have changed a lot over the years. For many years, it was
not uncommon for a complicated application to have a number of windows, one
for each functional area of the application. NSWindowController is a sufficient
building block for applications like this: one controller for each window, perhaps
with responsibility farmed out to smaller controller objects such as
NSArrayController. Nowadays it is much more common for the bulk of an
application’s UI to be composed within a single window. Applications like Pages,
Numbers, Keynote, and Xcode are examples of this.
Consider the project window in Xcode (Figure 30.1). Imagine implementing this
window with an NSWindowController. It would contain hundreds of outlets and
action methods, countless bindings, auxiliary properties and helper methods.
Thankfully this is just a thought experiment, as Cocoa provides a solution to
make this problem much more manageable: view controllers. View controllers
are much like window controllers, except that they are concerned with
controlling the contents of a view.
Figure 30.1 The Xcode project window
To understand how view controllers are used, first consider how the view
hierarchy of the Xcode window is constructed (Figure 30.2). Below the toolbar, at
the top level there is a split view containing the navigator, center, and utility
areas. The center area is split between the editor and debug areas. The utility
area is split between the inspector and library panes. The individual navigators,
inspectors, and libraries all have their own views.
Figure 30.2 Xcode’s project window is composed of a hierarchy of views
and view controllers
Wouldn’t it make sense to have a separate controller for each of these areas? A
controller for the navigator area, a controller for the inspector area, a controller
for the debugger, and one for each type of editor. Furthermore, it would be ideal
if each of the navigator and inspector tabs had its own controller.
One controller for each chunk of the view hierarchy: a logical place for all of the
action methods, outlets, helpers and so forth that pertain to that area of the UI.
View controllers are that controller, and in Cocoa the NSViewController class
serves this purpose.
NSViewController
As with NSWindowController, you employ view controllers in your project by
subclassing NSViewController. The view hierarchy is lazily loaded. When the
view property is accessed, the view controller calls loadView(), which defaults to
loading the view from a NIB file. However, you can override loadView() to
create the view programmatically, as you will see in Chapter 31. Just like
window controllers, except that the view controller has a view outlet. Consider
the parallels to NSWindowController as you read over this subset of the
NSViewController API:
class NSViewController : NSResponder {
var nibName: String? { get }
var representedObject: AnyObject?
var title: String?
var view: NSView
func loadView()
// Added in OS X 10.10:
func viewDidLoad()
func viewWillAppear()
func viewDidAppear()
func viewWillDisappear()
func viewDidDisappear()
...
}
Starting the ViewControl Application
In Xcode, create a new Cocoa Application named ViewControl. Uncheck Use Storyboards,
Create Document-Based Application, and Use Core Data.
In the first phase of the ViewControl application, you will create a view controller
to display an image. The view controller will be presented as the content view
controller of a window (Figure 30.3). That is, the view controller will manage
the contents of the window.
Figure 30.3 Object diagram for the ViewControl application
Open MainMenu.xib and delete the window supplied by the template. You will
create your own window programmatically in the AppDelegate.
Next, create a new Cocoa Class named ImageViewController. Set it to subclass
NSViewController and check Create XIB file. As with subclassing
NSWindowController, this creates two files: ImageViewController.swift and
ImageViewController.xib.
To tell the view controller which NIB file to open, you will override the nibName
property. Open ImageViewController.swift and make this change. While you
are there, define the image property which you will soon bind the image view to.
class ImageViewController: NSViewController {
}
Aside: In 10.10, the default NIB name is the name of the class, which should
mean that you do not need to override this. Unfortunately this only works well
for Objective-C because the string name of this Swift class includes the module
name, which in this case is ViewControl.ImageViewController.
Open ImageViewController.xib. As with window controllers, File's Owner
represents the view controller. Control-click on File's Owner to open the
connections panel. You will see that the view outlet is connected to the view in
the XIB file.
Drag an Image View onto the view and size it to fill the view. Use Auto Layout to
pin it to the edges of the view. Select the image view and switch to the attributes
inspector. Set Scaling to Proportionally Up or Down.
With the image view still selected, in the bindings inspector bind the Value of the
image view to the image property on the view controller. Remember, File's
Owner represents the view controller in this context. Therefore you must bind
the image view’s Value to File's Owner with a Controller Key of image (Figure 30.4).
Figure 30.4 Binding image view's Value attribute to File's Owner
In AppDelegate.swift, change the window outlet to an optional window property.
Then, in applicationDidFinishLaunching(_:), create an instance of the view
controller, assign the image property, and create and show a window with this
new view controller as its contents:
@IBOutlet weak var window: NSWindow!
var window: NSWindow?
func applicationDidFinishLaunching(aNotification:
NSNotification) {
let flowViewController = ImageViewController()
flowViewController.title = "Flow"
flowViewController.image = NSImage(named:
NSImageNameFlowViewTemplate)
In AppDelegate.swift, create the second image view controller as well as the tab
view controller:
func applicationDidFinishLaunching(aNotification:
NSNotification) {
let flowViewController = ImageViewController()
flowViewController.title = "Flow"
flowViewController.image = NSImage(named:
NSImageNameFlowViewTemplate)
let columnViewController =
ImageViewController()
columnViewController.title = "Column"
columnViewController.image = NSImage(named:
NSImageNameColumnViewTemplate)
tabViewController.addChildViewController(columnViewController)
Despite these limitations, if you are targeting OS X 10.9 and earlier and your
application would benefit from view controllers, we believe it is worth the extra
effort to work around these limitations and use view controllers.
You can include a view controller in the responder chain manually by patching it
in. This should be done whenever the view controller’s view is added to the view
hierarchy. For example, when setting an NSBox’s contentView to the view of a
view controller.
The following extension on NSViewController makes this a snap:
extension NSViewController {
func patchResponderChain() {
if view.nextResponder != self {
let resp = view.nextResponder
view.nextResponder = self
nextResponder = resp
}
}
}
Here is how you might use it:
override func viewDidLoad() {
super.viewDidLoad()
box.contentView = someViewController.view
someViewController.patchResponderChain()
someViewController.patchResponderChain()
}
Challenge: SpeakLineViewController
Create a new NSViewController subclass that contains the functionality of the
SpeakLine exercise you created back in Chapter 6. Add a new tab to the
NSTabViewController containing the SpeakLine UI. All of your SpeakLine code
should be confined to the SpeakLineViewController.
Challenge: Programmatic View Controller
ImageViewController is simple enough that it does not benefit much from having
a XIB file. Create your own ImageViewController which is purely programmatic.
Override loadView() to build the view(s) that you need and assign the root of
that hierarchy to the view property. Do not call super.loadView(), as it will
attempt to load the view hierarchy from a NIB file. As we mentioned at the
beginning of the chapter, loadView() is called to lazily load the view hierarchy
the first time the view property is accessed.
If you need a hint, see NerdTabViewController in Chapter 31.
Challenge: Add a Window Controller
In the exercise above you created a window with a content view controller, but
did not create a window controller. In fact, NSWindowController also has a
contentViewController property, which mirrors the window’s
contentViewController. Create an NSWindowController subclass called
MainWindowController and move the code for instantiating the view controller
within it.
31
View Swapping and Custom Container View
Controllers
In the last chapter you used NSTabViewController to allow the user to flip
between two view controllers. As we discussed, NSTabViewController is an
example of a container view controller, which is a view controller that manages
the display of its child view controllers’ views. NSTabViewController displays its
child view controllers by means of an NSTabView. The controller handles
populating the tab view with the contents of the child view controllers as needed.
But how does the tab view work?
View Swapping
When the tab view changes between tabs it uses view swapping, which is a fancy
way of saying that it changes out its subviews. It removes the view for the pane
that it had been showing and adds the new view. The view hierarchy
management methods discussed in Chapter 17, addSubview(_:) and
removeFromSuperview(), do the heavy lifting.
func reset() {
}
override func
insertChildViewController(childViewController:
NSViewController,
atIndex index: Int) {
super.insertChildViewController(childViewController,
atIndex: index)
if viewLoaded {
reset()
}
}
override func
removeChildViewControllerAtIndex(index: Int) {
super.removeChildViewControllerAtIndex(index)
if viewLoaded {
reset()
}
}
}
By checking viewLoaded in the child view controller insert and remove methods
you can avoid loading the view unnecessarily. Like NSWindowController,
NSViewController loads the view the first time the view property is accessed.
The bulk of the work in this class will be in reset(), which will rebuild the view
hierarchy. Before you implement reset(), add two properties – box and buttons
– and two methods: selectTabAtIndex(_:) and selectTab(_:).
class NerdTabViewController : NSViewController {
Part of the challenge here is that you want NerdTabViewController to work with
any NSViewController. So you look for a property on NSViewController where
you can assign the image and find representedObject. This would work, but it is
not very discoverable. Furthermore its type is AnyObject?. We discourage using
representedObject in general for this reason: it is better to create a strongly-
typed property specific to your model object.
ImageViewController already has an image property. It would not do for
NerdTabViewController to detect that it has an ImageViewController child. It
would defeat the modularity provided by container view controllers, or
necessitate adding another special case to NerdTabViewController every time
you wanted to support a new view controller. You need a way for any view
controller to opt into providing an image in a type-safe, preferably predictable
and self-documenting manner.
Protocols provide a fine means of accomplishing this. A protocol makes it clear
what the expectations are and allows the NerdTabViewController to check for
conformance.
Define the ImageRepresentable protocol in NerdTabViewController.swift:
import Cocoa
@objc
protocol ImageRepresentable {
var image: NSImage? { get }
}
Create a new Cocoa Application project in Xcode and call it RanchForecastSplit. Set
the language to Swift, and this time check Use Storyboards. Leave Create Document-
Based Application and Use Core Data unchecked.
In the project navigator you will notice that the selection of files is different than
usual. AppDelegate.swift is still there, but MainMenu.xib is gone and in its place
is Main.storyboard. ViewController.swift is new as well. Click on
Main.storyboard to open it in the editor. With the document outline fully
expanded it should resemble Figure 32.2.
Figure 32.2 New storyboard with window and view controller
Notice that there are three scenes in this storyboard: the Application Scene, Window
Controller Scene, and View Controller Scene. A gradient arrow points to the window
controller scene from the left; this indicates that it is the initial controller for the
storyboard.
You learned about window controllers in Chapter 5. View controllers are the
same concept, just for views. To create a view controller you subclass
NSViewController. And just like a window controller has a reference to a
window, so does a view controller have a reference to a view.
Back in the storyboard, an arrow points from the window controller scene down
to the view controller scene. This arrow is the sole segue of this storyboard,
establishing a relationship between the window controller’s window content and
the view controller. You can tell this by selecting the segue. The text in the jump
bar reads, Relationship "window content" to View Controller. In other words, this segue
stipulates that the contents of the view controller's view will be shown as the
window’s content.
The supplied view controller does not fit your needs for this exercise. Expand
the View Controller Scene in the document outline. Select the View Controller and delete
it. The segue between it and the window will disappear and the window will
display the text, No Content View Controller. Select ViewController.swift in the
project navigator and move it to the trash.
Select Main.storyboard once again and drag a Vertical Split View Controller onto the
canvas. It may be tricky to drop it in the correct place; try to steer clear of other
scenes in the storyboard. Drop the Split View Controller below the Window Controller.
To create the relationship segue between the window controller and the split
view controller, control-drag from the blue Window Controller icon in the border on
top of the window controller down to the split view controller. In the pop-up
Relationship Segue window that appears, select window content (Figure 32.3).
Figure 32.3 Setting the window content to the split view controller
When you are done you should see a segue between the two resembling
Figure 32.4. You may need to move the scenes apart from each other to see it.
Figure 32.4 Window content relationship is set
Explore the complete graph by panning within the canvas. As you can see, it can
get rather large, even for a relatively simple application. Use the pinch gesture to
zoom out, or the Editor → Canvas → Zoom menu, to see more of the canvas.
Of the two view controllers connected to the Split View Controller, the topmost one
represents the left pane of the split view. Make this view more narrow and place
it at the left of the other view controller to better represent the actual UI layout at
runtime, as shown in Figure 32.5.
Figure 32.5 Overall storyboard graph
Before continuing, zoom back in to 100%. Some editing is not allowed when the
storyboard editor is zoomed out.
Because this exercise builds on the ScheduleFetcher and Course classes you
created in Chapter 28, you need to add the corresponding Swift files to this
project. Drag ScheduleFetcher.swift and Course.swift into this project’s
RanchForecastSplit group. Check Copy items if needed and make sure that RanchForecastSplit
is checked to add the file to the target.
Note: If you do not check Copy items if needed, Xcode will only reference the files at
their existing path. This means that when you edit a file in one project, it will
also be edited in the other project. Most of the time, you will want to create a
new copy of the file for a new project.
fetcher.fetchCoursesUsingCompletionHandler {
(result) in
switch result {
case .Success(let courses):
println("Got courses: \(courses)")
self.courses = courses
case .Failure(let error):
println("Got error: \(error)")
NSAlert(error: error).runModal()
self.courses = []
}
}
}
}
Back in Main.storyboard, select the left, narrower view controller. In the identity
inspector, set the Class to CourseListViewController. Note that the title of the
scene changes to Course List View Controller.
As you did in Chapter 28, you are going to add a table view to the view
corresponding to the left (master) pane of the split view:
1. Add the array controller
Drag an Array Controller onto the Course List View Controller scene, as shown in
Figure 32.7. Drop it onto the top section of the scene on the canvas.
Alternatively, you can drag it into the document outline.
Connect the arrayController outlet on Course List View Controller to the array
controller you just added.
It is important that it is added to the correct scene within the storyboard;
otherwise it will not be available for connections such as the one you just
made, or for binding from the objects within that scene. Although the
scenes within a storyboard are shown on the same canvas, they are only
connected to each other through segues.
Figure 32.6 Dragging the array controller onto the course list view
controller scene
Figure 32.7 Dragging the array controller onto the course list view
controller scene
2. Bind the array controller
Bind the array controller’s Content Array to the Course List View Controller’s
self.courses.
Note that binding to Course List View Controller is analogous to binding to File's
Owner in a traditional XIB file setup.
Run the app. Make sure that the table view is populated after the schedule is
fetched.
Run the application. You should see the list of courses in the left pane, but
selecting different courses does not yet load them in the web view. You have yet
to connect the two.
Connecting the Course List Selection with the
Web View
In order to load the course URL in the web view on selection you will need to
trigger a call to loadURL(_:) when the table view selection changes.
Knowing what you know of Cocoa, how would you accomplish this? Come up
with two or three approaches before reading on.
Got two or three? Here are some that we came up with:
Directly couple the two view controllers by adding a reference from
CourseListViewController to WebViewController. Call a method on the
WebViewController whenever the selected course changes.
This would be the simplest approach and it would also be effective. But it
has a serious disadvantage in the way that it couples the two view
controllers directly to each other. This makes it harder to change the design
of the app in the future.
Post a notification from the CourseListViewController when the selection
changes and observe that notification in the WebViewController.
This approach reduces coupling somewhat: it is one-way instead of
bidirectional (the WebViewController needs to know about the notification
originated by the CourseListViewController). It does have the advantage
that multiple parties could be notified of the selection change, however.
Create a CourseListViewControllerDelegate protocol and have the
WebViewController conform to it. A new delegate method,
courseListViewController(_:selectedCourse:), would trigger the URL
loading.
More direct than using notifications, but it does potentially introduce
coupling between sibling view controllers, like the last approach. What if
you wanted multiple parts of the UI to react to a selection change?
Add a closure property to the CourseListViewController to be called when
the selection changes.
This is a pretty good option. Be careful not to create a strong reference
cycle. Where is the closure set, however?
Use KVO to observe the array’s selectedObjects property.
This works, but you should be wary of relying on KVO for simple tasks. It
is often not apparent that KVO behavior is being relied upon, and the
compiler cannot check it for you (for example, if you change the name of
the property being observed).
The actual approach you are going to use in this case is to create a third view
controller which will contain the master and detail view controllers. This parent
view controller will facilitate communication between the two by being a
delegate of the CourseListViewController and doing the work of loading URLs
in the WebViewController. For the parent view controller, you will subclass
NSSplitViewController. See Figure 32.10.
masterViewController.delegate = self
detailViewController.loadURL(defaultURL)
}
// MARK: CourseListViewControllerDelegate
func courseListViewController(viewController:
CourseListViewController,
selectedCourse:
Course?) {
if let course = selectedCourse {
detailViewController.loadURL(course.url)
}
else {
detailViewController.loadURL(defaultURL)
}
}
}
Finally, you need to make this class be used instead of a regular
NSSplitViewController when the app is run. Open Main.storyboard. Expand the
Split View Controller Scene and select the Split View Controller. In the identity inspector,
change the Class to MainSplitViewController (Figure 32.11).
Figure 32.11 Change the Class attribute to MainSplitViewController
That is it. Run the application and click between courses. You will see the web
view loading the page for the selected course.
For the More Curious: How is the Storyboard
Loaded?
You might be wondering how the storyboard gets loaded in the first place. Just
like with MainMenu.xib, the answer is in the Info.plist.
If you check the Info.plist for this project you will see that the key
NSMainStoryboardFile is set to Main.
Scenes can also be loaded from storyboards programmatically. The
NSStoryboard class provides this, much like NSBundle does for NIB-loading.
Within a storyboard, one scene can be designated as the initial controller. This is
used in the application’s Main.storyboard to designate the application window to
be opened at launch.
Controllers within a storyboard can also be instantiated by their identifier, which
is set in the identity inspector. If you had a view controller with an identifier of
Palette, you could instantiate the view controller and show it in a window using
the following code:
if let storyboard = NSStoryboard(name: "Main", bundle:
nil) {
if let vc =
storyboard.instantiateControllerWithIdentifier("Palette")
as? NSViewController {
paletteWindow =
NSWindow(contentViewController: vc)
paletteWindow.makeKeyAndOrderFront(nil)
}
}
33
Core Animation
In Chapter 17 you subclassed NSView to perform custom drawing in your
application. You issued drawing commands and Quartz drew them on the screen.
This is sufficient for many applications, where you might need to draw graphs or
use basic, relatively static controls. This style of drawing can be intensive,
however, and making it perform well requires that the developer to expend a lot
of effort. For example, you want to only draw the parts of the view that have
changed.
Core Animation takes a much different tack: instead of issuing drawing
commands, the programmer composes the view by creating layers. Layers can
display images or Bezier paths, and their appearance can be further manipulated
by setting their size, opacity, shadow, and more.
Layers are arranged in a tree. When the programmer changes a layer, Core
Animation does the hard work of figuring out how to redraw the tree in a very
efficient manner. Behind the scenes, the drawing is done using OpenGL.
As the name suggests, Core Animation also makes it relatively easy to create
very smooth animations – but it is an extremely useful tool even if you do not
intend to animate anything at all.
CALayer
Layers are represented by the CALayer class, which is the base class for all kinds
of layers. A layer’s appearance can be configured in numerous ways through its
properties. It is also possible to mask a layer with another or to apply a 3D
transform to give the layer perspective. Most properties can be animated simply
by setting a new value.
Layers are similar to views in some ways. Like views, layers are arranged in a
hierarchy. To display a layer, you set the layer upon a view (a layer-hosting
view). This root layer may then have sublayers, and so forth. You can also draw
into a CALayer, not unlike a custom view, but it is more common to rely on
setting the layer’s properties to customize its appearance.
Unlike views, however, layers are not typically subclassed. Also, they do not
receive user input events (mouse, keyboard) and are not part of the responder
chain. If you want the user to be able to manipulate layers you will need to write
the event handling code yourself. Layers do support hit testing to aid in this.
Below is a sampling of the properties available on CALayer:
class CALayer: NSObject {
var bounds: CGRect
var position: CGPoint
var zPosition: CGFloat
var frame: CGRect
var opacity: CGFloat
var hidden: Bool
var mask: CALayer!
var borderWidth: CGFloat
var borderColor: CGColor!
var cornerRadius: CGFloat
var shadowOpacity: CGFloat
var shadowRadius: CGFloat
var shadowOffset: CGSize
var shadowColor: CGColor!
var actions: [NSObject : AnyObject]! // Defaults
var actions: [NSObject : AnyObject]! // Defaults
to nil!
var delegate: AnyObject! // NSObject
(CALayerDelegate)
// ...
}
Manipulating a layer’s properties will animate it, but you sometimes need more
control. CAAnimation and its subclasses let you fine-tune animations.
CATransaction can be used to group and synchronize multiple animations, as
well as to disable animations temporarily.
Scattered
Let’s create an application that uses Core Animation to drive the interface. This
particular application will load all the images it finds in a folder and display
them using CALayer objects (Figure 33.1).
Figure 33.1 Completed application
In Xcode, create a new project called Scattered. Make sure that Swift is selected as
the Language. You will be using a storyboard in this project, so check Use Storyboards.
The project will not be document-based or use Core Data.
Open ViewController.swift and make the following additions:
import Cocoa
textContainer.addSublayer(textLayer)
}
Let’s discuss what you just set up. When the application launches, you configure
the view to be layer-hosting by first assigning its layer property and then setting
its wantsLayer property to true. The order of these calls is important. If you had
not assigned the layer first, the view would have been configured as layer-
backed, which is designed for animating views rather than Core Animation layer
hierarchies.
Next, you configure two layers: textContainer and textLayer. The first layer,
textContainer, is an instance of CALayer (the most basic layer type) and will
create a rounded rectangle filled black with a white border and a shadow. The
second layer, textLayer, is a CATextLayer, which, not surprisingly, can be used
to display text. Note how properties are used extensively in Core Animation to
configure the various graphical elements.
The first layer, textContainer is added to the view’s layer, and textLayer is
added to the textContainer (Figure 33.2).
Figure 33.2 Layer hierarchy
Note that the anchorPoint for each layer is set to (0, 0), which equates to the
lower-left corner. The default anchorPoint for a layer is (0.5, 0.5), which is
the center of the layer. The anchorPoint governs the position of the layer relative
to its position property.
Next, the text property is set. In a moment, you will implement this property’s
didSet to set the bounds of the layers. The bounds describe the size of the layer;
by default, layers have bounds of (0, 0, 0, 0). Layers do have a frame
property, like views, but it is more common to set the position and bounds
independently.
Next, implement the text property’s didSet observer.
var text: String? {
didSet {
let font =
NSFont.systemFontOfSize(textLayer.fontSize)
let attributes = [NSFontAttributeName :
font]
var size =
text?.sizeWithAttributes(attributes) ??
CGSize.zeroSize
// Ensure that the size is in whole
numbers:
size.width = ceil(size.width)
size.height = ceil(size.height)
textLayer.bounds = CGRect(origin:
CGPoint.zeroPoint, size: size)
textLayer.superlayer.bounds
= CGRect(x: 0, y: 0, width: size.width
+ 16, height: size.height + 20)
textLayer.string = text
}
}
Then, implement thumbImageFromImage(_:) and addImagesFromFolderURL(_:).
While you may not be familiar with the specific APIs being used, you should
have a general idea of what they are doing.
func thumbImageFromImage(image: NSImage) -> NSImage {
let targetHeight: CGFloat = 200.0
let imageSize = image.size
let smallerSize
= NSSize(width: targetHeight *
imageSize.width / imageSize.height,
height: targetHeight)
return smallerImage
}
var allowedFiles = 10
let thumbImage =
thumbImageFromImage(image)
presentImage(thumbImage)
let t1 =
NSDate.timeIntervalSinceReferenceDate()
let interval = t1 - t0
text = String(format: "%0.1fs",
interval)
}
}
}
}
This will not compile yet, because addImagesFromFolderURL(_:) calls
presentImage(_:), but presentImage(_:) is not implemented. Implement
presentImage(_:) in ViewController to animate the supplied image into view,
starting from a tiny speck in the middle and expanding out to a thumbnail
version at a random point on the view: func presentImage(image: NSImage) {
let superlayerBounds = view.layer!.bounds let center = CGPoint(x:
superlayerBounds.midX, y: superlayerBounds.midY) let imageBounds =
CGRect(origin: CGPoint.zeroPoint, size: image.size) let randomPoint =
CGPoint(x:
CGFloat(arc4random_uniform(UInt32(superlayerBounds.maxX))), y:
CGFloat(arc4random_uniform(UInt32(superlayerBounds.maxY)))) let
timingFunction = CAMediaTimingFunction(name:
kCAMediaTimingFunctionEaseInEaseOut) let positionAnimation =
CABasicAnimation() positionAnimation.fromValue = NSValue(point:
center) positionAnimation.duration = 1.5
positionAnimation.timingFunction = timingFunction let boundsAnimation
= CABasicAnimation() boundsAnimation.fromValue = NSValue(rect:
CGRect.zeroRect) boundsAnimation.duration = 1.5
boundsAnimation.timingFunction = timingFunction let layer = CALayer()
layer.contents = image layer.actions = ["position" : positionAnimation,
"bounds" : boundsAnimation] CATransaction.begin()
view.layer!.addSublayer(layer) layer.position = randomPoint layer.bounds
= imageBounds CATransaction.commit() }
That is it! Run the application. You should see ten images animate out from the
center of the window.
Implicit Animation and Actions
Before we talk about what is going on in presentImage(_:), let’s look at how the
most basic animation is done with Core Animation. Imagine that you have a
CALayer called layer that is displayed on the screen. Suppose that you set its
position like so: layer.position = CGPoint(x: 50, y: 50)
This simple action animates the layer from its current position to the new
position: implicit animation. Many properties of layers can be animated by
simply setting them. The didSet observer of the text property uses implicit
animation to change the size of the black status bubble.
What if you want to customize these animations? As it turns out, there are
several styles for achieving customization, which can make Core Animation
rather confusing. The most straightforward way to customize animations is to
modify a CALayer’s actions property, which contains a dictionary. This
dictionary associates properties (string keys) with the animations (instances of
CAAnimation subclasses) to be used when animating those properties. The
actions dictionary is used by Core Animation to determine what to do when a
property is assigned.
CABasicAnimation is, well, the most basic animation class. It has two important
properties: fromValue and toValue. By setting one or both of these, you will
influence the start and end values of the property you are animating. In
presentImage(_:), note that you set only the fromValue. Thus, later in the
method when you assign property and bounds, Core Animation can look in the
actions dictionary to determine what animation to use for those properties.
Because only fromValue is set, the properties will be animated from fromValue to
the value that you assigned. Specifically, you animate the position from the
center to a random point and the bounds from zero to the size of the thumbnail,
simultaneously.
Note that to have the layer display the image, you simply assign it to the
contents property. The contentsGravity property affects how contents is
scaled (or not scaled). In this application, the layers have been sized to match the
size of the image, so no scaling is necessary.
Last, your use of CATransaction is notable. CATransaction enables you to group
several changes to be executed at once by surrounding them in calls to
CATransaction.begin() and CATransaction.commit(). Try commenting out the
surrounding begin() and commit() calls. You may notice some flickering in the
display as the layers are shown before the animation begins.
CATransaction has methods to affect changes on the actions within the current
transaction, such as changing the duration, timing functions, or – perhaps most
useful – disabling all actions, which turns off animations:
CATransaction.setDisableActions(true)
You may have noticed that Scattered presently limits the number of files loaded to
ten. If you comment out that limit, you will see why. We will address that in the
next chapter.
More on CALayer
CALayer allows you to control quite a bit about its appearance through its
properties. But what if that were not enough: What if you wanted to do custom
drawing in a CALayer? The CALayerDelegate method drawLayer(_:inContext:)
allows you to do just that with Core Graphics/Quartz.
However, much of the time, you will simply want to control a few common
things:
an image
the background color
whether the corners are rounded and, if so, how much
an image filter to run the contents of the layer through
In these cases and others like it, you can simply modify the layer’s properties.
Subclasses of CALayer also make particular kinds of drawing easier:
As you already saw, drawing text on a layer is easier if the layer is an
instance of CATextLayer.
CAShapeLayer makes drawing a stroked and/or filled path simple.
CAGradientLayer displays a configurable gradient.
Getting OpenGL calls onto a layer is easier if the layer is a subclass of
CAOpenGLLayer.
Introducing Instruments
Instruments is a tool for analyzing a running program. The tool has many different
plug-ins, called instruments, that enable you to look at various aspects, usually
performance related, of the running application.
In Xcode, open the Product menu and select Profile. Xcode will use the Release build
configuration (configurable in the Scheme Editor) to rebuild the project and will
then start Instruments. In the template chooser, select Time Profiler and click Choose
(Figure 34.2). Instruments will present a new window where all the data that will be
collected will eventually be presented. Click the record button in the top left of
the window. Instruments will then run Scattered and start collecting data. Once the
images have animated, click the black stop button in the top left of Instruments to
stop profiling.
Figure 34.2 Choosing the Time Profiler in Instruments
The Instruments interface has several parts. The instruments list is at the upper left;
in this case, only one instrument, Time Profiler, is being run. To the right is the
track pane, which displays graphical data over time related to each of the
instruments being run. Looking at the graph for the Time Profiler, you can see
that there was a burst of activity, probably related to loading the images. Below
the track is the detail pane, which displays tabular data related to the selected
instrument (Figure 34.3).
Figure 34.3 Running Scattered under the Time Profiler
Time Profiler works by taking snapshots of the application’s call stack
repeatedly while it is running. This enables you to tell where the application is
spending its time. But it does not tell you how many times a particular method
has been called, as it has only been taking snapshots and does not know when
methods are entered and exited.
In the detail pane, you see the symbols sorted by time spent. Note the disclosure
triangles in the Symbol Name column. Try clicking through them to navigate the
stack, or use the keyboard arrow keys to explore the tree if you prefer. Note that
Invert Call Tree is checked by default; this means that you are seeing the deepest
functions where the CPU spent most of its time. You can toggle Invert Call Tree off
to see the individual entry points to the application. Sometimes, this will make it
faster to find the information you need.
If you dig down far enough, you will see that all this time is being spent in the
closure used to create the NSImage in thumbImageFromImage(_:) (a quick way to
find this is to enable Hide System Libraries). If you show the Extended Detail pane (using
the View segments in the toolbar), you can see another view of the stack; this can
be very useful when using memory-related instruments (Figure 34.4).
Figure 34.4 Showing the Extended Detail pane
At this point, it is pretty clear that quite a bit of time is being spent creating the
thumbnails. Try double-clicking on the stack frame for the closure used to create
the NSImage in thumbImageFromImage(_:). The detail pane will change to show
the source code of that method, with highlighting to show how much time is
spent on each line (Figure 34.5). In this case, all the time is spent in
NSImage.drawInRect(_:). Use the jump bar control above the detail view to
return to the Call Stack.
Figure 34.5 Source code display
Multithreaded Scattered
Let’s modify Scattered to use NSOperationQueue and try to improve it.
Open ViewController.swift and add a property for the NSOperationQueue:
import Cocoa
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.presentImage(thumbImage)
let t1 =
NSDate.timeIntervalSinceReferenceDate()
let interval = t1 - t0
self.text = String(format:
"%0.1fs", interval)
}
}
}
}
}
}
}
That is it! Run the application and marvel at the new and improved user
experience. You should notice a significant speed increase as well, depending on
your hardware.
Instead of explicitly creating NSOperation objects, you use NSOperationQueue’s
addOperationWithBlock(_:), which creates an NSBlockOperation for you and
adds it to the queue. Note how minimal the changes are; the general flow of the
application is practically unchanged. It will not always be this clean to add
multithreading to an application, but you are seeing more of how closures allow
you to avoid a lot of boilerplate code by enabling you to reference variables that
are in scope.
Thread synchronization
We did not appear to worry about race conditions as we led you through this
exercise. The reason is that the design of the application specifically avoids
using any shared mutable data structures from within the background thread.
Work in the background threads was limited to enumerating the folder, opening
images, and creating thumbnails. The only shared data, the Core Animation
layers, was modified from the main thread only. Multithreading is easiest when
you can avoid race conditions altogether.
Not all multithreading problems will be solvable using this approach, however.
Often you will need to protect a section of code (or multiple sections of code)
such that only one thread can be running it at a time. This is usually done with a
mutex – a mutually exclusive lock. When you need a simple mutex, you can use
Cocoa’s NSRecursiveLock class:
let lock = NSRecursiveLock()
func addImage(image: NSImage) {
lock.lock()
images.append(image)
lock.unlock()
}
An NSRecursiveLock is a mutex: it prevents multiple threads from running the
code between lock() and unlock() at once. An NSRecursiveLock is also
recursive: it can be locked multiple times from the same thread.
You can safely read from a constant Array on any thread. However, if an Array
is not constant, you must take care when reading from or writing to it because it
is not thread-safe – it is not designed to be modified from multiple threads. As a
result, it is recommended that you use a mutex when modifying an instance of
Array in a multithreaded environment. An NSRecursiveLock guarantees that only
one thread will be able to lock it at a time. If two threads were attempting to call
addImage(_:) at the same time, the first thread would obtain the lock and add the
image to the array and the other thread would block and wait for the lock to be
released.
You may be wondering why Array is not thread-safe. One reason is that a mutex
has overhead associated with it, and thread safety would make Arrays
significantly slower. Another reason is that it is often more useful (and common)
to lock a section of code, not just a single method call, such as if you were
moving objects from one data structure to another.
Cocoa provides a number of other tools for thread synchronization, such as
NSLock and NSCondition. These tools are discussed in detail in Advanced
Mac OS X Programming: The Big Nerd Ranch Guide, along with a more
involved look at NSOperationQueue and Grand Central Dispatch.
For the More Curious: Faster Scattered
The goal in this chapter was to make Scattered a better-behaved application by
moving heavy lifting off the main thread. You did not, however, fully address
the performance issues with this application. If you were watching closely, you
may have noticed that you limited the number of concurrent operations in
processingQueue to four. If you remove that constraint (by commenting out the
line), you may find that Scattered runs somewhat faster, or you may find that it
runs even slower. What is going on here?
One of the most useful features of Grand Central Dispatch (GCD), which
NSOperationQueue is built upon, is that GCD manages the number of running
operations (threads) based on the system’s hardware (number of cores) and the
current system load. In higher-level terms, GCD will create as many threads as it
thinks the system can handle in order to process the operations in a queue as
quickly as possible. If every queued operation is uniform as far as its required
resources, this works very well.
Consider how Scattered works, however: Each operation starts by reading data (an
image) from disk. Disk I/O puts very little demand on the CPU, so GCD sees
that the CPU is not being utilized and starts another operation, which starts by
reading data from the disk, and so forth. Perhaps you can see how GCD would
very quickly start a large number of threads to handle the operations in the
queue.
When the image data for the first operation is fully read in, the image is
decompressed and a thumbnail is created, which is somewhat CPU-intensive
work. While this work is being done, the second thread finishes reading from
disk and starts decompressing, and so on. Suddenly, the CPU is being asked to
do quite a bit of work!
In this exercise, you avoided this pile up by limiting the number of concurrent
operations. But this approach is not ideal, because you are frequently wasting
CPU time while waiting on the disk: recall how hilly the CPU graph in Instruments
was. The proper solution to this problem is to use two queues: one queue to load
image data from the disk, limited in the number of concurrent operations it can
conduct (because disks are slow), and another queue to do the work of creating
the thumbnail. This is, however, quite a bit more complicated to do properly.
Challenge: An Even Better Scattered
Adapt Scattered to use the proper solution outlined in the previous section.
Because NSImage avoids doing disk I/O until absolutely necessary, you will need
to read the data in manually and then create the image using that data. NSData
will read the image data. Check the documentation or header file for NSImage to
find a way to create an image from an NSData object.
35
NSTask
Each application that you have created is a bundle, a directory which OS X
displays to the user as a single file. Somewhere down in that directory is an
executable file. To run an executable on a Unix machine, like your Mac, a
process is forked and the new process executes the code in that file. Many
executables are command-line tools, and some are quite handy. In this chapter,
you will learn how to run a command-line tool from your Cocoa application
using NSTask.
NSTask is an easy-to-use wrapper for the Unix functions fork() and exec(). You
give NSTask a path to an executable and launch it. Many processes read data from
standard-in and write to standard-out and standard-error. Your application can
use NSTask to attach pipes to carry data to and from the external process. Pipes
are represented by the class NSPipe.
ZIPspector
The tool usrbin/zipinfo looks at the contents of a zip file. Find a zip file on your
machine and try running zipinfo in the Terminal like this (-1 is dash-one, not
dash-el):
# zipinfo -1 Usersaaron/myfile.zip
rad_file.txt
tubular.pages
swell_file.rtf
magnificent.pdf
You are going to create an application that displays the contents of a zip file in a
table. To achieve this, you are going to use zipinfo, sending it some arguments and
reading from its standard-out (Figure 35.1).
Figure 35.1 Completed application
In Xcode, create a new Cocoa Application named ZIPspector, and enable Create Document-
Based Application. You will not be using storyboards or Core Data in this
application. This program will only view zip files, not edit them. In the Info panel
for the target, update the Identifier and Role fields to set ZIPspector to be a viewer
for files with the UTI com.pkware.zip-archive (Figure 35.2) (This is a system-
defined UTI, so it will know the extension, icon, and so on, for zip files.)
Figure 35.2 Setting the UTI
In Document.swift, add an outlet for an NSTableView, make Document adopt
NSTableViewDataSource, and add an Array for holding the filenames in the zip
file:
class Document: NSDocument, NSTableViewDataSource {
Set the table view’s dataSource to be the Document and set the Document’s
tableView outlet to the table view. (Remember, in a document XIB, File's Owner is
the document.) Inside Document’s implementation, near the bottom, add a new
section for the required NSTableViewDataSource methods. Implement the
required methods so that the strings in filenames are displayed:
// MARK: - NSTableViewDataSource
// Check status
if status != 0 {
if outError != nil {
let errorDomain =
"com.bignerdranch.ProcessReturnCodeErrorDomain"
let errorInfo
= [ NSLocalizedFailureReasonErrorKey :
"zipinfo returned \(status)"]
outError.memory = NSError(domain:
errorDomain,
code: 0,
userInfo:
errorInfo)
}
return false
}
// Convert to a string
let string = NSString(data: data, encoding:
NSUTF8StringEncoding)!
// In case of revert
tableView?.reloadData()
return true
}
After creating the NSTask, you attached an NSPipe to standardOutput. The NSPipe
provides a buffer for data from the task’s standard-output to flow into. Finally,
you asked the NSPipe for its fileHandleForReading, which returns to you an
NSFileHandle. You used this file handle to read the data out of the pipe
(Figure 35.4).
Figure 35.4 Object diagram
Your application is read-only – it cannot create or modify zip files – so you can
delete the method dataOfType(_:error:) if you wish. Also, you can open up the
MainMenu.xib file and delete any menu items that are concerned with saving.
Build and run your application. You should be able to see the contents of any zip
file. Note: Since ZIPspector is a viewer, no Untitled document will appear; you will
need to open an existing .zip file.
Asynchronous Reads
As mentioned in Chapter 23, the run loop is the object that waits for events,
which may be keyboard, mouse, or timer events. These are all run loop data
sources. You can also make a file handle a run loop data source.
In the next section, you are going to fork off a process that burps up data
occasionally. You will attach a pipe to standardOutput, but instead of trying to
read all the data from the file handle immediately, you will ask the file handle to
read in the background and send a notification when data is ready.
You can use sbinping to check whether you can make an IP connection to another
machine. Try running it in Terminal:
$ ping -c10 www.bignerdranch.com
PING www.bignerdranch.com (69.39.89.150): 56 data
bytes
64 bytes from 69.39.89.150: icmp_seq=0 ttl=50
time=35.579 ms
64 bytes from 69.39.89.150: icmp_seq=1 ttl=50
time=35.099 ms
64 bytes from 69.39.89.150: icmp_seq=2 ttl=50
time=34.546 ms
64 bytes from 69.39.89.150: icmp_seq=3 ttl=50
time=35.495 ms
64 bytes from 69.39.89.150: icmp_seq=4 ttl=50
time=35.685 ms
64 bytes from 69.39.89.150: icmp_seq=5 ttl=50
time=35.667 ms
64 bytes from 69.39.89.150: icmp_seq=6 ttl=50
time=36.435 ms
64 bytes from 69.39.89.150: icmp_seq=7 ttl=50
time=52.296 ms
64 bytes from 69.39.89.150: icmp_seq=8 ttl=50
time=36.142 ms
64 bytes from 69.39.89.150: icmp_seq=9 ttl=50
time=36.188 ms
--- www.bignerdranch.com ping statistics ---
10 packets transmitted, 10 packets received, 0% packet
loss
round-trip min/avg/max/stddev =
34.546/37.313/52.296/5.021 ms
If you want to end the program prematurely, press Control-C to send it a sigint
signal. This will cause it to write out the stats and terminate.
iPing
Now you are going to write a Cocoa app that uses NSTask to run ping
(Figure 35.5).
Figure 35.5 Completed application
In Xcode, create a new project, iPing, of type Cocoa Application. Uncheck Create
Document-Based Application. You will want a storyboard for this app, so ensure that
Use Storyboards is checked.
}
Open Main.storyboard. In the Window Controller Scene, locate the Window. In the
attributes inspector, set its Title to be iPing.
Next, switch to the View Controller Scene. Drop a text field, a button, and a text view
onto the view, and set up appropriate constraints among them. Make the text
view be read only by unchecking Editable in the attributes inspector. Change the
button’s Type to Toggle. Set its title to Start Pinging (Figure 35.6) and its alternate title
to Stop Pinging.
Figure 35.6 Button attributes
Make the button’s target be the ViewController and make its action be
togglePinging:. Hook up the outlets you just added to ViewController to the
corresponding views (Figure 35.7).
Figure 35.7 Object diagram
In ViewController.swift, implement togglePinging(_:):
@IBAction func togglePinging(sender: NSButton) {
// Is there a running task?
if let task = task {
// If there is, stop it!
task.interrupt()
} else {
// If there isn't, start one.
self.task = task
self.pipe = pipe
self.fileHandle = fileHandle
let notificationCenter =
NSNotificationCenter.defaultCenter()
notificationCenter.removeObserver(self)
notificationCenter.addObserver(self,
selector:
Selector("receiveDataReadyNotification:"),
name:
NSFileHandleReadCompletionNotification,
object: fileHandle)
notificationCenter.addObserver(self,
selector:
Selector("receiveTaskTerminatedNotification:"),
name:
NSTaskDidTerminateNotification,
object: task)
task.launch()
outputView.string = ""
fileHandle.readInBackgroundAndNotify()
}
}
While the task is running, the file handle will be posting notifications when data
is ready. Implement the method that will get called:
func appendData(data: NSData) {
let string = NSString(data: data, encoding:
NSUTF8StringEncoding) as! String
let textStorage = outputView.textStorage!
let endRange = NSRange(location:
textStorage.length, length: 0)
textStorage.replaceCharactersInRange(endRange,
withString: string)
}
func receiveDataReadyNotification(notification:
NSNotification) {
let data
= notification.userInfo!
[NSFileHandleNotificationDataItem] as! NSData
let length = data.length
task = nil
pipe = nil
fileHandle = nil
startButton.state = 0
}
Build and run the application. Enter a URL or IP address into the text field and
click the button. The output of ping should appear in the text view.
Challenge: .tar and .tgz Files
A listing of files in a zip file is given by zipinfo. You can get a similar listing for
tar files by using the command-line tool tar:
# usrbin/tar tf MyFiles.tar
If the tar file is also compressed, just add a z to the flags:
# usrbin/tar tzf MyFiles.tgz
Extend ZIPspector to deal with .tar and .tgz files.
36
Distributing Your App
The time will come when you are ready for your app to leave its nest. You have
crushed all the bugs you can find and tested for leaks in Instruments. It is high time
for your app to see the world!
In this chapter, you will learn how to use Xcode to prepare your app for life
outside the debugger. You will follow the steps to distribute an app using the
RanchForecastSplit app from Chapter 32.
Build Configurations
Until now, you have been using debug builds when trying out your apps as you
have been writing them. Debug builds contain additional information that
enables the debugger to show detailed stack information. Furthermore, the
compiler has not performed any optimizations.
This is great for development. It makes the debugger more useful, and builds are
generated more quickly. But it is the opposite of what you want in a build that
you would release to customers: a release build. In a release build, optimizations
are turned on and debugging symbols are stripped (to reduce size and make
inspecting the code more difficult).
There is nothing particularly special about the debug and release build
configurations. They are simply a convention, and all the settings for these
configurations are modifiable within Xcode.
Open your RanchForecastSplit project from Chapter 32. You can find the project’s
existing build configurations in the project editor, on the Info pane (Figure 36.1).
You can also add new build configurations there.
Figure 36.1 Project build configurations
Xcode has several actions available: run, test, profile, analyze, and archive. A
build configuration is associated with each of these actions. You can configure
which configuration is used for each action using the Scheme Editor (Figure 36.2).
To view the scheme editor, select the Product → Scheme → Edit Scheme... menu item.
Figure 36.2 The scheme editor with the Debug build configuration selected
for Run
The specified build configuration will be used when building the target for that
particular action.
Preprocessor Directives: Using Build
Configurations to Change Behavior
One common use of build configurations is to hardcode behavioral settings in
your application. This is done using preprocessor directives.
In Xcode, select the project in the project navigator to bring up the project editor.
In the project editor, select the RanchForecastSplit target and switch to the Build Settings
tab. Scroll nearly to the bottom until you find the Swift Compiler - Custom Flags
heading, where you will see the Other Swift Flags field (Figure 36.3).
Figure 36.3 Other Swift flags
Expand that field, using the disclosure arrow on the left to show that the field
can be configured differently for Debug and for Release. You can use these fields to
define compile-time values which are different for debug and release builds.
Change the Debug field to -DDEBUG as in Figure 36.4. (You can change the field
by selecting it and pressing Return.) This change makes the compile-time value
DEBUG be defined only for builds made using the Debug build configuration.
Figure 36.4 Defining DEBUG for debug builds
You can check whether the DEBUG compile-time value is defined in your code
and perform different work depending on the result:
#if DEBUG
printOutEverything()
#else
printOutOnlyWhatsNeeded()
#endif
How does this work? While it is building your app, the compiler determines
whether DEBUG has been defined. If it has been defined, the compiler will only
emit code to call printOutEverything(). If it has not been defined, the emitted
code will only call printOutOnlyWhatsNeeded().
Note that the other code is completely omitted from the built app: There is not
even a branch to be evaluated when the program runs. The check for whether
DEBUG is defined happens at compile time only. However, even though the
compiler does not emit anything for it, the other code must be syntactically
correct.
To make this more concrete, change the logging behavior of RanchForecastSplit.
Open CourseListViewController.swift and find implementation of
viewDidLoad(). Make the fetched courses only be logged out if the code is build
under the debug configuration:
case .Success(let courses):
println("Got courses: \(courses)")
#if DEBUG
println("Got courses: \(courses)")
#else
println("Got courses")
#endif
self.courses = courses
Run the app. You should see the same thing logged out as before because you
are running the debug build, where DEBUG is defined. Use the Scheme Editor to
change the Run scheme to use the Release configuration instead (Figure 36.5).
Figure 36.5 Make the Run action use the Release build configuration
When you run the program this time, you should see something much shorter,
like the following, in the log:
Received 4915 bytes with status code 200.
Got courses
Before continuing, change the build configuration used by Run back to Debug.
One last note about using preprocessor directives: They can be used a lot more
effectively than just checking whether you are in a debug build all over your
code. For example, you might want to log certain statements only in your debug
build. Your first thought might be something like this:
#if DEBUG
println("This happened.")
#endif
But it would be much less obtrusive to simply use:
debugPrintln("This happened.");
You would implement a debugPrintln function as follows:
func debugPrintln<T>(object: T) {
#if DEBUG
#if DEBUG
println(object)
#endif
}
Creating a Release Build
Now that you know about build configurations – enough to know that when you
are distributing your app, you will want a release build – how do you create one?
The simplest way to do this in Xcode is by archiving your target. Note that in the
Scheme Editor the Release build configuration is selected for the Archive action
(Figure 36.6).
Figure 36.6 Archive uses the Release build configuration
Once Xcode has exported the app bundle, you can compress it in a ZIP archive
and post it on your website or send it to your beta testing team.
A Few Words on Installers
If you are new to Mac, you may be wondering how you are going to create an
installer for your application. Our advice is: Don’t. Application installation on a
Mac is different from other platforms, and in most cases an installer adds
unneeded complexity and hides its actions from the user. Most applications are
installed by simply having the user drag them from their downloads folder into
/Applications. This has the advantage of a very clear uninstallation: Drag the
application to the trash.
There are two common approaches for packaging an application for download.
Many app bundles are simply compressed in a ZIP archive. By default, Safari
unarchives ZIP archives containing app bundles, making drag installation very
easy for the user.
The second approach is to create a DMG (disk image), which has the advantage
of displaying a Finder window with the contents of the image when it is opened
(mounted). This allows for the inclusion of files in addition to the app bundle
itself, such as a readme, a symbolic link to /Applications (to make drag
installation even more convenient), and an optional custom background.
Configuring such a DMG is complicated enough that there are third-party tools
to help with the process.
Both approaches have their pitfalls. For example, users sometimes forget to drag
the application to their /Applications folder, leaving it in their downloads folder
or, worse, running it from the DMG. One solution some developers have
implemented is to detect the app bundle’s location on startup and offer to move
it for the user.
Note that Mac App Store apps cannot use an installer.
App Sandbox
In the old days, an application had all the same rights as the user running it. If
you trust all your applications, this sounds fine. However, most users do not
have the luxury of running only apps they trust, and, more important, no user can
run only bug-free applications. The unpleasant truth is that even a trustworthy
application can have an innocent bug that causes damage to a system or allows
an attacker access to the user’s system. The app sandbox is a big step toward
mitigating this problem.
Sandboxing is a security method that constrains the means by which an
application can interact with the system (filesystem, network). Apple has
required sandboxing of all apps on iOS since the very first 3rd party apps were
introduced in iOS 2.0. Apple first introduced sandboxing to the Mac in OS X
10.7. All applications on the Mac App Store must be sandboxed.
Entitlements
When you opt in to the app sandbox, by default your application will have fairly
limited access to system resources. It can load resources from its bundle and read
and write files within its container (more on those in a moment), but not much
more. If your application needs access to more – such as the network, the user’s
contacts, or the Music folder – you can specifically enable such access by adding
entitlements.
Consider the requirements of the RanchForecastSplit app. It needs to create outgoing
network connections and not much more. It does not need to read or write files
on disk, use the camera or microphone, or open a port for incoming network
connections. By limiting RanchForecastSplit’s entitlements to creating only outgoing
network connections, you have minimized any opportunities for mischief on the
part of this application. More importantly, you have minimized opportunities for
an attacker to exploit a weakness in RanchForecastSplit.
To specify an application’s entitlements, you can use the Capabilities tab of Xcode’s
project editor (Figure 36.9). Note that to specify the entitlements, you must first
enable the App Sandbox.
Try enabling the sandbox on RanchForecastSplit. What happens if you run it without
enabling the Network: Outgoing Connections (Client) entitlement?
Figure 36.9 Application entitlements
Containers
Sandboxed applications are provided with a container: a folder on disk in which
they can store caches and other resources. The container is stored in the user’s
library, under ~/Library/Containers. Paths returned to your code by APIs such
as NSFileManager’s URLsForDirectory(_:inDomains:) will return URLs within
the container.
Server-based verification
In addition to the local receipt validation approach discussed above, you can also
ask the Mac App Store to validate receipts.
To use this approach, you will have to set up a server to connect to the Mac App
Store server. Not only does this provide flexibility in how you use the
knowledge that a user has a verified receipt, such as for providing server-based
features only for verified users, but it also prevents a man-in-the-middle attack
from simply verifying all requests from all applications on a system.
To do App Store-based validation, you will need to perform the following steps:
1. In your app, read the receipt file at the location given by
NSBundle.appStoreReceiptURL().
2. In your app, send the data from that file to your server.
3. On your server, send an HTTP POST request with a JSON body containing
the receipt data to https://buy.itunes.apple.com/verifyReceipt.
4. On your server, interpret the response from the Mac App Store and send an
appropriate response to your app.
5. In your app, handle the response from your server, calling exit(173) if
necessary.
Apple has provided documentation for requests and responses to be sent and
received from the Mac App Store in the Receipt Validation Programming Guide.
There are also some helpful snippets to get you started with both the app code
and the server code in this guide.
Overall, be creative in your receipt validation, and remember to use varying
patterns between your applications.
37
Afterword
Congratulations! You have reached the end of the book. The knowledge that you
have earned has not come easy, and you have learned a lot of stuff. Be proud.
The only way to solidify what you have learned is to write applications, and the
sooner you start, the easier it will be. The good news is that while there is still
more to learn, you have gotten over the hump in the learning curve. Matters will
be easier from here, but the only way to progress is to write applications.
If you would like to learn more from Big Nerd Ranch, we offer five-and seven-
day classes. For a schedule, visit bignerdranch.com. Or use the RanchForecast exercise.
Also, look for Big Nerd Ranch Guides on other programming topics.
You can find us on Twitter, where we keep you informed about programming
and entertained about life: @bignerdranch, @aaronhillegass, @preble, and @neightchan.
As you continue programming, you will continue to have questions. We offer the
following list of resources as good places to find answers:
If you have a question about Cocoa, the first place to check is in the
reference documentation. All the classes, protocols, functions, and
constants are listed there. Additionally, Apple’s Programming Guides are a
great resource for practical instructions on using many APIs. You will find
these linked toward the top of many Class Reference pages.
The Mac App Programming Guide in Apple’s developer documentation is a
great resource for learning about the behaviors of a Mac-like app, and what
Cocoa APIs you can use to implement them.
If you have a question about Swift, the first place to check is in the online
Swift reference documentation.
If you have a question about Xcode or Interface Builder, the first place to check is
in the developer tools reference documentation.
Don’t be afraid to experiment—most questions can be answered by creating
a tiny application. Creating this application will probably take you less than
15 minutes.
The website for this book (bignerdranch.com/books) has links to the exercise
solutions and an interactive forum for discussing this book and Cocoa
programming.
Stack Overflow (stackoverflow.com) is an excellent place to find the answers
to your questions, and has a strong Cocoa and iOS presence. Chances are
somebody has faced the same challenge you are facing.
Mark Dalrymple wrote a book on the plumbing of OS X from a
developer’s point of view. If your code is going to do anything with the
operating system (such as multithreading or networking), we strongly
recommend that you pick up a copy of Advanced Mac OS X Programming:
The Big Nerd Ranch Guide.
If you are interested in learning iOS development and the Cocoa Touch
frameworks, check out iOS Programming: The Big Nerd Ranch Guide.
Find a CocoaHeads group near you. CocoaHeads has a mailing list and
groups around the world where Cocoa developers meet to discuss Cocoa
programming. You can find out more at cocoaheads.org.
Join the Mac Developer Program. It will give you access to the latest
developer tools and documentation, as well as prior years’ WWDC videos.
The Developer Programs site is developer.apple.com/programs.
If you have exhausted all other possibilities, Apple’s Developer Technical
Support can answer your questions at developer.apple.com/support/technical/. The
folks there have answered lots of questions for us, and we find them to be
consistently knowledgeable and helpful.
Finally, try to be nice. Help beginners. Give away useful applications and their
source code. Answer questions in a kind manner. It is a relatively small
community, and few good deeds go forever unrewarded.
Thanks for reading our book!
Index
A B C D E F G H I J K L M N O P Q R S T U V W X Z
Symbols
(_:), meaning of, Instance methods
.icns file, Setting the Extension and Icon for the File Type
.lproj files (localization), Localization and Bundles
.tar files, Challenge: .tar and .tgz Files
.tgz files, Challenge: .tar and .tgz Files
.xcdatamodeld (Core Data), Defining the Object Model
// MARK:, Creating the user interface
@IBAction, Defining an action method
@IBDesignable, Inspectable properties and designable views
@IBInspectable, Inspectable properties and designable views
@IBOutlet, Creating an outlet
@NSApplicationMain, NSApplication and NSApplicationDelegate
A
accents, typing, Localizing a XIB File
acceptsFirstResponder (NSResponder), NSResponder
acceptsMouseMovedEvents (NSWindow), For the More Curious: Rollovers
access modifiers, Your First Test, For the More Curious: Access Modifiers
actions
(see also connections (in Interface Builder), controls, NSControl, outlets)
and AnyObject, Connecting the slider’s target and action
connecting in Interface Builder, Connecting actions
defined, Making Connections
feature of NSControl, About Controls
and menu items, Starting the Chatter Application
as messages, About Controls, A continuous control
methods for, Defining an action method, Connecting the slider’s target and action
nil-targeted, Nil-Targeted Actions
and NSApplication, For the More Curious: Which Object Sends the Action Message?
setting programmatically, For the More Curious: Setting the Target Programmatically
actions (CALayer), Implicit Animation and Actions
addChildViewController(_:) (NSViewController), Adding Tab Images
addObserver(_:selector:name:object:) (NSNotificationCenter), NSNotificationCenter
addSubview(_:) (NSView), View Swapping
alerts, Alerts and Closures
alpha values (NSColor), Using the Documentation
ambiguous layouts (Auto Layout), Does Not Compute, Part 2: Ambiguous Layout
animations, Core Animation
and timers, NSTimer-based Animation
AnyObject, Connecting the slider’s target and action, Adding Tab Images
API Reference, Using the Documentation
App Store (distribution), The Mac App Store
AppDelegate
about, NSApplication and NSApplicationDelegate
role, The controller layer
and window controllers, Showing the Window, Improving Controller Design
append(_:), Instance methods
AppKit (framework), The Cocoa Frameworks, Creating the MainWindowController class, frame
Apple Developer Programs, Prerequisites
application architecture
basic, Showing the Window, Improving Controller Design
document-based, NSArrayController, The Document Architecture
master/detail, Container View Controllers
with multiple view controllers, Connecting the Course List Selection with the Web View
and MVC, Model-View-Controller
single-window, Setting up RGBWell, Creating and using an Xcode snippet
and view controllers, View Controllers, Starting the ViewControl Application
and window controllers, Improving Controller Design, Creating an instance of
MainWindowController, View Controllers vs. Window Controllers
application bundles, NSBundle, NSTask
applications
(see also application architecture, projects)
App Store, using, The Mac App Store
build configurations for, Build Configurations
containers for, Containers
copy protection for, Receipt Validation
custom file extensions for, Setting the Extension and Icon for the File Type
custom file icons for, Setting the Extension and Icon for the File Type
distributing, Creating a Release Build, The Mac App Store
document-based, NSArrayController
entitlements of, Entitlements
and event loop, The main event loop
exporting, Creating a Release Build
installers for, A Few Words on Installers
launching, NSApplication and NSApplicationDelegate, The main event loop
lifecycle methods, NSApplication and NSApplicationDelegate
localizing, Localization and Bundles, Localizing a XIB File
locations for data, Application Data and URLs
mediated file access, Mediated file access and Powerbox
and multiple threads, Multithreading
packaging, A Few Words on Installers
printing from, Printing
and release builds, Creating a Release Build
sandboxing, App Sandbox
storage for, Application Data and URLs
and system resources, Entitlements
unit testing, Unit Testing
ARC (Automatic Reference Counting), Automatic Reference Counting, Strong reference
cycles, What is ARC?
(see also memory management)
archivedDataWithRootObject(_:), Saving and NSKeyedArchiver
archiving
about, Archiving
build targets, Creating a Release Build
decoding, Decoding
and document architecture, The Document Architecture
encoding, Encoding
loading objects, Loading and NSKeyedUnarchiver
NSCoder, NSCoder and NSCoding
NSData, Saving and NSKeyedArchiver, Loading and NSKeyedUnarchiver
NSKeyedArchiver, Saving and NSKeyedArchiver
NSKeyedUnarchiver, Loading and NSKeyedUnarchiver
preventing infinite loops in, For the More Curious: Preventing Infinite Loops
saving objects, Saving and NSKeyedArchiver
vs. Core Data, Choosing a Cocoa Persistence Technology
XIB files, XIB files and NIB files
ARepeat (NSEvent), NSEvent
arrangedObjects (NSArrayController), Introducing NSArrayController, Binding the Table
View’s Selection to the Array Controller, Sorting in RaiseMan
array controllers
(see also NSArrayController)
about, Introducing NSArrayController
customizing, Customizing Objects Created by NSArrayController
filtering with, For the More Curious: Filtering
immediate fetching, Configure the Array Controller
labeling in Interface Builder, Configure the Array Controller
and model abstractions, Introducing NSArrayController
and NSManagedObjectContext, Configure the Array Controller
sorting with, Sorting in RaiseMan
as target of controls, Connecting the Add Employee Button
arrays
about, Collection types
append(_:), Instance methods
count, Properties
filtering, For the More Curious: Filtering
memory management of, Deallocating objects in a hierarchy
and NSArray, Bridging with collections
reverse(), Instance methods
subscripting, Literals and subscripting
and traps, Literals and subscripting
as, Basic bridging
assert(), Runtime Errors
assertions (unit testing), Testing in Xcode, Refactoring for Testing
assistant editor, Making a connection with the assistant editor
associated values (enums), NSURLSession and asynchronous API design
astrophysics degrees, Some Advice on Learning
attributes (Core Data), Defining the Object Model
attributes (views), Configuring view objects
attributes inspector (Xcode), Configuring view objects, A continuous control
Auto Layout
(see also constraints (Auto Layout))
adding constraints, Constraints from subview to superview
ambiguous layouts, Does Not Compute, Part 2: Ambiguous Layout
vs. autoresizing masks, For the More Curious: Autoresizing Masks
clip view warning, Adding a Table View
described, What is Auto Layout?
intrinsic content size, Intrinsic Content Size
and NSBox, View Swapping, Challenge: Boxless NerdTabViewController
unsatisfiable constraints, Does Not Compute, Part 1: Unsatisfiable Constraints
Visual Format Language, Visual Format Language
visualizeConstraints(_:), Does Not Compute, Part 2: Ambiguous Layout
with right-to-left languages, Creating Layout Constraints Programmatically
auto-complete (Xcode), Implementing a delegate method
automatic document saving, For the More Curious: Automatic Document Saving
automatic initializers, Structures
autoresizing masks, For the More Curious: Autoresizing Masks
availableTypeFromArray(_:) (NSPasteboardItem), NSPasteboard
B
background threads, NSURLSession and asynchronous API design, Multithreading, Analyzing
output from Instruments
Base.lproj, Localizing String Literals
beginCriticalSheet(_:completi…) (NSWindow), Sheets
beginDraggingSessionWithItems(…) (NSView), Starting a drag
beginSheet(_:completionHandler:) (NSWindow), Sheets, Present the Sheet
beginSheetModalForWindow(_:comple…) (NSAlert), Completion Handlers and
Closures
bindings
array controllers, Introducing NSArrayController
benefits of, Bindings
with Core Data, Basic Core Data
creating, Using bindings
creating programmatically, For the More Curious: Key Paths
debugging, Debugging Bindings, Using the debugger to see bindings in action
and KVC/KVO, Bindings, Key-value observing, Making keys observable
and NSObject, RaiseMan’s Model Layer, RanchForecast Project
patterns for, Connections and Bindings
for table data, Binding the text field to the table cell view
and value transformers, For the More Curious: NSValueTransformer
when to use, Binding other attributes
bindings inspector (Xcode), Binding the text field to the table cell view
blocking
(see also multithreading)
CPU-bound, Analyzing output from Instruments
I/O-bound, Analyzing output from Instruments
and modal windows, Modals and Sheets, Modal Windows
boldSystemFontOfSize(_:) (NSFont), NSFont
Bool, Number and boolean types
boolean types, Number and boolean types
boolForKey(_:) (NSUserDefaults), NSUserDefaults
bounds (NSView), bounds
breakpoint navigator, Deleting breakpoints
breakpoints, Using breakpoints, Deleting breakpoints
bridging, Working with Foundation Types
build actions, Build Configurations
build configurations
changing app behavior with, Preprocessor Directives: Using Build Configurations to
Change Behavior
debug, Debugging Hints, Build Configurations
debugging symbols in, Build Configurations
finding, Build Configurations
and Instruments, Introducing Instruments
and preprocessor directives, Preprocessor Directives: Using Build Configurations to
Change Behavior
release, Debugging Hints, Build Configurations
setting flags in, Preprocessor Directives: Using Build Configurations to Change Behavior
specifying, Build Configurations
build targets, Testing in Xcode, Creating a Release Build
bundles
application, Setting the Extension and Icon for the File Type, NSBundle, NSTask
described, NSBundle
identifiers for, Application Data and URLs, What is the User’s Defaults Database?
and localization, Different Mechanisms for Localization, NSBundle’s role in localization
main, NSBundle
and strings files, NSBundle’s role in localization
buttons
disabling, Updating Buttons
in Interface Builder, Adding view objects
radio, Challenge: Busy Board
recessed, Binding other attributes
titles for, Configuring view objects
C
CAAnimation, CALayer
CABasicAnimation, Implicit Animation and Actions
CAGradientLayer, More on CALayer
CALayer
about, CALayer
actions, Implicit Animation and Actions
delegate of, More on CALayer
described, CALayer
subclasses, More on CALayer
CALayerDelegate, More on CALayer
canvas (Interface Builder), Creating the User Interface in Interface Builder
CAOpenGLLayer, More on CALayer
capture lists, Closures and capturing
caseInsensitiveCompare(_:), For the More Curious: The caseInsensitiveCompare(_:) Method
CAShapeLayer, More on CALayer
casting, Basic bridging
categories, Extensions
CATextLayer, More on CALayer
CATransaction, CALayer, Implicit Animation and Actions
cell-based tables, Tables, Cells, and Views
cells
and controls, A word about NSCell
history in Cocoa, A word about NSCell, Tables, Cells, and Views
in table views, Tables, Cells, and Views
CGFloat, Using the Documentation, Changing the color of the color well
CGRect
contains(_:), Improving Hit Detection
characters (NSEvent), NSEvent
checkboxes (NSButton), Challenge: Busy Board
Clang Static Analyzer, What is ARC?
class methods, Getting Voice Data
classes
(see also individual class names, initializers, methods, objects, properties, types)
about, Application Design
creating new, Creating the MainWindowController class
defining, Classes
extending, Extensions
and inheritance, Inheritance
initializing, Designated and convenience initializers
making @IBDesignable, Inspectable properties and designable views
prefixes for, Adding an Array Controller to the XIB
in product modules, Adding an Array Controller to the XIB
reference pages for, Using the Documentation
vs. structures, Reference and Value Types
clearContents() (NSPasteboard), NSPasteboard
clickCount (NSEvent), NSEvent, Getting Mouse Events
clip views, Table view and related objects
closures, Reference and Value Types, Completion Handlers and Closures
Cocoa
API reference, Using the Documentation
classes in, Choosing between reference and value types, Swift and Objective-C, Using the
Documentation
documentation, Using the Documentation
frameworks in, The Cocoa Frameworks, Creating the MainWindowController class
history of, The Story of Cocoa
Cocoa Touch (framework), Afterword
CocoaHeads, Afterword
code snippet library, Creating and using an Xcode snippet
code snippets, Creating and using an Xcode snippet
color (NSColorWell), Using the Documentation
color wells, NSColorWell and NSColor
com.pkware.zip-archive, ZIPspector
completion handlers
about, Completion Handlers and Closures
with asynchronous API, NSURLSession and asynchronous API design
implementing, NSURLSession and asynchronous API design
testing, For the More Curious: Asynchronous Testing
computed properties
about, Computed Properties
and KVC, KVC and Property Accessors
storage for, KVC and Property Accessors
concludeDragOperation(_:) (NSDraggingDestination), Make DieView a Drag
Destination
concurrency, Concurrency, NSOperationQueue
conditionals
if-else, Creating the user interface
if-let, Optionals
switch, Enumerations and the Switch Statement
connections (in Interface Builder), Making Connections
(see also actions, outlets)
with assistant editor, Making a connection with the assistant editor
to File's Owner, File's Owner and making connections
connections inspector, Connecting actions
connections panel, Connecting an outlet
console
exceptions in, Adding Key-Value validation to RaiseMan
importance in debugging, Debugging Hints
LLDB (debugger), The LLDB console
viewing in playground, Optionals
viewing in project, Connecting the slider’s target and action
constants, Using Standard Types
constraints (Auto Layout)
(see also Auto Layout)
adding in Interface Builder, Constraints from subview to superview
adding programmatically, Challenge: Add Constraints Programmatically
and ambiguous layouts, Does Not Compute, Part 2: Ambiguous Layout
animating, Creating Layout Constraints Programmatically
between siblings, Constraints from subview to superview
creating in Interface Builder, What is Auto Layout?, Constraints from subview to
superview
creating programmatically, Creating Layout Constraints Programmatically
debugging, Does Not Compute, Part 1: Unsatisfiable Constraints
for positioning views, What is Auto Layout?
priorities of, Constraints from subview to superview
size constraints, Size constraints
subview-superview, Constraints from subview to superview
types of, What is Auto Layout?
unsatisfiable, Does Not Compute, Part 1: Unsatisfiable Constraints
containers (for applications), Containers
containers (view controllers), Container View Controllers
contains(_:) (CGRect), Improving Hit Detection
content (NSArrayController), Introducing NSArrayController
Content Array (array controllers), Introducing NSArrayController, Adding an Array Controller
to the XIB
content views, The view layer, Creating an empty XIB file
contexts (graphics), Graphics contexts and states, Saving and Restoring the Graphics State
continuous (NSControl), A continuous control
controller layer (MVC), The controller layer
(see also view controllers, window controllers)
controllers
(see also Model-View-Controller (MVC))
controls
(see also actions, NSControl)
about, About Controls
and action messages, About Controls, A continuous control
array controllers as targets, Connecting the Add Employee Button
and cells, A word about NSCell
creating programmatically, Visual Format Language
enabling/disabling, Disabling a control
formatting, Formatters and a control’s objectValue
making continuous, A continuous control
outlets to, Controls and Outlets
and target-action, About Controls
convertPoint(_:fromView:) (NSView), Improving Hit Detection
convertPoint(_:toView:) (NSView), Improving Hit Detection
copy protection, Receipt Validation
copying-and-pasting (implementing), Pasteboards and Nil-Targeted Actions
Core Animation, Core Animation
Core Data
.xcdatamodeld, Defining the Object Model
attributes, Defining the Object Model
benefits of, Basic Core Data, Choosing a Cocoa Persistence Technology
with bindings, Basic Core Data
data model inspector, Fetching Objects from the NSManagedObjectContext
and data set size, Choosing a Cocoa Persistence Technology
defining object model, Defining the Object Model
entities, Defining the Object Model
explained, How Core Data Works
faulting, Choosing a Cocoa Persistence Technology
fetch requests, Fetching Objects from the NSManagedObjectContext
NSManagedObject, Defining the Object Model
NSManagedObjectContext, Configure the Array Controller, How Core Data Works,
Fetching Objects from the NSManagedObjectContext, Choosing a Cocoa Persistence Technology
NSManagedObjectModel, Defining the Object Model, How Core Data Works
NSPersistentDocument, How Core Data Works
pros and cons, Choosing a Cocoa Persistence Technology
relationships, Defining the Object Model
and SQLite, Persistent Store Types
store types, Persistent Store Types
vs. archiving, Choosing a Cocoa Persistence Technology
Core Graphics (framework), frame, For the More Curious: Core Graphics and Quartz
count (arrays), Properties
createDirectoryAtURL(_:withIntermed…) (NSFileManager), Application Data and
URLs
currentContextDrawingToScreen() (NSGraphicsContext), For the More Curious: Are
You Drawing to the Screen?
cutting-and-pasting (implementing), Pasteboards and Nil-Targeted Actions
D
Dalrymple, Mark, Afterword
Darwin (Unix), OSX, Unix, and Cocoa
data model inspector (Core Data), Fetching Objects from the NSManagedObjectContext
data sources (run loops), Asynchronous Reads
data sources (table views), Delegates and data sources, The table view-data source conversation,
The NSTableViewDataSource Protocol
dataForType(_:) (NSPasteboardItem), NSPasteboard
dataOfType(_:error:) (NSDocument), Saving documents
dataSource (NSTableView), Delegates and data sources
dataSource (property)
exposed as outlet, Connecting the dataSource outlet
setting in Interface Builder, Connecting the dataSource outlet
dataWithPDFInsideRect(_:) (NSView), Getting Your View to Generate PDF Data
date formatters, Formatters, programmatically, Lay out the interface
date pickers, Add the Views
debug builds, Debugging Hints, Build Configurations
DEBUG compile-time value, Preprocessor Directives: Using Build Configurations to Change
Behavior
debug navigator, Using breakpoints
debugger bar, Stepping through code
debugging
(see also debugging tools, errors, exceptions)
Auto Layout constraints, Does Not Compute, Part 1: Unsatisfiable Constraints
bindings, Debugging Bindings, Using the debugger to see bindings in action
exceptions, Setting an exception breakpoint
hints, Debugging Hints
stack trace, Using breakpoints
stepping through methods, Stepping through code
symbols, Build Configurations
with zombie objects, Debugging Hints
debugging tools
breakpoints, Using breakpoints, Deleting breakpoints
debug navigator, Using breakpoints
debugger, Using the Debugger
LLDB (debugger) console, The LLDB console
stack trace, Using breakpoints
variables view, Using breakpoints
decodeBoolForKey(_:) (NSCoder), Decoding
decodeDoubleForKey(_:) (NSCoder), Decoding
decodeFloatForKey(_:) (NSCoder), Decoding
decodeIntForKey(_:) (NSCoder), Decoding
decodeObjectForKey(_:) (NSCoder), Decoding
default: (switch statement), Enumerations and the Switch Statement
defaultCenter() (NSNotificationCenter), NSNotificationCenter
defaults, User Defaults
delegate (property), Being a delegate
(see also delegate methods, delegation)
exposed as outlet, The NSTableViewDelegate Protocol
setting in Interface Builder, Implementing another delegate, The NSTableViewDelegate
Protocol
delegate methods
(see also delegate (property), delegation)
and notifications, Delegate protocols and notifications
optional, Being a delegate, For the More Curious: How Optional Delegate Methods Work
required, Being a delegate
types of, Implementing another delegate
using auto-complete for, Implementing a delegate method
delegation
(see also delegate (property), delegate methods)
about, Delegation
classes using, Cocoa classes that have delegates
errors in implementing, Common errors in implementing a delegate
NSWindowDelegate, Implementing another delegate
and protocols, Being a delegate
steps in implementing, Being a delegate
vs. subclassing, Delegation, Being a delegate
and table views, Delegates and data sources
and web services, NSURLSession and asynchronous API design
dependent keys, For the More Curious: Dependent Keys
developer programs, Prerequisites
dictionaries
about, Collection types
accessing, Subscripting dictionaries
and NSDictionary, Bridging with collections
subscripting, Subscripting dictionaries
didChangeValueForKey(_:), Making keys observable
didSet (property observer), Updating Buttons
directories
(see also bundles)
(see also bundles, files)
.lproj, Localization and Bundles
application, Application Data and URLs
as file wrappers, Saving documents
localization, Localization and Bundles, Localizing String Literals
project source, Localizing String Literals
dirty rects, When is my view drawn?, For the More Curious: Dirty Rects
dismissWithModalResponse(_:), Present the Sheet
distributing (applications), Creating a Release Build, The Mac App Store
DMG (disk image), A Few Words on Installers
dock (Interface Builder), Creating the User Interface in Interface Builder
Document (template-created class), NSArrayController
document architecture, The Document Architecture
document controllers, Info.plist and NSDocumentController
document outline (Interface Builder), Creating the User Interface in Interface Builder, Table
cell views
document-based applications, NSArrayController
and printing, Printing
and responder chain, Nil-Targeted Actions
documentation
for Cocoa classes, Using the Documentation
for protocols, Implementing another delegate
for Swift, Exploring Apple’s Swift Documentation
documents
(see also document architecture, document controllers, files, NSDocument)
automatic saving of, For the More Curious: Automatic Document Saving
extensions for, Setting the Extension and Icon for the File Type
icons for, Setting the Extension and Icon for the File Type
loading, Loading documents
and loading NIB files, Loading documents
printing from, Printing
saving, Saving documents
DOM parsing, For the More Curious: Parsing XML
Double, Number and boolean types
doubleValue, About Controls
drag-and-drop, Drag-and-Drop
draggingEntered(_:) (NSDraggingDestination), Make DieView a Drag Destination,
Implement the dragging destination methods
draggingExited(_:) (NSDraggingDestination), Make DieView a Drag Destination
draggingSession(_:endedAtPoint:operati…) (NSDraggingSource), After the drop
draggingSession(_:sourceOperationM…), Make DieView a Drag Source
draggingUpdated(_:) (NSDraggingDestination), Make DieView a Drag Destination
drawAtPoint(_:) (NSAttributedString), Drawing Strings and Attributed Strings
drawAtPoint(_:withAttributes:) (NSString), Drawing Strings and Attributed Strings
drawFocusRingMask() (NSView), Focus Rings
drawing
(see also animations, views)
and dirty rects, When is my view drawn?, For the More Curious: Dirty Rects
frameworks for, For the More Curious: Core Graphics and Quartz
and graphics contexts, Graphics contexts and states, Saving and Restoring the Graphics State
images, Drawing Images
and layers, Core Animation
PDF data, Getting Your View to Generate PDF Data
and points, frame
printer vs. screen, For the More Curious: Are You Drawing to the Screen?
views, Custom Drawing
drawInRect(_:) (NSImage), Drawing Images
drawInRect(_:fromRect:op…) (NSImage), Drawing images with finer control
drawInRect(_:withAttributes:) (NSString), Drawing Strings and Attributed Strings
drawLayer(_:inContext:), More on CALayer
drawRect(_:) (NSView), drawRect(_:)
dynamic, Making keys observable
E
enabled, Disabling a control
encodeBool(_:forKey:) (NSCoder), Encoding
encodeConditionalObject(_:forKey:) (NSCoder), For the More Curious: Preventing Infinite
Loops
encodeDouble(_:forKey:) (NSCoder), Encoding
encodeFloat(_:forKey:) (NSCoder), Encoding
encodeInt(_:forKey:) (NSCoder), Encoding
encodeObject(_:forKey:) (NSCoder), Encoding
encodeWithCoder(_:) (NSCoding), NSCoder and NSCoding, Encoding
endSheet(_:returnCode:) (NSWindow), Sheets, Present the Sheet
entities (Core Data), Defining the Object Model
entitlements (application), Entitlements
enumerate(), Loops and String Interpolation
enums (enumerations)
with associated values, NSURLSession and asynchronous API design
defined, Enumerations and the Switch Statement
instance methods in, Instance methods
nested, NSURLSession and asynchronous API design
and raw values, Enumerations and raw values
and switch statements, Enumerations and the Switch Statement
errors
(see also debugging, exceptions, NSError)
Auto Layout, Does Not Compute, Part 2: Ambiguous Layout
auto-saving, Undo for Edits
with bindings, Debugging Bindings
and completion handlers, NSURLSession and asynchronous API design
and enums, NSURLSession and asynchronous API design
in event-handling, Adding Key-Value validation to RaiseMan
exceptions, Runtime Errors
HTTP codes, NSURLSession, HTTP status codes, and errors
in delegation, Common errors in implementing a delegate
in playgrounds, Using Standard Types
with KVC, Adding Key-Value validation to RaiseMan
runtime, Runtime Errors
traps, Literals and subscripting, Runtime Errors
with untyped data, Safely Working with Untyped Data Structures
XCTFail(), Refactoring for Testing
event loop, The main event loop
events
(see also mouse events)
errors in handling, Adding Key-Value validation to RaiseMan
in event loop, The main event loop
keyboard, Keyboard Events
mouse (see mouse events)
exception breakpoints, Setting an exception breakpoint
exceptions, Runtime Errors, Setting an exception breakpoint
(see also errors)
expressions
evaluating with LLDB, The LLDB console
and string interpolation, Loops and String Interpolation
extensions (of a class), Extensions
F
factory defaults, NSUserDefaults
fallthrough (switch statement), Enumerations and the Switch Statement
fatalError(), Runtime Errors
faulting (Core Data), Choosing a Cocoa Persistence Technology
fetch requests (Core Data), Fetching Objects from the NSManagedObjectContext
file handles, ZIPspector, Asynchronous Reads, iPing
file wrappers, Saving documents
File's Owner, File's Owner and making connections
files
(see also directories, documents)
copying, A New UI for RanchForecast
custom extensions for, Setting the Extension and Icon for the File Type
custom icons for, Setting the Extension and Icon for the File Type
formats for pasting, Pasteboards and Nil-Targeted Actions
in project, Getting around in Xcode
loading, Loading documents
saving, Saving documents
fileWrapperOfType(_:error:) (NSDocument), Saving documents
fill() (NSBezierPath), Custom Drawing
filter(), For the More Curious: Filtering, For the More Curious: Functional Methods and Minimizing
Closure Syntax
filtering (array controllers), For the More Curious: Filtering
find(), Pre-selecting the default voice
first responder, Keyboard Events, Looking at the XIB file
(see also NSResponder, responder chain)
flagsChanged(_:) (NSResponder), NSResponder
flipped views, For the More Curious: Flipped Views
Float, Number and boolean types
floatForKey(_:) (NSUserDefaults), NSUserDefaults
floating-point types, Number and boolean types, Initializers, Testing in Xcode
floatValue, About Controls
focus rings, Focus Rings
fonts, NSFont, For the More Curious: NSFontManager
for-in, Loops and String Interpolation
forced unwrapping (of optionals), Optionals
formatter (NSControl), Formatters and a control’s objectValue
formatters
about, Formatters and Validation
and controls, Formatters and a control’s objectValue
date, Formatters, programmatically
interaction with locale, Formatters and localization
number, Formatting the Raise Text Field, Formatters, programmatically
vs. KVC validation, Validation with Key-Value Coding
writing custom, Formatters and localization
forwardInvocation(_:) (NSInvocation), Message Passing and NSInvocation
Foundation (framework), The Cocoa Frameworks, Working with Foundation Types, RaiseMan’s
Model Layer
frame (NSView), Views, Rectangles, and Coordinate Systems
frameworks
AppKit, The Cocoa Frameworks, Creating the MainWindowController class
Cocoa, The Cocoa Frameworks, Creating the MainWindowController class
Cocoa Touch, Afterword
Core Data, Basic Core Data, How Core Data Works, Choosing a Cocoa Persistence Technology
Core Graphics, frame, For the More Curious: Core Graphics and Quartz
defined, The Cocoa Frameworks
documentation for, Using the Documentation
for drawing, For the More Curious: Core Graphics and Quartz
Foundation, The Cocoa Frameworks, Working with Foundation Types, RaiseMan’s Model
Layer
importing, Creating the MainWindowController class, RaiseMan’s Model Layer
Quartz, For the More Curious: Core Graphics and Quartz
shipped with OS X, The Cocoa Frameworks
XCTest, Testing in Xcode, Your First Test
func, Instance methods
functional programming, For the More Curious: Functional Methods and Minimizing Closure
Syntax
functions
(see also closures, initializers, methods)
for functional programming, For the More Curious: Functional Methods and Minimizing
Closure Syntax
as types, Reference and Value Types
G
generalPasteboard() (NSPasteboard), NSPasteboard
genstrings (localization), Localizing String Literals, Demystifying NSLocalizedString and
genstrings
gesture recognizers, Gesture Recognizers
Grand Central Dispatch (GCD) (multithreading), For the More Curious: Faster Scattered
graphics contexts, Graphics contexts and states, Saving and Restoring the Graphics State
drawing to screen, For the More Curious: Are You Drawing to the Screen?
graphics states, Graphics contexts and states, Saving and Restoring the Graphics State
groups (project files), Getting around in Xcode
H
Hashable, Collection types
helper objects, Delegation
hierarchies, view, The view layer
hit testing/detection, Improving Hit Detection, Challenge: NSBezierPath-based Hit Testing
HTTP, Web Services
HTTP status codes, NSURLSession, HTTP status codes, and errors
I
.icns file, Setting the Extension and Icon for the File Type
identity inspector, File's Owner and making connections
if-else, Creating the user interface
if-let, Optionals
image wells, Add the Views
images, Drawing Images
implicit animation, Implicit Animation and Actions
implicitly unwrapped optionals, Implicitly unwrapped optionals
importing frameworks, Creating the MainWindowController class, RaiseMan’s Model Layer
importing modules, Your First Test
Info.plist, Info.plist and NSDocumentController
init (keyword), Structures
init(coder:) (NSCoding), NSCoder and NSCoding, Decoding
init(…) (see initializers)
initialFirstResponder (NSWindow), Starting the Chatter Application, The Key View Loop
initializers
about, Initializers
automatic, Structures
chaining, Structures
for classes, Classes
designated, Designated and convenience initializers, Decoding
inheriting, Inheritance, Decoding
parameters, Structures
and properties, Structures
for standard types, Initializers
for structures, Structures
writing, Structures
inspectors
attributes, Configuring view objects
bindings, Binding the text field to the table cell view
connection, Connecting actions
data model, Fetching Objects from the NSManagedObjectContext
identity, File's Owner and making connections
installers (application), A Few Words on Installers
instances, Initializers
Instruments, Tools for Cocoa Programming, Introducing Instruments
Int, Number and boolean types
integer types, Number and boolean types
integerForKey(_:) (NSUserDefaults), NSUserDefaults
integerValue, About Controls
Interface Builder
adding menu items, Getting Your View to Generate PDF Data
adding views in, Adding view objects, Configuring view objects
assistant editor, Making a connection with the assistant editor
connecting dataSource in, Connecting the dataSource outlet
connecting delegate in, Implementing another delegate
connecting objects in, Making Connections, File's Owner and making connections
connections panel, Connecting an outlet
copying and pasting in, Adding two more sliders
creating bindings in, Using bindings
designing custom classes in, Inspectable properties and designable views
File's Owner, File's Owner and making connections
inspecting custom properties in, Inspectable properties and designable views
navigating, Creating the User Interface in Interface Builder
overview, Tools for Cocoa Programming
placeholders, File's Owner and making connections
view hierarchy popover, For the More Curious: Using Interface Builder’s View Hierarchy
Popover
internal (access modifier), Your First Test, For the More Curious: Access Modifiers
interpretKeyEvents(_:) (NSResponder), Receive keyboard events
intrinsicContentSize, Cleaning up with Auto Layout
invalidate() (NSTimer), NSTimer-based Animation
isEmpty (strings), Properties
J
Jobs, Steve, The Story of Cocoa, From NeXTSTEP to OS X to iOS
JSON parsing, Add JSON parsing to ScheduleFetcher
jump bar (Xcode), Creating the user interface
K
key paths, For the More Curious: Key Paths
key view loop, The Key View Loop
key windows, Keyboard Events, Nil-Targeted Actions
key-value coding (see KVC (key-value coding))
key-value observing (see KVO (key-value observing))
key-value pairs
(see also KVC (key-value coding), KVO (key-value observing))
in dictionaries, Collection types
in strings files (localization), Demystifying NSLocalizedString and genstrings
key-value validation, Formatters and Validation, Validation with Key-Value Coding
keyboard events, Keyboard Events
keyCode (NSEvent), NSEvent
keyDown(_:) (NSResponder), NSResponder
keyPathsForValuesAffectingFullName(), For the More Curious: Dependent Keys
keys
(see also KVC (key-value coding), KVO (key-value observing))
dependent, For the More Curious: Dependent Keys
in dictionaries, Collection types
in key-value coding, KVC, KVO, and Bindings
making observable, Making keys observable
observing, Key-value observing
keyUp(_:) (NSResponder), NSResponder
Knight Rider, Delegation
knowsPageRange(_:) (NSView), Dealing with Pagination
KVC (key-value coding)
(see also key-value validation, KVO (key-value observing))
about, KVC, KVO, and Bindings
and proxy objects, Key-Value Coding and To-Many Relationships
and to-many relationships, Key-Value Coding and To-Many Relationships
and bindings, Bindings
and computed properties, KVC and Property Accessors
empty string exceptions, Adding Key-Value validation to RaiseMan
in undo, Key-Value Coding and To-Many Relationships
method naming conventions, Key-Value Coding and To-Many Relationships
methods, KVC, KVO, and Bindings, KVC and nil
and nil, KVC and nil, Adding Key-Value validation to RaiseMan
and predicates, For the More Curious: Filtering
and property accessors, KVC and Property Accessors
and type safety, Making keys observable
validateKEY(_:error:), Adding Key-Value validation to RaiseMan
validation for, Validation with Key-Value Coding
KVO (key-value observing)
about, Key-value observing
and bindings, Key-value observing, Making keys observable
compliance, Making keys observable
dependent keys, For the More Curious: Dependent Keys
in undo, Key-Value Observing
methods, Making keys observable
and Swift, Making keys observable
L
labelFontOfSize(_:) (NSFont), NSFont
labels, Working with Controls
layer (NSView), Scattered
layers
animating, Implicit Animation and Actions
creating, Scattered
drawing, Core Animation
lazy copying (pasteboard), For the More Curious: Lazy Copying
length (NSRange), NSAttributedString
let, Using Standard Types
level indicators, Add the Views
libraries
code snippet, Creating and using an Xcode snippet
object, Adding view objects
lineToPoint() (NSBezierPath), drawRect(_:)
literal values, Literals and subscripting
in testing, A Note on Literals in Testing
LLDB (debugger), Tools for Cocoa Programming, The LLDB console
(see also debugging)
loading documents, Loading documents
loading NIB files, Connecting a window controller and its window
loading objects, Loading and NSKeyedUnarchiver
loadView() (NSViewController), NSViewController, Adding the web view
Localizable.strings, Localizing String Literals
localization
adding localizations to projects, Localizing a XIB File
and NSBundle, NSBundle’s role in localization
base directory, Localizing String Literals
described, Localization and Bundles
directories, Localizing String Literals
and formatters, Formatters and localization
genstrings, Localizing String Literals
global resources, NSBundle’s role in localization
images, Different Mechanisms for Localization
language-specific resources, NSBundle’s role in localization
and NIB files, Different Mechanisms for Localization
NSLocalizedString, Localizing String Literals
of XIB files, Different Mechanisms for Localization, Localizing a XIB File
and plurals, For the More Curious: Localization and Plurality
region-specific resources, NSBundle’s role in localization
replacing string literals, Localizing String Literals
of resources (non-XIB), Different Mechanisms for Localization
and strings files, Localization and Bundles, Different Mechanisms for Localization, Localizing
a XIB File
token ordering in strings, Explicit Ordering of Tokens in Format Strings
ways to achieve, Different Mechanisms for Localization
location (NSRange), NSAttributedString
locationInWindow (NSEvent), NSEvent, Improving Hit Detection
loops
event, The main event loop
examining in Value History, Loops and String Interpolation
for, Loops and String Interpolation
for-in, Loops and String Interpolation
run, For the More Curious: NSRunLoop
in Swift, Loops and String Interpolation
.lproj files (localization), Localization and Bundles
M
Mac App Store (distribution), The Mac App Store
Mac Developer Program, Prerequisites
main bundle, NSBundle
main thread, Multithreading
managed object model (Core Data), Defining the Object Model
managedObjectContext (NSArrayController), Configure the Array Controller
map(), For the More Curious: Functional Methods and Minimizing Closure Syntax
// MARK:, Creating the user interface
master/detail interfaces, Container View Controllers
maxValue (NSSlider), Setting the slider’s range values
mediated file access, Mediated file access and Powerbox
memory management
and arrays, Deallocating objects in a hierarchy
in closures, Closures and capturing
and delegate, Setting the delegate property
and Instruments, Introducing Instruments
manual reference counting, What is ARC?
need for, Memory Management
and notifications, Using Notifications in Chatter
of windows, Windows, Controllers, and Memory Management
for reference types, Memory Management
reference counting, Automatic Reference Counting
strong reference cycles, Strong reference cycles
strong references, Strong and Weak References
and timers, NSTimer and Strong/Weak References
unowned references, Unowned references
for value types, Memory Management
weak references, Strong and Weak References, Strong reference cycles
and zombie objects, Debugging Hints
menu items
creating in Interface Builder, Getting Your View to Generate PDF Data
disabling, Menu Item Validation
hooking up, Starting the Chatter Application
and keyboard events, The Key View Loop
and NSDocument, Saving documents, Loading documents
and NSDocumentController, Info.plist and NSDocumentController
state, Menu Item Validation
targets of, Nil-Targeted Actions
validating, Menu Item Validation
messageFontOfSize(_:) (NSFont), NSFont
messages
(see also methods)
action, About Controls
explained, Swift and Objective-C
and NSInvocation, Message Passing and NSInvocation
methods
(see also individual method names, initializers, messages, properties)
(_:), meaning of, Instance methods
about, Instance methods
action, Connecting the slider’s target and action
application lifecycle, NSApplication and NSApplicationDelegate
class, Getting Voice Data
in classes, Add an instance method
data source, The table view-data source conversation, Connecting the dataSource outlet,
Implementing data source methods
defined, Types in Swift, Instance methods
delegate, For the More Curious: Delegates and Notifications
in enums, Instance methods
in extensions, Extensions
KVC, KVC, KVO, and Bindings
KVO, Making keys observable
naming conventions, Instance methods
optional, Being a delegate, For the More Curious: How Optional Delegate Methods Work
parameters, Instance methods
in protocols, Being a delegate
required, Being a delegate
spelling errors in, Common errors in implementing a delegate
static, Types in Swift
stepping through, Stepping through code
in structures, Instance methods
minValue (NSSlider), Setting the slider’s range values
modal alerts, NSAlert, Modals and Sheets
modal windows, Modal Windows
model key path, Using bindings
model layer (MVC), The model layer
binding to array controller, Binding the Array Controller to the Model
encapsulating in web services, NSURLSession and asynchronous API design
and table views, Delegates and data sources
Model-View-Controller (MVC)
(see also application architecture, controller layer (MVC), model layer (MVC), view layer
(MVC))
defined, Model-View-Controller
and web services, RanchForecast Project, NSURLSession and asynchronous API design
modifierFlags (NSEvent), NSEvent, NSEvent
modules, Your First Test
modules (product), Adding an Array Controller to the XIB
mouse events
(see also events, first responder, NSEvent)
checking click count, Getting Mouse Events
double-clicks, Getting Mouse Events
gesture recognizers, Gesture Recognizers
handler methods, NSResponder
hit testing, Improving Hit Detection
mouseDragged(_:), Starting a drag
rollovers, For the More Curious: Rollovers
mouseDragged(_:) (NSResponder), Starting a drag
mouseEntered(_:) (NSResponder), For the More Curious: Rollovers
mouseExited(_:) (NSResponder), For the More Curious: Rollovers
mouseMoved(_:) (NSResponder), For the More Curious: Rollovers
moveToPoint() (NSBezierPath), drawRect(_:)
multithreading
background threads, NSURLSession and asynchronous API design, Multithreading,
Analyzing output from Instruments
complexities in using, A Deep Chasm Opens Before You
considerations with mutable Array, Thread synchronization
Grand Central Dispatch (GCD), For the More Curious: Faster Scattered
main thread, Multithreading
mutex, Thread synchronization
NSOperationQueue, NSOperationQueue
NSRecursiveLock, Thread synchronization
operation queues, NSURLSession and asynchronous API design
race conditions, A Deep Chasm Opens Before You
thread synchronization, Thread synchronization
threads, Multithreading
and web services, NSURLSession and asynchronous API design
mutability, Implications of reference and value types
mutex (multithreading), Thread synchronization
MVC (see Model-View-Controller (MVC))
N
navigators (Xcode)
about, Getting around in Xcode
breakpoint, Deleting breakpoints
debug, Using breakpoints
project, Getting around in Xcode
needsDisplay (NSView), When is my view drawn?
nested types, NSURLSession and asynchronous API design
nextKeyView (NSView), The Key View Loop
nextResponder (NSResponder), Nil-Targeted Actions
NeXTSTEP, The Story of Cocoa, The view layer
NIB files
(see also XIB files)
defined, XIB files and NIB files
loading, Connecting a window controller and its window
and loading documents, Loading documents
and localization, Different Mechanisms for Localization
names of, Improving Controller Design
naming conventions for, Creating an empty XIB file
nil-targeted actions, Nil-Targeted Actions
notifications
about, What Notifications Are
adding observers for, NSNotificationCenter
constants for, Using Notifications in Chatter
in delegate methods, Delegate protocols and notifications, For the More Curious: Delegates
and Notifications
and memory management, Using Notifications in Chatter
observing, What Notifications Are
posting, NSNotificationCenter, Using Notifications in Chatter
registering for, NSNotificationCenter, Using Notifications in Chatter
removing observers of, NSNotificationCenter, Using Notifications in Chatter
responding to, Using Notifications in Chatter
unregistering for, NSNotificationCenter, Using Notifications in Chatter
and web services, NSURLSession and asynchronous API design
NS prefix, The view layer
NSAlert, Alerts and Closures
NSApplication
(see also AppDelegate, applications)
about, NSApplication and NSApplicationDelegate
in responder chain, Nil-Targeted Actions
sendAction(_:to:from:), For the More Curious: Which Object Sends the Action Message?
NSApplicationDelegate, NSApplication and NSApplicationDelegate
@NSApplicationMain, NSApplication and NSApplicationDelegate
NSArray, Bridging with collections
(see also arrays)
NSArrayController
(see also array controllers)
arrangedObjects, Introducing NSArrayController, Binding the Table View’s Selection to the
Array Controller, Sorting in RaiseMan
content, Introducing NSArrayController
managedObjectContext, Configure the Array Controller
selectionIndexes, Introducing NSArrayController, Binding the Table View’s Selection to the
Array Controller
subclassing for custom objects, Customizing Objects Created by NSArrayController
NSAttributedString, NSAttributedString
(see also NSString, strings)
NSBezierPath, Custom Drawing
NSBox, View Swapping, NerdTabViewController, Challenge: Boxless NerdTabViewController
NSBundle, NSBundle
NSButton, Adding view objects
(see also buttons)
NSCell, A word about NSCell
NSClipView, Table view and related objects
NSCoder, NSCoder and NSCoding
NSCoding (protocol), NSCoder and NSCoding
NSColor, Using the Documentation, For the More Curious: More on NSColor
NSColorWell, Using the Documentation
NSComparisonResult, For the More Curious: The caseInsensitiveCompare(_:) Method
NSControl
(see also controls)
action, About Controls
continuous, A continuous control
enabled, Disabling a control
formatter, Formatters and a control’s objectValue
inheritance hierarchy of, About Controls
setting target/action programmatically, For the More Curious: Setting the Target
Programmatically
target, About Controls, Connecting the slider’s target and action
value properties, About Controls
NSData, Saving documents, Loading documents, Saving and NSKeyedArchiver, Loading and
NSKeyedUnarchiver
NSDateFormatter, Formatters, programmatically
NSDatePicker, Add the Views
NSDictionary, Bridging with collections
(see also dictionaries)
NSDistributedNotificationCenter, What Notifications Are Not
NSDocument
(see also documents, NSDocumentController)
about, NSArrayController, NSDocument
and archiving, The Document Architecture
dataOfType(_:error:), Saving documents
fileWrapperOfType(_:error:), Saving documents
NSDocumentChangeType, For the More Curious: Document-Based Applications Without
Undo
printOperationWithSettings(_:error:), Printing, Adding Printing to RaiseMan
readFromData(_:ofType:error:), Loading documents
readFromFileWrapper(_:ofType:error:), Loading documents
readFromURL(_:ofType:error:), Loading documents
in responder chain, Nil-Targeted Actions
updateChangeCount(_:), For the More Curious: Document-Based Applications Without
Undo
windowControllerDidLoadNib(_:), Loading documents
writeToURL(_:ofType:error:), Saving documents
NSDocumentController, Info.plist and NSDocumentController
(see also NSDocument)
in responder chain, Nil-Targeted Actions
NSDraggingDestination (protocol), Make DieView a Drag Destination
NSDraggingInfo (protocol), Make DieView a Drag Destination
NSDraggingItem, Starting a drag
NSDraggingSource (protocol), Make DieView a Drag Source
NSDragOperation, Drag-and-Drop
NSError, Saving documents, Understanding NSErrorPointer
(see also errors)
NSErrorPointer, Understanding NSErrorPointer
NSEvent
(see also events)
and keyboard events, NSEvent
and mouse events, NSEvent
NSFetchRequest, Fetching Objects from the NSManagedObjectContext
NSFileHandle, ZIPspector, Asynchronous Reads, iPing
NSFileManager, Application Data and URLs
NSFont, NSFont
NSFontAttributeName (NSAttributedString), NSAttributedString
NSFontManager, For the More Curious: NSFontManager
NSForegroundColorAttributeName (NSAttributedString), NSAttributedString
NSFormatter, Formatters and localization
NSGradient, Challenge: Gradients, Add highlighting
NSGraphicsContext, Saving and Restoring the Graphics State, For the More Curious: Are You
Drawing to the Screen?
NSImage
drawing on, Starting a drag
drawInRect(_:), Drawing Images
drawInRect(_:fromRect:op…), Drawing images with finer control
NSImageView, Add the Views
NSInvocation, NSUndoManager
NSKeyedArchiver, NSCoder and NSCoding, Saving and NSKeyedArchiver
NSKeyedUnarchiver, Loading and NSKeyedUnarchiver
NSLevelIndicator, Add the Views
NSLocalizedString (localization), Localizing String Literals
NSMakeRange(), NSAttributedString
NSManagedObject (Core Data), Defining the Object Model
NSManagedObjectContext (Core Data), Defining the Object Model, Configure the Array
Controller, How Core Data Works, Fetching Objects from the NSManagedObjectContext, Choosing a
Cocoa Persistence Technology
NSManagedObjectModel (Core Data), Defining the Object Model, How Core Data Works
NSMatrix, Challenge: Busy Board
NSMenuItem (see menu items)
NSMutableAttributedString, NSAttributedString
NSMutableURLRequest, Web Services APIs
NSNotification, NSNotification
(see also notifications)
NSNotificationCenter
about, What Notifications Are, NSNotificationCenter
commonly-used methods, NSNotificationCenter
and memory management, Using Notifications in Chatter
NSNumber, Basic bridging
NSNumberFormatter, Formatters, programmatically
NSObject
base Cocoa class, Choosing between reference and value types
required for bindings, RanchForecast Project
NSOperationQueue (multithreading), NSURLSession and asynchronous API design,
NSOperationQueue
NSParagraphStyleAttributeName (NSAttributedString), NSAttributedString
NSPasteboard, Pasteboards and Nil-Targeted Actions, For the More Curious: UTIs and the Pasteboard
NSPasteboardItem, NSPasteboard, For the More Curious: Lazy Copying
NSPasteboardReading (protocol), NSPasteboard
NSPasteboardWriting (protocol), NSPasteboard
NSPersistentDocument (Core Data), Defining the Object Model, How Core Data Works
NSPipe, NSTask, ZIPspector, iPing
NSPoint, frame
NSPredicate, For the More Curious: Filtering, Fetching Objects from the NSManagedObjectContext
NSPrintInfo, Challenge: Persist Page Setup
NSPrintOperation, Printing
NSRange, NSAttributedString
NSRect, frame
NSRecursiveLock (multithreading), Thread synchronization
NSResponder
about, About Controls
first responder methods, NSResponder, Accept first responder, Receive keyboard events
mouse event handler methods, NSResponder, Getting Mouse Events
mouseDragged(_:), Starting a drag
nextResponder, Nil-Targeted Actions
responder chain, Nil-Targeted Actions
NSRunLoop, For the More Curious: NSRunLoop
NSSavePanel, Getting Your View to Generate PDF Data
NSScroller, Table view and related objects
NSScrollView, Scroll Views
NSShadow, Saving and Restoring the Graphics State
NSShadowAttributeName (NSAttributedString), NSAttributedString
NSSlider, Working with Controls, Setting the slider’s range values
NSSliderCell, A word about NSCell
NSSortDescriptor, Sorting in RaiseMan, For the More Curious: Sorting Without NSArrayController
NSSpeechSynthesizer
implementing, Synthesizing Speech
voices available for, Getting Voice Data
NSSpeechSynthesizerDelegate (protocol), Being a delegate
NSSplitView, Container View Controllers
NSSplitViewController, Container View Controllers
NSStackView, NerdTabViewController
NSString
(see also NSAttributedString, strings)
drawAtPoint(_:withAttributes:), Drawing Strings and Attributed Strings
drawInRect(_:withAttributes:), Drawing Strings and Attributed Strings
sizeWithAttributes(_:), Drawing Strings and Attributed Strings
and String, Basic bridging
NSSuperscriptAttributeName (NSAttributedString), NSAttributedString
NSTableColumn, Table columns
NSTableHeaderView, Table view and related objects, Table columns
NSTableView
(see also NSTableViewDataSource, NSTableViewDelegate, table views)
dataSource, Delegates and data sources
reloadData(), Challenge: Make a Data Source
sortDescriptors, Sorting in RaiseMan
NSTableViewDataSource
implementing, The NSTableViewDataSource Protocol
numberOfRowsInTableView(_:), The table view-data source conversation, Connecting
the dataSource outlet, Implementing data source methods
tableView(_:objectValueForTa…), The table view-data source conversation, Connecting
the dataSource outlet, Implementing data source methods
NSTableViewDelegate, The NSTableViewDelegate Protocol
NSTabView, Container View Controllers
NSTabViewController, Container View Controllers, View Swapping
NSTask, NSTask, ZIPspector, iPing
NSTextField (see text fields)
NSTextFieldCell, A word about NSCell
NSTimer, NSTimer
NSUnderlineColorAttributeName (NSAttributedString), NSAttributedString
NSUnderlineStyleAttributeName (NSAttributedString), NSAttributedString
NSUndoManager, Using NSUndoManager, For the More Curious: Windows and the Undo Manager
NSURL, Web Services APIs
NSURLRequest, Web Services APIs
NSURLSession, Web Services APIs, NSURLSession, HTTP status codes, and errors
NSURLSessionDataTask, NSURLSession and asynchronous API design
NSURLSessionTask, Web Services APIs, NSURLSession and asynchronous API design
NSUserDefaults, User Defaults
NSValueTransformer, For the More Curious: NSValueTransformer
NSView
(see also NSViewController, views)
addSubview(_:), View Swapping
beginDraggingSessionWithItems(…), Starting a drag
bounds, bounds
convertPoint(_:fromView:), Improving Hit Detection
convertPoint(_:toView:), Improving Hit Detection
custom subclasses of, NSView and Drawing, Creating a view subclass
dataWithPDFInsideRect(_:), Getting Your View to Generate PDF Data
drawFocusRingMask(), Focus Rings
drawRect(_:), drawRect(_:)
flipped, For the More Curious: Flipped Views
frame, Views, Rectangles, and Coordinate Systems
intrinsicContentSize, Cleaning up with Auto Layout
knowsPageRange(_:), Dealing with Pagination
layer, Scattered
needsDisplay, When is my view drawn?
nextKeyView, The Key View Loop
_NSViewBackingLayer, More on CALayer
rectForPage(_:), Dealing with Pagination
registerForDraggedTypes(_:), Make DieView a Drag Destination,
registerForDraggedTypes(_:)
removeFromSuperview(), View Swapping
setNeedsDisplayInRect(_:), For the More Curious: Dirty Rects
viewDidMoveToWindow(), For the More Curious: Rollovers
wantsLayer, Scattered
_NSViewBackingLayer, More on CALayer
NSViewController
(see also NSView, view controllers)
about, NSViewController
loadView(), NSViewController, Adding the web view
nibName, Starting the ViewControl Application
in responder chain, Nil-Targeted Actions
view, NSViewController
viewLoaded, NerdTabViewController
NSWindow
(see also NSWindowController, NSWindowDelegate (protocol), windows)
beginCriticalSheet(_:completi…), Sheets
beginSheet(_:completionHandler:), Sheets, Present the Sheet
endSheet(_:returnCode:), Sheets, Present the Sheet
firstResponder, Keyboard Events
initialFirstResponder, The Key View Loop
sheet methods, Sheets
sheetParent, Present the Sheet
visualizeConstraints(_:), Does Not Compute, Part 2: Ambiguous Layout
NSWindowController
(see also NSWindow, window controllers)
and NSDocument, NSWindowController
in responder chain, Nil-Targeted Actions
window, Connecting a window controller and its window
windowDidLoad(), Controls and Outlets
windowNibName, Improving Controller Design, Connecting a window controller and its
window
NSWindowDelegate (protocol), Implementing another delegate
NSWorkspace, Opening URLs
NSXMLDocument, For the More Curious: Parsing XML
NSXMLNode, For the More Curious: Parsing XML
number formatters, Formatting the Raise Text Field, Formatters, programmatically
numberOfRowsInTableView(_:), The table view-data source conversation, Connecting the
dataSource outlet, Implementing data source methods
O
object library (Xcode), Adding view objects
object-oriented programming, Application Design
objectForKey(_:) (NSUserDefaults), NSUserDefaults
Objective-C
about, Introducing the Swift Language
and bindings, RanchForecast Project
dynamic, Making keys observable
in documentation, Using the Documentation
messages, Swift and Objective-C, Message Passing and NSInvocation
reference types, Choosing between reference and value types
and Swift, Swift and Objective-C
Objective-C
(see also Cocoa, KVC (key-value coding), KVO (key-value observing))
objects
(see also classes, methods, properties)
about, Application Design
and memory management, Memory Management
objectValue (NSControl)
about, About Controls
binding to, Binding the text field to the table cell view
formatting, Formatters and a control’s objectValue
and table data, Table cell views
observers (notifications), What Notifications Are
observers (property), Updating Buttons
OpenStep, NeXTSTEP and OpenStep
operation masks (drag-and-drop), For the More Curious: Operation Mask
operation queues, NSURLSession and asynchronous API design, NSOperationQueue
operators, overloading, Operator Overloading
optional (protocol methods), Being a delegate, For the More Curious: How Optional Delegate
Methods Work
optional binding, Optionals
optional chaining, For the More Curious: How Optional Delegate Methods Work
optionals
about, Optionals
as?, Bridging with collections
and casting, Bridging with collections
chaining, For the More Curious: How Optional Delegate Methods Work
and dictionary subscripting, Subscripting dictionaries
forced unwrapping of, Optionals
if-let, Optionals
implicitly unwrapped, Implicitly unwrapped optionals
and optional binding, Optionals
syntax for, Optionals
unwrapping, Optionals
origin (NSRect), frame
OS X
(see also Cocoa)
frameworks for, The Cocoa Frameworks
history of, From NeXTSTEP to OS X to iOS
as Unix-based, OSX, Unix, and Cocoa
outlets
(see also properties)
assistant editor, connecting with, Making a connection with the assistant editor
connecting in Interface Builder, Connecting an outlet, Making a connection with the
assistant editor
creating in code, Creating an outlet
dataSource, Connecting the dataSource outlet
defined, Making Connections
delegate, Implementing another delegate, The NSTableViewDelegate Protocol
as implicitly unwrapped optionals, Implicitly unwrapped optionals
as weak references, Strong reference cycles
when to use, Controls and Outlets
overloading operators, Operator Overloading
P
packaging (applications), A Few Words on Installers
pagination (printing multiple pages), Dealing with Pagination
parameter names, Instance methods
pasteboards, Pasteboards and Nil-Targeted Actions, For the More Curious: UTIs and the Pasteboard
pasting (implementing) (see pasteboards)
PDFs, generating, Getting Your View to Generate PDF Data
performance issues, Introducing Instruments, Analyzing output from Instruments
performDragOperation(_:) (NSDraggingDestination), Make DieView a Drag Destination
persistence (see archiving, Core Data)
pipes, ZIPspector, iPing
placeholder text, Creating the user interface
placeholders, File's Owner and making connections
playgrounds (Xcode), Using Standard Types
errors in, Using Standard Types
Value History, Loops and String Interpolation
viewing console in, Optionals
pointers, Reference and Value Types
points (in drawing), frame
postNotification(_:) (NSNotificationCenter), NSNotificationCenter
postNotificationName(_:object:) (NSNotificationCenter), NSNotificationCenter
Powerbox, Mediated file access and Powerbox
predicates, For the More Curious: Filtering, Fetching Objects from the NSManagedObjectContext
preferences (user), User Defaults
prepareForDragOperation(_:) (NSDraggingDestination), Make DieView a Drag
Destination
preprocessor directives, Preprocessor Directives: Using Build Configurations to Change Behavior
pressure (NSEvent), NSEvent
Printable (protocol), Making Types Printable
printing, Printing
printOperationWithSettings(_:error:) (NSDocument), Printing, Adding Printing to
RaiseMan
private (access modifier), For the More Curious: Access Modifiers
product modules, Adding an Array Controller to the XIB
programmer errors, Runtime Errors
programming
functional, For the More Curious: Functional Methods and Minimizing Closure Syntax
object-oriented, Application Design
project navigator, Getting around in Xcode
projects
(see also applications)
copying files into, A New UI for RanchForecast
creating, Creating an Xcode Project
source directories of, Localizing String Literals
targets in, Testing in Xcode
properties
(see also methods, outlets)
in attributes inspector, A continuous control
computed, Computed Properties, KVC and Property Accessors
default values, Classes
defined, Properties
didSet, Updating Buttons
and extensions, Extensions
initializing, Structures, Classes
making @IBInspectable, Inspectable properties and designable views
shadowing, Showing the Window
stored, Computed Properties
willSet, Updating Buttons
property observers, Updating Buttons
propertyListForType(_:) (NSPasteboardItem), NSPasteboard
protocols
CALayerDelegate, More on CALayer
conforming to, Being a delegate, Conforming to a protocol, Implementing a delegate method
creating, For the More Curious: Creating a Protocol, Adding Tab Images
defining roles with, Being a delegate
documentation for, Implementing another delegate
header files for, Common errors in implementing a delegate
NSApplicationDelegate, NSApplication and NSApplicationDelegate
NSCoding, NSCoder and NSCoding
NSDraggingDestination, Make DieView a Drag Destination
NSDraggingInfo, Make DieView a Drag Destination
NSDraggingSource, Make DieView a Drag Source
NSPasteboardReading, NSPasteboard
NSPasteboardWriting, NSPasteboard
NSSpeechSynthesizerDelegate, Being a delegate
NSTableViewDataSource, The NSTableViewDataSource Protocol
NSTableViewDelegate, The NSTableViewDelegate Protocol, Implementing a delegate
method
NSWindowDelegate, Implementing another delegate
optional methods in, Being a delegate, For the More Curious: How Optional Delegate
Methods Work
Printable, Making Types Printable
reference pages for, Implementing another delegate
required methods in, Being a delegate
public (access modifier), Your First Test, For the More Curious: Access Modifiers
Q
Quartz (framework), For the More Curious: Core Graphics and Quartz
Quick Help (Xcode), Inferring types
R
race conditions (multithreading), A Deep Chasm Opens Before You
radio buttons (NSButton), Challenge: Busy Board
Range, Loops and String Interpolation
rawValue (enums), Enumerations and raw values
readFromData(_:ofType:error:) (NSDocument), Loading documents
readFromFileWrapper(_:ofType:error:) (NSDocument), Loading documents
readFromURL(_:ofType:error:), ZIPspector
readFromURL(_:ofType:error:) (NSDocument), Loading documents
readObjects(_:options:) (NSPasteboard), NSPasteboard
receipt validation, Receipt Validation
receivers, Swift and Objective-C
recoverable errors, Runtime Errors
rectForPage(_:) (NSView), Dealing with Pagination
redo stack, How the NSUndoManager Works
reduce(), For the More Curious: Functional Methods and Minimizing Closure Syntax
reference counting, Automatic Reference Counting, What is ARC?
reference types, Reference and Value Types
references, strong, Strong and Weak References
references, unowned, Unowned references
references, weak, Strong and Weak References
registerDefaults(_:) (NSUserDefaults), NSUserDefaults
registerForDraggedTypes(_:), Make DieView a Drag Destination
registerForDraggedTypes(_:) (NSView), Make DieView a Drag Destination,
registerForDraggedTypes(_:)
relationships (Core Data), Defining the Object Model
release builds, Debugging Hints, Build Configurations, Creating a Release Build
reloadData() (NSTableView), Challenge: Make a Data Source
removeFromSuperview() (NSView), View Swapping
removeObjectForKey(_:) (NSUserDefaults), NSUserDefaults
removeObserver(_:) (NSNotificationCenter), NSNotificationCenter
representedObject, Adding Tab Images
resignFirstResponder() (NSResponder), NSResponder
resources
(see also bundles)
application access to, Entitlements
for future learning, Afterword
in bundles, NSBundle
localizing, Localization and Bundles
responder chain, Nil-Targeted Actions
restoreGraphicsState() (NSGraphicsContext), Saving and Restoring the Graphics State
reverse(), Instance methods
RoboCop, Delegation
run loops, For the More Curious: NSRunLoop, Asynchronous Reads
runModal() (NSAlert), NSAlert
runModalForWindow(_:) (NSApplication), Modal Windows
runtime errors, Runtime Errors
S
sandboxing (applications), App Sandbox
saveGraphicsState() (NSGraphicsContext), Saving and Restoring the Graphics State
saving documents, Saving documents, For the More Curious: Automatic Document Saving
saving objects, Saving and NSKeyedArchiver
SAX parsing, For the More Curious: Parsing XML
scenes (storyboards), Storyboards, Adding the course list, For the More Curious: How is the
Storyboard Loaded?
scheduledTimerWithTimeInterval (NSTimer), NSTimer-based Animation
Scheme Editor, Build Configurations, Preprocessor Directives: Using Build Configurations to Change
Behavior, Creating a Release Build
scroll views, Table view and related objects, Scroll Views
scrollers, Table view and related objects
Sculley, John, The Story of Cocoa
segues (storyboards), Storyboards, A New UI for RanchForecast
selectionIndexes (NSArrayController), Introducing NSArrayController, Binding the Table
View’s Selection to the Array Controller
selectors, Swift and Objective-C
selectTab(_:), NerdTabViewController
selectTabAtIndex(_:), NerdTabViewController
self
in closures, Closures and capturing
in initializers, Structures
in instance methods, Using self in instance methods
and property names, Showing the Window
sendAction(_:to:from:) (NSApplication), For the More Curious: Which Object Sends the
Action Message?
sender (action methods), Connecting the slider’s target and action
setBool(_:forKey:) (NSUserDefaults), NSUserDefaults
setData(_:forType:) (NSPasteboardItem), NSPasteboard
setFloat(_:forKey:) (NSUserDefaults), NSUserDefaults
setInteger(_:forKey:) (NSUserDefaults), NSUserDefaults
setNeedsDisplayInRect(_:) (NSView), For the More Curious: Dirty Rects
setNilValueForKey(_:), KVC and nil
setObject(_:forKey:) (NSUserDefaults), NSUserDefaults
setPropertyList(_:forType:) (NSPasteboardItem), NSPasteboard
sets, Collection types, Initializers
setString(_:forType:) (NSPasteboardItem), NSPasteboard
setUp(), Testing in Xcode, Creating a Consistent Testing Environment
setValue(_:forKey:), KVC, KVO, and Bindings
shadowing (properties), Showing the Window
shadows, drawing, Saving and Restoring the Graphics State
Shared User Defaults Controller, Using bindings
sharedDocumentController() (NSDocumentController), Info.plist and
NSDocumentController
sheetParent (NSWindow), Present the Sheet
sheets
and alerts, Modals and Sheets
vs. modal window, Modal Windows
presenting, Present the Sheet
Visible At Launch, Lay Out the Interface
size (NSAttributedString), Drawing Strings and Attributed Strings
size (NSRect), frame
sizeWithAttributes(_:) (NSString), Drawing Strings and Attributed Strings
sliders
about, Working with Controls
setting range of, Setting the slider’s range values
snippets (code), Creating and using an Xcode snippet
sort descriptors, Sorting in RaiseMan, For the More Curious: Sorting Without NSArrayController,
Fetching Objects from the NSManagedObjectContext
sortDescriptors (NSTableView), Sorting in RaiseMan
sorting (array controllers), Sorting in RaiseMan
sorting (table views), For the More Curious: Sorting Without NSArrayController
speech synthesis, implementing, Synthesizing Speech
split view controllers, A New UI for RanchForecast
springs (autoresizing), For the More Curious: Autoresizing Masks
SQLite, Persistent Store Types
stack (memory), Using breakpoints
stack trace, Using breakpoints
stacks, undo and redo, How the NSUndoManager Works
standardUserDefaults() (NSUserDefaults), NSUserDefaults
states, graphics, Graphics contexts and states, Saving and Restoring the Graphics State
static methods, Types in Swift
stopModalWithCode(_:) (NSApplication), Modal Windows
storage, application, Application Data and URLs
store types (Core Data), Persistent Store Types
storyboards
about, Storyboards
loading, For the More Curious: How is the Storyboard Loaded?
scenes, Storyboards, Adding the course list, For the More Curious: How is the Storyboard
Loaded?
segues, Storyboards, A New UI for RanchForecast
string interpolation, Loops and String Interpolation
stringForType(_:) (NSPasteboardItem), NSPasteboard
strings
(see also NSAttributedString, NSString)
initializers for, Initializers
interpolation, Loops and String Interpolation
isEmpty, Properties
literal, Literals and subscripting
NSAttributedString, NSAttributedString
and NSString, Basic bridging
strings files (localization), Localization and Bundles, Different Mechanisms for Localization,
Localizing a XIB File, NSBundle’s role in localization
strings tables (localization), Localizing String Literals
stringValue, About Controls
stroke() (NSBezierPath), Custom Drawing, drawRect(_:)
strong reference cycles, Strong reference cycles
strong references, Strong and Weak References
structures, Structures
vs. classes, Reference and Value Types
struts (autoresizing), For the More Curious: Autoresizing Masks
subclassing
vs. extensions, Extensions
vs. helper objects, Delegation
subscripting
arrays, Literals and subscripting
dictionaries, Subscripting dictionaries
subviews (NSView), Deallocating objects in a hierarchy
Swift, Types in Swift
about, Introducing the Swift Language , Swift Types
documentation for, Exploring Apple’s Swift Documentation
and Objective-C, Swift and Objective-C
switch, Enumerations and the Switch Statement
switch statements, Enumerations and the Switch Statement
systemFontOfSize(_:) (NSFont), NSFont
T
tab images, Adding Tab Images
tab view controllers, Add a Tab View Controller, View Swapping, NerdTabViewController
table cell views
about, Table cell views
with checkbox, Add the Views
different views in, Add the Views
with formatters, Add the Views
with images, Add the Views
table columns, Table columns
table header views, Table view and related objects, Table columns
table view
delegate’s role, Delegates and data sources
table view cells, Table cell views
table views
(see also NSTableView, NSTableViewDataSource, NSTableViewDelegate)
about, About Table Views
Apple’s guide to, Pre-selecting the default voice
binding to array controllers, Introducing NSArrayController, Binding the Table View’s
Content to the Array Controller
bindings, data supplied from, Connecting the dataSource outlet
cell-based, Tables, Cells, and Views
cells in, Tables, Cells, and Views
and clip views, Table view and related objects
as collections of classes, Table view and related objects
columns in, Table columns
and data sources, Delegates and data sources, The table view-data source conversation, The
NSTableViewDataSource Protocol
data source methods vs. bindings, Connecting the dataSource outlet, Pre-selecting the
default voice
delegate for, The NSTableViewDelegate Protocol, Implementing a delegate method
displaying data with bindings, Binding the text field to the table cell view
header views, Table view and related objects, Table columns
in Interface Builder, Adding a Table View
and scroll views, Table view and related objects
and scrollers, Table view and related objects
Selection Indexes, Introducing NSArrayController
sorting in, Sorting in RaiseMan, For the More Curious: Sorting Without NSArrayController
view-based, Tables, Cells, and Views
tableView(_:objectValueForTa…), The table view-data source conversation, Connecting the
dataSource outlet, Implementing data source methods
tableViewSelectionDidChange(_:), Implementing a delegate method
Taligent, Controls
.tar files, Challenge: .tar and .tgz Files
target (NSControl)
setting programmatically, For the More Curious: Setting the Target Programmatically
target-action (NSControl), About Controls, Opening URLs
targets
(see also actions, controls)
about, About Controls
array controllers as, Connecting the Add Employee Button
nil, Nil-Targeted Actions
project, Creating a Release Build
as weak references, Connecting the slider’s target and action
targets (in projects), Testing in Xcode
tearDown(), Testing in Xcode, Creating a Consistent Testing Environment
test fixtures, Sharing Constants
testExample(), Testing in Xcode
testing (see unit testing)
testPerformanceExample(), Testing in Xcode
text fields
alignment of, Configuring view objects
as table view cells, Table cell views
behavior, setting, Configuring view objects
cut, copy, and paste, Connecting the Model Layer to the Controller
editable, Configuring view objects
changing fonts, Configuring view objects
in Interface Builder, Adding view objects
as labels, Working with Controls
and placeholder text, Creating the user interface
selectable, Configuring view objects
styles of, Working with Controls
.tgz fies, Challenge: .tar and .tgz Files
thread synchronization (multithreading), Thread synchronization
threads (see multithreading)
Time Profiler (Instruments), Introducing Instruments
timers, NSTimer
timestamp (NSEvent), NSEvent
titleBarFontOfSize(_:) (NSFont), NSFont
toll-free bridging, Working with Foundation Types
toolTipsFontOfSize(_:) (NSFont), NSFont
top-level objects, Windows, Controllers, and Memory Management
trailing closure syntax, For the More Curious: Functional Methods and Minimizing Closure Syntax
traps, Literals and subscripting
tuples, Loops and String Interpolation
type inference, Inferring types
type safety, Making keys observable
types
(see also classes, enumerations, structures, UTIs (universal type identifiers))
boolean, Number and boolean types
bridging, Working with Foundation Types
casting, Basic bridging
floating-point, Number and boolean types, Initializers
hashable, Collection types
inference of, Inferring types
instances of, Initializers
integer, Number and boolean types
nested, NSURLSession and asynchronous API design
reference, Reference and Value Types
sets, Collection types, Initializers
specifying, Specifying types
tuples, Loops and String Interpolation
values, Reference and Value Types
types (NSPasteboardItem), NSPasteboard
U
unarchiveObjectWithData(_:), Loading and NSKeyedUnarchiver
unarchiving (NIB files), XIB files and NIB files, Connecting a window controller and its window
undo
about, NSUndoManager
implementing, Undo for Edits
undo stack, How the NSUndoManager Works
Unicode warning for Localizable.strings, Localizing String Literals
unit testing
about, Unit Testing
assertions, Testing in Xcode
asynchronous tasks, For the More Curious: Asynchronous Testing
planning for, Refactoring for Testing
refactoring for, Refactoring for Testing
test fixtures, Sharing Constants
using constants in, A Note on Literals in Testing
Unix, NeXTSTEP and OpenStep, OSX, Unix, and Cocoa
unowned references, Unowned references
updateChangeCount(_:) (NSDocument), For the More Curious: Document-Based
Applications Without Undo
URLForResource(_:withExtension:) (NSBundle), NSBundle’s role in localization
URLsForDirectory(_:inDomains:) (NSFileManager), Application Data and URLs
user defaults, User Defaults
user interfaces
(see also Interface Builder, views)
with bindings, Bindings, Binding other attributes
creating in Interface Builder, Creating the MainWindowController class
master/detail, Container View Controllers
as view layer in MVC, The view layer
user preferences, User Defaults
userFixedPitchFontOfSize(_:) (NSFont), NSFont
userFontOfSize(_:) (NSFont), NSFont
utilities (Xcode), Getting around in Xcode
UTIs (universal type identifiers)
about, For the More Curious: Universal Type Identifiers
exported, Setting the Extension and Icon for the File Type
and pasteboards, NSPasteboard, For the More Curious: UTIs and the Pasteboard
V
validateKEY(_:error:), Adding Key-Value validation to RaiseMan
validateMenuItem(_:) (NSMenuValidation), Menu Item Validation
validation
key-value, Formatters and Validation, Validation with Key-Value Coding
value transformers, For the More Curious: NSValueTransformer
value transformers, For the More Curious: NSValueTransformer
value types, Reference and Value Types
valueForKey(_:), KVC, KVO, and Bindings
var, Using Standard Types
variables, Using Standard Types
capturing in closures, Closures and capturing
variables view, Add an instance method, Using breakpoints
view (NSViewController), NSViewController
view controllers, A New UI for RanchForecast
(see also NSViewController, views)
about, View Controllers
architecture, Connecting the Course List Selection with the Web View
benefits of using, View Controllers, Starting the ViewControl Application
container, Container View Controllers
instantiating in storyboards, For the More Curious: How is the Storyboard Loaded?
loading views, NerdTabViewController
making reusable, Adding the web view
and memory management, Windows, Controllers, and Memory Management
and NIB files, Starting the ViewControl Application
pre-OSX 10.0, Considerations for OS X 10.9 and Earlier
reason for, View Controllers
split, A New UI for RanchForecast
and swapping views, View Swapping
tab, Add a Tab View Controller, View Swapping
views of, NSViewController, Adding the web view
ways to connect multiple, Connecting the Course List Selection with the Web View
when to use, View Controllers vs. Window Controllers
vs. window controllers, Starting the ViewControl Application, View Controllers vs. Window
Controllers
view hierarchies, The view layer, Adding view objects
and responder chain, Nil-Targeted Actions
view hierarchy popover, For the More Curious: Using Interface Builder’s View Hierarchy Popover
view layer (MVC), The view layer
(see also views)
view swapping, View Swapping
view-based tables, Tables, Cells, and Views
viewDidMoveToWindow() (NSView), For the More Curious: Rollovers
viewLoaded (NSViewController), NerdTabViewController
views
(see also NSView, view controllers)
adding in Interface Builder, Adding view objects, Configuring view objects
archiving, XIB files and NIB files
attributes, configuring, Configuring view objects
binding to array controller, Introducing NSArrayController
connecting in Interface Builder, Making Connections, Making a connection with the
assistant editor
content views, Creating an empty XIB file
copying and pasting, Adding two more sliders
creating custom, Creating a view subclass
creating programmatically, Creating Views Programmatically
described, NSView and Drawing
drawing, bounds
and first-responder status, Keyboard Events
flipped, For the More Curious: Flipped Views
and focus rings, Focus Rings
hierarchies of, The view layer, Adding view objects
in MVC, The view layer
and key view loop, The Key View Loop
in NIB files, XIB files and NIB files
positioning, Views, Rectangles, and Coordinate Systems
unarchiving, XIB files and NIB files, Connecting a window controller and its window
WKWebView, Adding the web view
in XIB files, XIB files and NIB files
Visible At Launch, Creating an empty XIB file, Connecting a window controller and its window,
Lay Out the Interface
Visual Format Language (Auto Layout), Visual Format Language
visualizeConstraints(_:) (NSWindow), Does Not Compute, Part 2: Ambiguous Layout
W
wantsLayer (NSView), Scattered
weak, Strong and Weak References
weak references, Strong and Weak References, Strong reference cycles
web services
about, Web Services
and asynchronous tasks, NSURLSession and asynchronous API design
and completion handlers, NSURLSession and asynchronous API design
and completion handlers, NSURLSession and asynchronous API design
and HTTP, Web Services
making requests, Web Services APIs, NSURLSession and asynchronous API design
reusing classes with, NSURLSession and asynchronous API design
synchronous API, NSURLSession and asynchronous API design
testing asynchronous tasks, For the More Curious: Asynchronous Testing
and threads, NSURLSession and asynchronous API design
ways to fetch asynchronously, NSURLSession and asynchronous API design
willChangeValueForKey(_:), Making keys observable
willSet (property observer), Updating Buttons
window (NSEvent), NSEvent
window (NSWindowController), Connecting a window controller and its window
window controllers
(see also NSWindowController, windows)
and AppDelegate, Showing the Window, Improving Controller Design
and documents, NSWindowController
initializing, Showing the Window
instantiating in storyboards, For the More Curious: How is the Storyboard Loaded?
loading NIB files, Connecting a window controller and its window
loading windows, Connecting a window controller and its window
and NIB names, Improving Controller Design
vs. view controllers, Starting the ViewControl Application, View Controllers vs. Window
Controllers
when to use, View Controllers vs. Window Controllers
and windows, Connecting a window controller and its window
window servers, NeXTSTEP and OpenStep, OSX, Unix, and Cocoa
windowControllerDidLoadNib(_:) (NSDocument), Loading documents
windowDidLoad(), Controls and Outlets
windowNibName, Improving Controller Design
windowNibName (NSWindowController), Improving Controller Design, Connecting a
window controller and its window
windows
(see also NSWindow, window controllers)
and content views, The view layer
described, NSView and Drawing
disabling resizing of, Disabling resizing of the window
first responders of, Keyboard Events
key, Keyboard Events
loading, Connecting a window controller and its window
modal, Modal Windows
resizing, Disabling resizing of the window
showing, Creating an instance of MainWindowController
and view hierarchies, The view layer, Adding view objects
Visible At Launch, Creating an empty XIB file, Connecting a window controller and its
window, Lay Out the Interface
and window controllers, Connecting a window controller and its window
windowShouldClose(_:) (NSWindowDelegate), Implementing another delegate
WKWebView, Adding the web view
writeObjects(_:) (NSPasteboard), NSPasteboard
writeToURL(_:ofType:error:) (NSDocument), Saving documents
X
.xcdatamodeld (Core Data), Defining the Object Model
Xcode
(see also debugging tools, Interface Builder)
adding localizations in, Localizing a XIB File
assistant editor, Making a connection with the assistant editor
attributes inspector, Configuring view objects
auto-complete, Implementing a delegate method
Cocoa documentation in, Using the Documentation
code snippets in, Creating and using an Xcode snippet
connections inspector, Connecting actions
creating classes in, Creating the MainWindowController class
creating projects in, Creating an Xcode Project
data model inspector, Fetching Objects from the NSManagedObjectContext
debugger, Using the Debugger
files in, Getting around in Xcode
groups, Getting around in Xcode
identity inspector, File's Owner and making connections
Instruments, Introducing Instruments
jump bar, Creating the user interface
modules, Your First Test
navigator area, Getting around in Xcode
navigators, Getting around in Xcode
overview, Tools for Cocoa Programming
playgrounds, Using Standard Types
project navigator, Getting around in Xcode
project source directories, Localizing String Literals
project window, Getting around in Xcode
Quick Help, Inferring types
saving files in, XIB files and NIB files
testing in, Unit Testing
Time Profiler, Introducing Instruments
using // MARK:, Creating the user interface
utilities area, Getting around in Xcode
variables view, Add an instance method, Using breakpoints
XCTAssert(expr), Testing in Xcode
XCTAssertEqual(expr), Testing in Xcode
XCTAssertEqualWithAccuracy(expr), Testing in Xcode
XCTAssertFalse(expr), Testing in Xcode
XCTAssertNotEqual(expr), Testing in Xcode
XCTAssertNotEqualWithAccuracy(expr), Testing in Xcode
XCTAssertNotNil(expr), Testing in Xcode
XCTAssertTrue(expr), Testing in Xcode
XCTest (framework), Testing in Xcode, Your First Test
XCTestCase
setUp(), Testing in Xcode, Creating a Consistent Testing Environment
tearDown(), Testing in Xcode, Creating a Consistent Testing Environment
testExample(), Testing in Xcode
testPerformanceExample(), Testing in Xcode
XCTFail(), Testing in Xcode, Refactoring for Testing
XIB files
(see also NIB files)
archiving files in, XIB files and NIB files
connections in, Making Connections, File's Owner and making connections
defined, XIB files and NIB files
File's Owner, File's Owner and making connections
localizing, Different Mechanisms for Localization, Localizing a XIB File
naming conventions for, Creating an empty XIB file
and NIB files, XIB files and NIB files
placeholders, File's Owner and making connections
pronounced as zib, Creating the MainWindowController class
XML parsing, For the More Curious: Parsing XML
Z
zip files
for distribution, Creating a Release Build
inspecting, ZIPspector
zipinfo utility, ZIPspector
zombie objects, Debugging Hints