Ios 9 SDK Development - p2 - 0
Ios 9 SDK Development - p2 - 0
I like this book. I like its approach to building something real in Swift. The result
is an app that feels good and is useful. Along the way, you learn the basics of iOS
development from an experienced pro. Highly recommended.
➤ Eric J. Knapp
Program Director, Mobile Applications Development, Madison College
iOS 9 SDK Development is the perfect book to get your feet wet with iOS. The
authors introduce you to iOS by way of Swift, giving you cutting-edge skills at the
perfect time. Whether you’re new to programming or simply new to Apple platforms,
this book will leave you ready to create your own amazing apps.
➤ Jeff Kelley
iOS developer at Detroit Labs and author of Developing for Apple Watch, Second
Edition
This book neatly covers building apps with iOS 9 from the ground up, starting
with the basic tools and the nuances of the Swift language, and then progressing
through interface design. You’ll see how to build interfaces that auto-resize
cleanly to multiple screen sizes. There’s more to building an app than just assem-
bling the pieces and getting it to compile. With iOS 9 SDK Development, you’ll also
learn invaluable testing practices, and the right approach using the tools at your
disposal to fix things when they go wrong. The chapters on closures are particu-
larly well placed for people new to Swift. I’d recommend this book to anyone.
➤ Kevin J. Garriott
Director, Mobile Technology, Rockfish
Whether you’re new to iOS programming or just need some help getting up to
speed on iOS and Swift, this is the perfect book for you. Chris and Janie take you
on a well-thought-out and fun journey into iOS SDK development.
➤ Dave Klein
Founder of CocoaConf and author of Grails: A Quick-Start Guide
Acknowledgments . . . . . . . . . . . ix
Preface . . . . . . . . . . . . . . xi
Index . . . . . . . . . . . . . . 311
So, for this book being in your hands or on your screen, thanks start with
the Pragmatic Programmers. Working with the Prags themselves, Dave and
Andy, and Susannah Pfalzer, recognized the need for the Prags introductory
book on iOS to become an annual thing, if it’s to be a viable title and serve
as a prerequisite for other Prags iOS titles. Together, we worked out a plan
that would make for a book with enough new content and revisions to be
worthy of a new release, without killing the primary author by trying to get
300 pages out of him in five months (while holding down a day job). Of course,
that still wouldn’t have been possible without the support of editor Rebecca
Gulick, who helped keep everything on track, caught lots of mistakes, and
had a keen ear for when the prose needed to stop info-dumping and work
more intuitively with the reader.
Thanks also go out to friends and colleagues in the Mac/iOS (and watchOS
and tvOS…) development community, who helped out with questions, feed-
back, and encouragement on Twitter and IRL at conferences like WWDC and
CocoaConf. Speaking of CocoaConf, thanks as always to Dave Klein and his
family for putting on that traveling event, which is still my favorite way to
meet fellow developers and catch up on the state of the art of iOS program-
ming, outside of Apple’s official channels.
Speaking of conferences, Janie spent most of the summer and fall putting
together ambitious new talks for CocoaConf, 360iDev, and CocoaLove, so she
was quite limited in what she could contribute to the book this time around.
Still, we’ve incorporated all her work from the previous book, and updated it
for iOS 9, and she did give us a new story sidebar about her continuing
development as an iOS developer. It’s been great to watch her career take off
over the last few years, and if you see her speaking at a conference near you,
be sure to go see her.
Once again, the Prags’ “beta book” process allowed us to roll out a mostly
complete book for early feedback as soon as Apple’s NDA dropped, allowing
us to make improvements and corrections while finishing up the last few
chapters. The book’s tech reviewers also gave us great feedback to build on:
thanks to Jacob Chae, Kevin Garriott, Gábor László Hajba, Laura Hart, Carlos
Lopez, Wil Moore III, Mario Tatis, and Stephen Wolff.
As always, thanks to family members who got used to the office door being
closed and the headphones blocking everything out while I banged away in
TextMate and Xcode (and sometimes banged on my desk because of the latter)
for too many evenings and weekends.
Obligatory end-of-book music check: This time it was Metric, Joe Jackson,
Dire Straits, Minami Kuribayashi, Yuki Kajiura, and Scandal (the one from
Japan, not the one from the ’80s). Current musical stats at http://www.last.fm/
user/invalidname.
But that’s like saying the web isn’t a big deal. Of course it is. And for mobile
users, the app often replaces the web. Apps are faster, more secure, more
immediate, and more personal than any web page can be. When you use an
app, you have a sense that it’s yours, because everything it’s doing is happen-
ing right there in your hands.
This immediacy is a big part of what makes writing iOS apps so compelling.
It’s also what makes it so different from other kinds of development. If you’ve
done web development before, on the server or in the browser, some of it will
seem familiar, and yet much will be quite different. Writing apps places differ-
ent responsibilities on the developer, and gives you different opportunities.
In this book, we’re going to jump into development for iOS, the leading mobile
platform, atop which literally millions of apps have been written, installed
and run on hundreds of millions of devices. Soon, yours can be one of them.
Notice that instead of a two-to-three-year gap like the previous titles, the book
you’re holding is coming out just one year and one iOS version after the pre-
vious one. The motivation for this is that the platform has developed so
quickly that the previous books were near-total rewrites of their predecessors,
which took years to finish, yet quickly became dated with the release of a new
version of the OS and its software development tools.
Also weighing on our minds is the fact that because of all of the changes in
Swift 2.0, most of the code from the iOS 8 book doesn’t even compile anymore.
So we’re trying something different this year: a refresh, not a reboot. Instead
of rewriting the whole book every three years, we’re going to try to rewrite
roughly a third of it every year. If this plan works, it means a perpetually up-
to-date title for beginning iOS developers, whenever they happen to decide
they’re ready to give the platform a try.
For this year’s edition, specifically, we targeted our handling of the Swift
programming language. After a year in the wild (with both authors using it
extensively or exclusively in their day jobs), the language is ready to be taken
seriously and described in detail. So whereas our iOS 8 book treated Swift
as a means to an end (namely, calling the various iOS frameworks), this year
we put Swift front-and-center. The first three chapters are an entirely new
introduction to the Swift language itself, working in coding “playgrounds” to
get us ready to build real apps. The next two chapters after that were inspired
by the previous book, but are radically rewritten and reorganized to ease the
transition into full-on app development.
None of this should be inferred as saying the rest of the book is a copy-and-
paste job. Every example in the book has been rewritten from scratch to take
advantage of the Swift 2.0 programming language and its features. Many
chapters also have entirely new sections to cover new iOS 9 features, such
as automated UI testing (see User Interface Testing, on page 111) and stack
views (see Stack Views and the User Detail View Controller, on page 192). Also,
we have completely rewritten the final chapter, Chapter 17, Publishing and
Maintaining the App, on page 289, to include Apple’s TestFlight beta-testing
service and the many changes to its developer Member Center and iTunes
Connect sites.
important non-code topics, like testing and debugging, which are crucial and
sometimes overlooked parts of the development process.
With that in mind, here’s where our journey will take us:
All code in this book uses the Swift programming language. Swift is a perfor-
mant, practical language that Apple clearly intends to be the future of all
development for its platforms. When it was open-sourced in late 2015, the
“About Swift” page declared:
The goal of the Swift project is to create the best available language for uses
ranging from systems programming, to mobile and desktop apps, scaling up to
cloud services. Most importantly, Swift is designed to make writing and maintaining
correct programs easier for the developer.
On the same page, it says that Swift “is intended as a replacement for C-based
languages (C, C++, and Objective-C).” That’s not an unreasonable goal! Swift
is a neat language that cleans out a lot of cruft from C and Objective-C, while
also drawing inspiration from functional programming languages like Haskell.
Online Resources
This book isn’t just about static words on a page or screen. It comes with a
web page, https://www.pragprog.com/titles/adios3, where you can learn more and
access useful resources:
• Download the complete source code for all the code examples in the book
as ready-to-build Xcode projects.
If you’re reading the ebook, you can also access the source file for any code
listing by clicking on the gray-green rectangle before the listing.
As we build our sample projects in this book, we will often write simple code,
only to rewrite it with more ambitious code later as our knowledge increases.
All the different versions would be hard to put in one source file. So in the
downloadable book code, we often have multiple copies of each project, each
representing a different stage of its development. The different stages use
numbered folders, like PragmaticTweets-1-1, PragmaticTweets-2-1, and so on, with
the first number representing the chapter number and the second being a
revision within that chapter. These folder names also appear in the captions
for each code example in the text. You can either code along for the entire
book from scratch, or copy over one of these “stages” and pick up from there.
And Here We Go
With our expectations set and our goal in mind, let’s head into our first task:
getting our tools set up and learning just enough Swift to make our devices
do neat stuff.
Coding in Swift
You’ll start by simply playing around, and by the time you’re done, you’ll be
ready to ship an app.
There is one must-have tool for iOS development: Xcode. This integrated
development environment (IDE) will be where we do nearly all our work of
developing, testing, and unleashing iOS apps. Xcode lets us build our apps,
run them, debug them, and submit them to App Store. There’s very little in
this book that won’t involve working in Xcode.
We get Xcode from the Mac App Store, which is always available from the
Apple menu in any application. Search for Xcode in the store, and click the
Get button. Don’t worry, it’s free, but you will need to have an Apple ID to
get apps from the store. Actually, you’ll need an Apple ID for a bunch of other
tasks later, so create an Apple ID now if you don’t have one, either in the Mac
App Store app or at https://appleid.apple.com.
• Interface Builder, for creating user interfaces visually, rather than in code
• A build system, to convert our source code and user interface files into
runnable apps
• Debugging tools, which help us find and fix errors in our code
• Testing tools, to verify the correctness of our code and ensure that fixed
bugs do not return
That’s a lot of stuff to fit in one app! It might be overwhelming, but our first
run of Xcode will offer a pretty gentle introduction. Launch Xcode from the
Applications folder (you may want to put it in your Dock, too), and click OK
if it asks to install additional components, which are the command-line exe-
cutables that will build our projects for us. When we get to the first window,
Xcode keeps things simple:
We start with three simple options: “Get started with a playground,” “Create
a new Xcode project,” and “Check out an existing project.” On the right, there’s
a blank space that says No Recent Projects as we start working in Xcode; this
will fill in with a list of our Xcode creations.
This brings up a window like the following, with a toolbar and status pane at
top, a source editor on the left, an empty pane on the right, and a time slider
at the bottom that says “30 sec.” For a moment, the status indicator will say
Running MyPlayground…, and then the text "Hello, playground" will appear in
the right pane, directly across from the source code line var str = "Hello, playground".
("Hello, playground") and assigns it to the variable str. The result of this assignment
is also the return value, which is why it shows up in the results pane.
Well, two can play at that game, right? On a new line at the bottom, let’s write
something really simple:
var two = 1 + 1
Great, now we can do some math. Let’s add another line to use that result:
two = two * two
That’s all well and good, but it’s not much better than we could achieve with
a calculator, or even by punching mathematical expressions into the Spotlight
search bar. Let’s think of something a little more ambitious.
Don’t panic; this isn’t as scary as you think. We can get this running with
shockingly little code. But let’s do so in a new playground. Close the current
playground window, and use File > New > Playground to create a new play-
ground. Call it WebRadioPlayground. Keep the line that says import UIKit, but delete
the var str = "Hello, playground" line, and replace it with the following:
playing/WebRadioPlayground.playground/Contents.swift
Line 1 import AVFoundation
2 let url = NSURL(string:
3 "http://www.publicbroadcasting.net/wgvu/ppr/wgvufm.m3u")
4 let player = AVPlayer(URL: url!)
5 player.play()
Line 1 tells the Swift compiler that we want to use AV Foundation, a program-
ming framework that lets us bring audio and video features to our apps. On
lines 2–3, we create a URL for the station we want to play. Technically, this
is an NSURL object, which we create by passing in a string.
Line 4 creates an AVPlayer, which is an object that can play various kinds of
audio and video media. We create it with the url on the previous line, and the
! character is our assertion that the url is valid and not nil. This is actually a
dangerous practice—we’re not really in a position to know whether the URL
is valid—and is something we will want to fix up a little later. Finally, on line
5, we tell the player to start playing.
Be Your Own DJ
You don’t have to use our default URL; we just thought using a
National Public Radio (from Chris’ town of Grand Rapids) station
was a good bet for a URL that wouldn’t get us sued and wouldn’t
disappear anytime soon. But we could be wrong; the URL we used
in an earlier revision of this chapter disappeared in May, 2016,
after more than a decade of non-stop streaming.
The results pane will show the URL string next to the line that creates the
NSURL, and the lines involving the player will show AVPlayer and a big (64-bit)
hexadecimal number (the player object’s address in memory, which does us
no good here). But nothing’s actually playing, right?
We need to do one special thing for this example. Swift is a language with
built-in memory management that frees unused objects for us. Once we say
player.play(), all the code in the playground has been executed, and Xcode
assumes it’s OK to clean everything up. Unfortunately, this results in the
immediate destruction of the player object that we want to play our audio!
When we have a case where we want the contents of the playground to hang
around after the initial execution finishes, we need to send a special command
to Xcode to do so. Add the following to the end of the playground code:
playing/WebRadioPlayground.playground/Contents.swift
Line 1 import XCPlayground
2 XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
The import statement on line 1 tells the playground to load the functions and
methods that let us interact with the playground execution itself, and the
XCPlaygroundPage.currentPage.needsIndefiniteExecution call on line 2 gets the current
page of the playground and tells it to keep executing indefinitely, instead of
exiting (which would destroy the player variable).
Notice that as soon as you enter this last line and stop typing for a few sec-
onds, you’ll start hearing the web radio station playing (provided you have
an Internet connection). Cool! Web radio with five lines of code!
Typing again causes the music to stop, until you let up on the keyboard.
Basically, when the playground thinks you’re done, it tries to build and run
the code, and in this case, that means music starts playing again.
So that’s our first little bit of code that does something cool. Now let’s look at
how we got it to work at all.
My First Computer (Chris)
Messing around in the Xcode Playground reminds me of how computers used to dump you into
an interactive programming environment by default. My first computer was a Texas Instruments
TI-99 4/A, bought by my father because the saleswoman at K-Mart was really persuasive, and
maybe because it cost $400 when the Apple IIs we had at school were over $1,000 (Plus ça change,
plus c’est la même chose).
When you turned it on, the menu gave you two choices by default: TI BASIC, or whatever cartridge
(if any) was in the slot. With no cartridge, all it could do was BASIC. Back in those days, messing
around with some flavor of BASIC was what every home computer did. For a while, the idea was
that anyone could program—and would actually want to—and pretty much any student in our
school at least knew enough to do 10 PRINT "SARA IS GREAT" 20 GOTO 10.
In the TV documentary Triumph of the Nerds, Steve Jobs once recalled that for every one person
who wanted to hack on hardware in that era, there were another thousand who wanted to hack
on software. And beyond Jobs’s observation, it seems that for every one person who wanted to
hack on software, another thousand just wanted to run the stuff, without necessarily knowing
how it works. Inevitably, computers got away from writing programs as being the primary user
experience, and coding eventually became a specialist skill and no longer accessible to the
layperson, despite the occasional programming renaissance like Hypercard.
Having a playground is like going back to those summer nights of the 1980s, with mosquitos
banging off the window screen (attracted by the glow of the TV that served as a monitor), a Styx
cassette playing on the tape player that was used to load and save programs, and a blinking
cursor inviting me to write some code…just to see what happens.
The documentation for the iOS Software Development Kit (SDK) is available
within Xcode itself, in a handy documentation viewer. Use the menu item
Window > Documentation and API Reference (BD0) to show it.
When opened for the first time, the documentation viewer shows a “What’s
New in Xcode” document, with an index of topics in a pane on the left. At the
top, there’s a toolbar, with forward and back buttons, two other buttons, and
a search field.
This document offers an overview of all the audio and video APIs available in
the iOS SDK. It’s got a lot to cover, because it turns out there are a bunch of
them, with some overlap that’s not always easy to figure out. But follow the
link to “Using Audio” and start scrolling down the page. It begins with a list
of possible use-cases and which APIs you’d use for them. The second item
reads:
Use the AV Foundation framework to play and record audio using a simple
Objective-C interface. For details, see AV Foundation Framework Reference and
the avTouch sample code project.
“Simple” sounds good, at least for a first chapter, right? We’ll ignore the dated
reference to “Objective-C” (this particular guide hasn’t been updated in years,
unfortunately) and click on the blue text (which means it’s a link) to go to the
AV Foundation Framework Reference.
The content pane on the right contains a description of every method, prop-
erty, constant, function, and so on in the AVPlayer class. On the left, the table
of contents offers quick access to these contents by name, organized by
functional topic (Creating a Player, Managing Playback, and so on).
If we look under Creating a Player, we find initWithURL, and the Swift syntax
for creating an AVPlayer from a URL. It just says init(URL URL: NSURL), with the
NSURL formatted as a link. So now we know that to create an AVPlayer to play
our audio, we just need to create an NSURL object. And if we scroll down, we’ll
find the play() method that started our stream.
Searching Documentation
Now if we were working backward like this, we’d say to ourselves: “OK, to get
an AVPlayer, we need to have an NSURL, so how do we do that?” Obviously, it
would be easy to just click the NSURL link here, but let’s learn how to do a
search, too. Click in the search bar at the top, and type URL. The results of
the search will immediately appear in a sheet beneath the search bar, as
shown in the screenshot on page 13.
There are a bunch of different kinds of hits. The NSURL class we want is at the
top, denoted by a “C” icon to indicate it’s a class. There are other results, like
properties called url in various classes, and some guides on how to work with
URLs in different APIs. But let’s select that first result and go to the NSURL
page. This documentation page is organized by topic, just like the last one,
and under Creating an NSURL Object, we can find the initializer that takes
a string.
So, by browsing, searching, and working backward, we can find the two
classes we needed to create our streaming audio playground. We’ll have more
to say about the language itself in the next few chapters, but whenever we
need functionality that we believe the SDK provides, we now know we can
just bring up the documentation viewer and look for it.
Open up Xcode’s preferences with Xcode > Preferences, and select the
Downloads tab. This shows optional pieces that can be downloaded into our
local copy of Xcode. These include different versions of the iOS Simulator
application (which we’ll be using a little later) and documentation.
We want the iOS 9 SDK documentation, so click the arrow next to that. This
is where things are going to get a little sticky.
If this is your first time using Xcode, you’ll see an alert sheet slide out saying
that you have to authenticate with Apple in order to download documentation.
There’s a free level of participation, but it means some extra steps, so let’s
see what’s involved.
Click the Open Accounts button in the alert to switch to the Accounts tab of
Xcode preferences. The left pane of this tab organizes three kinds of accounts
you can be logged into with Xcode: Apple IDs for development, source code
repositories, and Servers for certain advanced tasks. The list is initially blank;
we add to it with the plus (+) button at the bottom left.
What we need to add is an Apple ID, which you should already have, since
it’s what you used to download Xcode from the Mac App Store. Click the plus
button to add an Apple ID, and then enter the email and password for your
Apple ID account. Assuming authentication succeeds, your Apple ID will be
added to the list.
Notice that the detail pane fills in with the Apple ID email and password, and
a table shows the account as now having a free-level membership to the iOS
and Mac developer programs. The reason this is a table is that it’s possible
to join other development teams—for example, if you join a company or take
on freelance work for an organization with its own development account—and
those memberships and their privileges will be listed here.
For the time being, our concern is downloading documentation. Now that you
have a basic developer membership, switch back to the Downloads tab, and
click the arrow on the iOS 9 documentation line. Now the documentation set
should download to your Mac, and you’ll find the documentation viewer loads
faster and works offline.
Since we’ll be writing our apps with the Swift programming language, we
created a playground to try out some simple Swift code in an interactive
sandbox, which lets us type in a little code and see what happens. The play-
ground gives us access to most of the things we could do with the iOS SDK,
from crunching numbers to playing web radio. And with so much to play with
in the SDK, we looked at how the documentation viewer lets us browse or
search the documentation for classes, methods, and functions that we can
call. And, finally, we downloaded a local copy of the iOS 9 documentation, so
we’ll be able to keep coding even without Wi-Fi.
Our next step will be to get a real grip on the Swift programming language
so we can start building more sophisticated behavior.
In this chapter, we’re going to start learning the Swift programming language,
which will let us create our applications by giving us a way to call into the
iOS SDK, and a way to compose our app’s internal logic. We’ll spend the entire
chapter in playground mode, which will allow us to try things out, see what
does and doesn’t work, and quickly learn from our mistakes.
• Strongly Typed, meaning that objects we work with are clearly identified
as strings, integers, floats, and so on. These types are static, meaning
they can’t change once they’re assigned. Swift also provides type inference,
so we don’t have to explicitly indicate a type when the compiler can
unambiguously figure it out.
In this book, we are covering Swift 2.0, which is supported by Xcode 7. Earlier
versions of the language were supported by Xcode 6. Swift and Xcode version-
ing moves in lock-step, meaning you can’t write Swift 1.2 syntax in Xcode 7,
nor can you write Swift 2.0 in any version of Xcode 6. Fortunately, the com-
piled code is forward- and backward-compatible, so Swift 1.2 code written in
Xcode 6 will run on iOS 9, and Swift 2.0 code written in Xcode 7 will run on
iOS 8 (provided that the app as a whole is marked as being backward-com-
patible with iOS 8). For simplicity, we are focusing only on current versions
in this book: Swift 2.0, Xcode 7, and iOS 9.
Computers were originally built for math, so let’s start with some simple
numbers. Type the following:
startingswift/NumbersPlayground.playground/Contents.swift
let foo = 1
The right pane shows a 1. This is nice and simple: we’ve created foo and
assigned it the value 1, which is what’s shown in the results pane.
So far, so good. Now let’s do some math. Like many languages, Swift has a
+= operator to add and assign, so let’s try that.
startingswift/NumbersPlayground.playground/Contents.swift
foo += 1
There’s also a red circle in the left gutter next to our new line of code. Red
means “error” in Xcode, so that’s bad. On the other hand, the circle icon
means “instant fix,” so that’s good! Click the icon to see the problem and its
solution:
Clicking the error icon shows us a two-line pop-up menu. The first restates
the error, and any subsequent lines give us instant-fix options. The error is
that let creates a constant, a value that can’t change. Once we set foo to 1, we
can never change it again. If we do want foo to change, it needs to be a variable.
That’s what the second line is offering: click it and the declaration changes
from let to var, and now we can perform all the math on foo that we like.
Constants versus variables might seem like a semantic difference: some lan-
guages like C make variables the default and constants uncommon, while
JavaScript doesn’t have constants at all. In Swift, using constants is preferred
when you know that a value should not or cannot change. This allows the
compiler to make certain performance optimizations for constants. We always
have to choose between marking things as constants or variables, and this
is on purpose; as we’ll see again and again, many of Swift’s design choices
are built on the idea of the developer being explicit about his or her intentions.
That looks good, and since it’s a variable, we can change its value:
bar = 0.5
Oh, no! We get another error, and this time without an instant fix. Worse yet,
the description is unhelpful: “Cannot assign a value of type 'Double' to a value
of type 'Int'.”
So what’s the deal? Can Swift seriously not convert between floating-point
numbers and integers? Actually, Swift can, but our variable bar cannot. The
problem is that we never specified what the type of bar is, so the Swift compiler
made its best guess. Replace the second bar = 0.5 line with the following:
bar.dynamicType
The dynamicType property allows us to see what type a value has. In the right
pane, we see that the type evaluates to Int.type. That makes sense: since the
original value was 0, Swift took a guess and assumed we would want an
integer, not a floating-point type. This inferring of types is called, appropriately,
type inference.
We could tell Swift to use a floating-point type by using 0.0 as the initial value;
try that and you’ll see the dynamicType becomes Double.type. But if we want a
given type, we can and should just declare it that way. Change the declaration
like this:
var bar : Double = 0
Now Swift doesn’t need to infer anything: we’ve explicitly declared that we
want bar to be a Double, meaning a double-precision floating-point type.
warn you about loss of precision. Swift forbids this altogether, even if you
wouldn’t be losing anything. Try this:
startingswift/NumbersPlayground.playground/Contents.swift
let myInt = 1
let myDouble : Double = myInt
startingswift/NumbersPlayground.playground/Contents.swift
let myInt = 1
let myDouble = Double(myInt)
The second line here uses Double’s initializer to create a new Double, using the
value passed in as a parameter.
Swift’s numeric types work with the usual arithmetic operators popular in
other languages: +, -, *, /, and %. The last one, the modulo or “remainder”
operator, works not just on Int but also Float and Double, which isn’t always
true of other languages. On the other hand, thanks to strong typing, Bools
aren’t just numeric values, and thus none of these mathematic operators
work with Boolean values. And that’s a good thing because what would “false
divided by true” mean, anyway? Instead, we use the usual Boolean operators
! (NOT), && (logical AND), and || (logical OR).
Of course, right off the bat, the playground template creates a string for us
with var str = "Hello, playground", which evaluates as Hello, playground in the results
pane.
Since str is defined with the var keyword, it’s a variable, so let’s change it up
a bit. Type the following:
startingswift/StringsPlayground.playground/Contents.swift
str = str + "!!"
This evaluates as Hello, playground!!, and proves that we can combine strings
with the + operator.
Swift strings are fully Unicode compliant, meaning they can contain any
Unicode character, including all the various written languages and symbols
supported by iOS. Let’s add some of those characters now. Xcode offers quick
access to Unicode characters with the menu item Edit > Emoji & Symbols (
CDspace). The popover, shown in the following figure, allows quick selection
of groups of emojis and other characters like “technical symbols” and “pic-
tographs.” Scroll to the top to find a search field to look up characters by
name, and a button to switch to the full-size character input window.
Let’s see if we can add an emoji character to our string. Write the following
code, and when you need to insert the emoji inside the quotes, bring up the
symbols window, find an emoji character, and double-click it to insert it into
the source code:
As you’ll see in the results pane, this appends the emoji to the end of the
string. And it turns out Swift’s support for Unicode isn’t just for strings;
Unicode is fully supported throughout Swift source code as well. That means
we can do something really silly, like this:
This creates a variable whose name is actually the running person emoji, and
the assignment operator (=) sets its value to the current value of str, so we see
the same value as before in the results pane.
Aside from the + operator, we can also build up strings through a substitution
technique. Whenever the sequence \() is found in a string in source code, the
contents of the parentheses are evaluated and substituted into the string.
The contents could be variables, mathematical expressions, or other strings,
as in the following screenshot:
This evaluates to the single character “é.” Two characters go in, and one comes
out.
When we do want to pull out the contents of a string, we can use its characters
property to let us treat the string contents as an array, one of the collection
types we’ll be talking about in the next section. This lets us count the number
of user-readable characters, and for kicks, we’ll substitute that number into
a larger string:
startingswift/StringsPlayground.playground/Contents.swift
"Sentence has \(sentence.characters.count) characters"
The contents of the characters array are of a type called Character, which repre-
sents a single human-readable character. Using our Unicode string from
before, we can find the location in the string of the “book” emoji by represent-
ing it as a Character, and then using the array’s indexOf() to find it in the characters
array.
Once we write this, we see the result "book is at index Optional(10) in sentence". The
10 is right, but the Optional() is weird, right? We’re going to discover what’s up
with that a little later.
Arrays
For many of us, the most frequently used collection is the array. Arrays con-
tain multiple items and maintain the ordering of those items. They also allow
for the same item to appear multiple times in the array.
We’ll start with an array of strings that lists our favorite iPhone models.
Declare an array as follows, and immediately check its dynamicType:
startingswift/CollectionsPlayground.playground/Contents.swift
var models = ["iPhone 5", "iPhone 5s", "iPhone 6s"]
models.dynamicType
Our array now has four strings. Keep in mind, however, that we can add only
Strings because that’s the type of the array. If we wanted to be able to add
other types, like any of the numeric types, we would have had to originally
declare that models accepts any type of object, which we would write as var
models : Array<AnyObject>. There’s also a simpler syntax for array type declara-
tions: [AnyObject].
As our array grows, it likely won’t fit on one line of the results pane. Fortu-
nately, there’s a way to see the whole thing. Mouse over the last line in the
results pane, and two icons will appear on the far right: Quick Look and
Preview.
Click the rightmost button, Preview, to see the results shown directly below
the line of code that produced them.
Now let’s dig into our array. To access a member of an array by its index, we
can use the square-brace syntax familiar from C, Java, and many other lan-
guages:
startingswift/CollectionsPlayground.playground/Contents.swift
let firstItem = models[0]
The evaluation pane shows that firstItem has been set to the value "iPhone 6s
Plus", which we inserted a few lines back.
Since we can insert, we can, of course, remove items as well, either with
removeAtIndex() or removeLast(). To see the shortened array in the results pane,
we type models by itself on the next line.
startingswift/CollectionsPlayground.playground/Contents.swift
models.removeLast()
models
Keep in mind that all of these mutating operations are possible only because
models was originally declared with the var keyword. If we’d used let, Swift would
infer we wanted the array to be constant and would have created an immutable
array.
Swift arrays have a few other neat tricks that make it easy to combine and
split arrays, provided their types are compatible. Try this:
startingswift/CollectionsPlayground.playground/Contents.swift
Line 1 let iPhones = ["iPhone 5", "iPhone 5s", "iPhone 6s", "iPhone 6s Plus"]
2 let iPads = ["iPad Air 2", "iPad mini"]
3 models = iPhones
4 models.appendContentsOf(iPads)
5 models.insertContentsOf(["iPod touch"], at: 4)
In this section of code, lines 1 and 2 create immutable arrays called iPhones
and iPads, respectively. Line 3 assigns our models array to now be the contents
of iPhones. Then, on line 4, we use the appendContentsOf() function to append the
contents of the iPads array to the models array. If we don’t want to add items
Sets
Arrays are a bread-and-butter collection, but for certain tasks, sets make
more sense. A set has no sense of order and does not allow duplicate items.
Sets are useful for when you want to simply know whether or not a given item
is part of a collection, and you don’t care if it’s “before” or “after” other mem-
bers of the collection.
Let’s kick off an empty set and start adding stuff to it.
startingswift/CollectionsPlayground.playground/Contents.swift
Line 1 var set = Set<String>()
2 set.insert("iPhone 6s")
3 set.insert("iPhone 6s")
Line 1 shows the recipe for creating an empty collection of any type. We have
to declare the type (because there are no objects to infer it from), so we use
the type Set<String>, and then empty parentheses to call the initializer for that
type.
On line 2 we add the string "iPhone 6s", and on line 3, we do it again. Look at
the results pane: after the second insert(), the set still only has one member.
That’s because a given item can appear only once in a Set; we don’t care how
many of something are in the set, just whether that thing is in or out.
Where sets really shine is their operators for determining membership between
members of multiple sets. Let’s create two sets to play with:
startingswift/CollectionsPlayground.playground/Contents.swift
var iPhoneSet : Set = ["iPhone 6s"]
var iPadSet : Set = ["iPad Air 2", "iPad mini", "iPad Pro"]
Notice that as a convenience we can create a Set from an Array. In this example,
["iPhone 6s"] is one array, and ["iPad Air 2", "iPad mini", "iPad Pro"] is another. We have
to declare the Set type (because otherwise the square-brace syntax would
imply an Array), but the type of the contents (Strings) can be inferred.
If we were interested in what members are in both sets, that’s a one-line call:
startingswift/CollectionsPlayground.playground/Contents.swift
iPhoneSet.intersect(iPadSet)
The results pane shows us that no items are members of both sets. Let’s
change that. The iPhone 6s Plus feels nearly as big as an iPad mini, so let’s
add it to both sets and try another call to intersect().
startingswift/CollectionsPlayground.playground/Contents.swift
iPadSet.insert("iPhone 6s Plus")
iPhoneSet.insert("iPhone 6s Plus")
iPhoneSet.intersect(iPadSet)
Now the results pane shows us the one item in both sets, "iPhone 6s Plus". Notice
that the returned collection is also a Set.
If you studied set theory in high school, you’ll recall that an intersection is
the items that are in both sets and that the union is all the members of both
sets. Swift gives that to us, too:
startingswift/CollectionsPlayground.playground/Contents.swift
iPhoneSet.union(iPadSet)
This gives us everything from both sets. Notice that "iPhone 6s Plus" appears only
once, despite being present in both sets. After all, the return type is a Set, and
any object can appear only once in a set.
Dictionaries
Swift’s third major collection type is dictionaries. A dictionary is a collection
that maps keys to values, which gives you a way to quickly look up a given
value in the future, provided you have the corresponding key.
Let’s create a dictionary that lets us look up iOS device sizes by model name:
startingswift/CollectionsPlayground.playground/Contents.swift
let sizeInMm = [
"iPhone 6s": 138.1,
"iPhone 6s Plus" : 158.1,
"iPad Air 2" : 240.0,
"iPad Pro" : 305.7]
sizeInMm.dynamicType
As we can see in the results pane, this creates a dictionary with four key-
value pairs, and the type of this collection is inferred to be Dictionary<String,
Double>, meaning the keys are Strings and the values are Doubles.
The results pane shows that we got back 138.1 for the "iPhone 6s" key and nil for
the "iPad mini" key, which makes sense because sizeInMm doesn’t have that key.
Dictionaries are good for fast lookups of single items, although it’s also possible
to walk through the whole collection. To do that, though, we’re going to need
ways to have our code loop through the collection. So let’s move on to that.
Swift’s tools for control flow are probably very familiar to most developers,
since many languages have if, for, while, and so forth. We’ll try them out now,
so start a new playground called ControlFlowPlayground.
for Loops
Control flow also goes hand-in-hand with collections, which is why we’re
reaching it now: once you have a collection of items, it’s natural to want to
go through the collection and run some code on each item. We’ll start with
going through an array with for.
startingswift/ControlFlowPlayground.playground/Contents.swift
Line 1 let models = ["iPhone 6s", "iPhone 6s Plus", "iPad Air 2",
2 "iPad mini", "iPad Pro"]
3 for model in models {
4 NSLog ("model: \(model)")
5 }
On lines 1–2, we create the models array, consisting of five strings. Line 3 is
the for-in loop syntax, which says we want to go through every member of the
models collection, and each time through, the item we’re working with will be
represented with the local variable model.
The results pane doesn’t show us anything about what happened each time
through the loop. To see that we’re actually doing something each time, we
use the NSLog() function on line 4 to write a message to the debug log. The
output from NSLog() isn’t shown by default; bring it up with View > Debug Area
> Activate Console (BDC), or the middle button on the pane-switcher on the
toolbar:
Once the console pane is revealed, you should see the output of this loop:
model: iPhone 6s
model: iPhone 6s Plus
model: iPad Air 2
model: iPad mini
model: iPad Pro
If it’s useful to have the index of members of the collection, we can do a loop
that counts the members of the collection numerically, like this:
startingswift/ControlFlowPlayground.playground/Contents.swift
for i in 0 ..< models.count {
NSLog ("model by index: \(models[i])")
}
This style of for loop creates a variable for the index (i in this case), and counts
through a range of values. We create this with the range operator: ..<, which
counts from the starting value (0) to one less than the ending value (models.count,
the length of the array). If we wanted to include the ending value, we would
use the range operator ... instead.
What if it would be convenient to have both the index and a local variable
inside the loop? Sure, we could do let model = models[i] as the first line inside
the loop, and then use that. However, Swift gives a much more elegant alter-
native, albeit one we’ll have to wait to discover in the next chapter.
if-else Statements
We often want to execute some statements only if certain conditions are true,
and while the if statement is unfashionable in some coding circles, it’s familiar
to nearly every programmer. Swift’s are simple enough, with one or two unique
wrinkles. Let’s try an if statement that pulls a value out of a dictionary:
startingswift/ControlFlowPlayground.playground/Contents.swift
Line 1 let sizeInMm = [
- "iPhone 6s": 138.1,
- "iPhone 6s Plus" : 158.1,
- "iPad Air 2" : 240.0,
5 "iPad Pro" : 305.7]
-
- let model = "iPhone 6s"
- if sizeInMm[model] != nil {
- NSLog ("size of \(model) is \(sizeInMm[model])")
10 } else {
- NSLog ("couldn't find \(model)")
- }
After creating the sizeInMm dictionary, we define the model key we are interested
in, and then try to get its matching value from the dictionary. If the value is
not nil, we execute the NSLog() on line 9, and otherwise the NSLog() on 11. Change
the value of model to different values to see each block of the if-else log its
message to the console.
The one truly interesting thing to say here is that the curly braces in Swift
if-else statements are required, even if only a single line is to be executed in
either case. This is different from the single-line behavior of C and Java, and
eliminates easy-to-miss bugs caused by the inconsistent syntax of making
the curly braces optional.
goto fail;
When Apple first announced that if statements in Swift would always require curly braces, a lot
of us cheered and snarked that Apple had learned its lesson. Because just a few months earlier,
missing curly braces hurt them badly.
In early 2014, Apple quietly updated its SSL implementation—used for any secure networking
on iOS or OS X—and security researchers found that the earlier versions had a critical bug.
One part of the code needed to carry out a series of checks before calling an all-important
sslRawVerify() method. It basically looked something like this:
Without curly braces, a true if statement in C will execute one statement. So the if statement on
line 3 executes line 4 if the SecondFunction() test fails, which means we’re in an error state and
call goto fail;, which means we never call sslRawVerify(). And that’s fine; that’s what’s supposed to
happen.
The problem is that, despite the indentation, line 5 is always called, regardless of what happened
in the if statement. It looks like it’s part of the if, but it’s not, and the result is that sslRawVerify() is
never called, because line 5 always makes us goto fail;.
It’s a simple mistake, and lots of people missed it, but the loose syntax of C led to a critical
security hole. It’s no wonder that when they designed Swift, Apple required curly braces on all
if statements, to make sure this kind of bug was no longer possible!
Swift also offers a guard statement that is sort of like the opposite of if: it doesn’t
have a curly-brace clause for the true case, just an else for when the condition
is not true. We typically use these for early exits when we don’t want to run
many lines of code if the condition isn’t met, and we don’t want to have to
nest important code deeply in if-else indentation. Typically, guard statements
perform early returns to bail out of code we don’t want to run, and we can’t do
that kind of early return in a playground, so we’ll have to wait until we’re
writing a real app to get our guard on.
switch Statements
The last kind of control flow technique we need to be aware of is switch. The
switch keyword lets us test a variable against several possible values, and
execute different code in each case. Let’s write a simple example:
startingswift/ControlFlowPlayground.playground/Contents.swift
switch model {
case "iPhone 6s Plus":
NSLog ("That's what I want")
case "iPhone 7":
NSLog ("Have they even released that?")
default:
NSLog ("Not my thing")
}
This switch will log "That's what I want" if model is "iPhone 6s Plus", or "Have they even
released that?" if it’s "iPhone 7", or "Not my thing" in all other cases.
If you’re familiar with C’s switch, you’ll be pleasantly surprised by one feature
here: Swift’s switch works on Strings (or any type that can be evaluated with ==,
actually), and not just on numeric types. Another improvement from other
languages is that a matched case doesn’t fall through to the ones after it; in
C, you would have to put a break at the end of the first case, or the code would
execute the second case and the default as well.
The switch statement gets heavily used in Swift because it’s the perfect way to
deal with enumerations, which you’ll learn about in the next chapter.
We’ll start by adding the sizeInMm dictionary from a few sections back, since
that’s something that started giving us this “optional” stuff.
startingswift/OptionalsPlayground.playground/Contents.swift
let sizeInMm = [
Looking at this, we can see that sizeInMm["iPhone 6s"] should evaluate to 138.1,
which is a Double, meaning a double-precision floating-point number.
Well, that’s great, but what if we evaluate sizeInMm["iPhone 7"], a key not in the
dictionary. If our return value is a Double, what’s the right value for its size?
0? -1? Some huge positive or negative value that we just interpret as a “no-
value”?
Swift has a better answer for this: optionals. An optional is a type that repre-
sents two different things: whether there’s a value at all and, if so, what the
value actually is.
It turns out that dictionaries always return optionals for their values, as we
can see by inspecting the dynamicType of the value we get back:
startingswift/OptionalsPlayground.playground/Contents.swift
let size6 = sizeInMm["iPhone 6s"]
size6.dynamicType
In the results pane, this shows size6 as 138.1 and the dynamicType as Optional<Dou-
ble>.Type.
Now let’s try the same thing with a nonexistent value, like the size of the fic-
tional iPhone 7:
startingswift/OptionalsPlayground.playground/Contents.swift
let size7 = sizeInMm["iPhone 7"]
size7.dynamicType
This shows us a size of nil and the dynamicType of Optional<Double>.Type. It’s the
same type as before, a Double optional, only this time there isn’t a value.
Unwrapping Optionals
As you might imagine, we’re frequently going to be concerned with whether
an optional value is nil, and when it’s not, we often want to get to the value
itself. We do this through a process called unwrapping. To “unwrap” a Double
optional like the values in our dictionary means to take an Optional<Double>
and turn it into just a normal Double.
One way to unwrap is to use the force-unwrap operator, which is the ! charac-
ter. Try it out on size6:
startingswift/OptionalsPlayground.playground/Contents.swift
size6!.dynamicType
This force-unwraps size6 to be a non-optional type, and then gets its dynamicType.
The results pane shows the dynamicType as Double. Huzzah! We got our Double
out from inside the optional!
Ack! The results pane says “Error,” and there’s a red band with a bunch of
scary text about EXC_BAD_INSTRUCTION.
This is pretty bad: our code has crashed inside the playground. And the reason
for that is something we need to remember: unwrapping nil values crashes
our code! size7 is nil, we said to unwrap it with the ! operator—bang, we’re
dead. Let’s delete that line so it doesn’t give us any more trouble!
Now we need to figure out what we’re going to do to not crash anymore. One
option would be to always test optionals against nil, and only unwrap if they’re
non-nil. That works, but it gets ugly. Nest a few if foo != nil blocks, and soon
you’ve got what Swift developers call the “pyramid of doom” from all that
indentation.
Once we finish typing this, notice that the first if let block shows Double.Type
for the type in the results pane, meaning that size is a normal Double inside
the block and not an optional. But the second block of code doesn’t show
anything, because its if let fails (because size7 is nil, so size is not assigned).
The if let keyword gets used a lot, so it has a few tricks that will help us write
more concise code. The first is that we can combine several if lets on a single
line, comma-separated:
startingswift/OptionalsPlayground.playground/Contents.swift
if let size6 = size6, size7 = size7 {
size6.dynamicType
size7.dynamicType
}
There are two things to notice here. First, each assignment in an if let creates
a variable name that’s only visible inside the scope of the curly braces. Often,
it makes sense to just use the same name that a variable has outside the if
let. So, in this case, if let size6 = size6 is not a meaningless tautology; instead, it
looks at the right side (the optional size6) and says “if that’s not nil, create an
unwrapped variable also called size6 inside the curly-brace scope.” At first it
may look weird, but it’s a convention that comes easily to Swift programmers
and is better than having to come up with different variable names for use
only inside the if let block.
Second, there’s nothing in the results pane, because not all of the if let
assignments succeeded. Since size7 is nil, we can’t unwrap it, and the if let fails.
One other trick we use a lot is testing a value that we’ve just unwrapped, as
part of the if let. For example, what if we want to run some code on an
optional Double only if it’s non-nil and its value is greater than some constant.
We could use an if let followed by a if size6 > 100.0, but nesting ifs is going to give
us that “pyramid of doom” we spoke of before. Instead, we can do this:
startingswift/OptionalsPlayground.playground/Contents.swift
if let size6 = size6 where size6 > 100.0 {
size6.dynamicType
}
The where clause on an if let allows us to perform logic with the unwrapped
size6 Double while still on the if let line. This makes it clear that everything on
the if let line has to pass for us to get into the curly-brace block.
It may seem like a lot of work to deal with optionals, but the concept ends up
being powerful: we can use a single variable to both hold a value and to say
“nothing to see here” if there isn’t a value. In some languages, we’d either
have to use two variables for that, or a magical flag value that we just agree
to treat as a “no value” value. And programming history has shown that
approach can cause a lot of unexpected problems.
This chapter has been all about working with the essential data types in Swift.
We started with the numeric types—integers, floating-point numbers, and
Booleans—and strings. We saw how to combine strings with the basic con-
catenation operator (+) and pattern substitution, and how to access their
contents. Also, we went a little nuts with the Unicode support in Swift strings,
but it’ll pay off if we ever want to support multiple languages, or lots of emoji.
Finally, since dictionaries may or may not give us a value for a given key, we
started working with Swift optionals, to see how they represent the presence
or absence of a value, and how to get to the value.
These are the building blocks we’ll use to build full-blown iOS apps. In the
next chapter, you’ll see how to combine them into more sophisticated data
structures, how to create functions to work with them, and how to do so with
style and aplomb.
Swift is a remarkably flexible language, one that takes its inspiration from a
number of different sources. It’s true to both the object-oriented nature of
Objective-C and to new ideas about design, elegance, and maintainability in
functional programming languages. You can write Swift like Objective-C, like
C, like Java, or even like Haskell, and it will still work.
Since there’s no one right way to write Swift, we will be making choices about
how we want to organize our code. In this chapter, we’re going to look at what
Swift offers us for building bigger data structures, and how our choices will
affect the evolution of our apps as we write and rewrite them. If the one
hammer in your toolbox when you started this book was the good ol’ class,
let’s discover what we can do by taking lightweight types like structures and
enumerations and extending them with custom functionality.
Creating Classes
Many programmers—professionals and students, hobbyists and cowboy
coders—have grown up in the mind-set of object-oriented programming. As
Janie once said on the NSBrief podcast, “I didn’t think I was learning object-
oriented programming. I thought I was learning programming…like that was
the only way to do it.”
And it’s not like anyone’s wrong to learn OO! It’s the dominant paradigm for
a good reason: it has proven over the decades to be a good way to write
applications. Whole languages are built around the concepts of OO: it’s nigh-
impossible to break out of the OO paradigm in Java, and Objective-C has OO
in its very name, after all!
So let’s see how Swift supports object-oriented programming. The heart and
soul of OO is to create classes, collections of common behavior from which
we will create individual instances called objects. We’ll begin by creating a
new playground called ClassesPlayground, and deleting the "Hello, playground" line
as usual.
In the last chapter’s collections examples, we used arrays, sets, and dictionar-
ies to represent various models of iOS devices. But it’s not easy or elegant to
collect much more than a name that way, and there are lots of things we want
in an iOS device model. So we will create a class to represent iOS devices.
We’ll start by tracking a device’s model name and its physical dimensions:
width and height. Type the following into the playground:
stylishswift/ClassesPlayground.playground/Contents.swift
class IOSDevice {
var name : String
var screenHeight : Double
var screenWidth : Double
}
In Swift, we declare a class with the class keyword, followed by the class name.
If we were subclassing some other class, we would have a colon and the name
of the superclass, like class MyClass : MySuperclass, but we don’t need that for this
simple class.
There’s just one problem: this code produces an error. We need to start
thinking about how our properties work.
Properties
The error flag tells us “Class IOSDevice has no initializers,” and the red-circle
instant-fix icon offers three problems and solutions. The problem for each is
that there is no initial value for these properties. Before accepting the instant
fix, let’s consider what the problem is.
The properties we have defined are not optionals, so, by definition, they must
have values. The tricky implication of that is that they must always have
values. The value can change, but it can’t be absent: that’s what optionals
are for.
That’s one solution, as long as we’re OK with the default values. But here
they don’t quite make sense because we probably never want an iOS device
with an empty string for a name.
Again, no more error, so that’s good. Problem now is that any code that wants
to access these properties has to do the if let dance from the last chapter to
safely unwrap the optionals. And again, do we ever want the device name to
be nil? That seems kind of useless.
The initializer runs from lines 6 to 10. The first line is the important one, as
it starts with init and then takes a name and type for each of the parameters
to be provided to the initializer code. In the initializer itself, we just use the
self keyword to assign the properties to these arguments.
Computed Properties
The three properties we’ve added to our class are stored properties, meaning
that Swift creates the in-memory storage for the String and the two Doubles. We
access these properties on an instance with dot syntax, like iPhone6.name.
Swift also has another kind of property, the computed property, which is a
property that doesn’t need storage because it can be produced by other means.
Back at the bottom of the file, after creating the iPhone6 constant, fetch the
computed property by calling it with the same dot syntax as with a stored
property:
iPhone6.screenArea
The results pane shows the computed area, 9,252.7 (or possibly 9252.699…).
It’s also possible for stored properties to run arbitrary code; instead of com-
puting values, we can give stored properties willSet and didSet blocks to run
immediately before or after setting the property’s value. We’ll use this approach
later on in the book.
Methods
Speaking of running arbitrary code, one other thing we expect classes to do
is to let us, you know, do stuff. In object-oriented languages, classes have
methods that instruct the class to perform some function. Of course, Swift
makes this straightforward.
Let’s take our web radio player from the first chapter and add that to our
IOSDevice. After all, real iOS devices are used for playing music all the time,
right? We’ll start by adding the import statement to bring in the audio-video
APIs, and the special code we used to let the playground keep playing. Add
the following at the top of the file, below the existing import UIKit line:
stylishswift/ClassesPlayground.playground/Contents.swift
import AVFoundation
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
We need our IOSDevice to have an AVPlayer we can start and stop, so add that
as a property after the existing name, screenHeight, and screenWidth:
stylishswift/ClassesPlayground.playground/Contents.swift
private var audioPlayer : AVPlayer?
Notice that this property is an optional type, AVPlayer?, since it will be nil until
it is needed.
Now, let’s add a method to the class. We do this with the func keyword, followed
by the method name, a list of arguments, and a return type. Add this playAudio()
method somewhere inside the class’s curly braces, ideally after the init’s closing
brace, since we usually write our initializers first and our methods next.
stylishswift/ClassesPlayground.playground/Contents.swift
func playAudioWithURL(url: NSURL) -> Void {
audioPlayer = AVPlayer(URL: url)
audioPlayer!.play()
}
Like the init, the parentheses contain the parameters to the method and their
types. By convention, we often imply the first parameter type in the name of
the method. This is because when we call the method, we do not label the
first parameter, but we do use labels for any other parameters. For example,
if playAudioWithURL() also took a rate argument, we would call it like playAudioWith-
URL(someURL, rate: 1.0). Compared to some languages, the labeled parameters
may seem chatty or verbose, but, in practice, they make the code more
readable by exposing what each value is there for.
After the parameters, the return type is indicated by the -> arrow. In this
case, the method returns nothing, so we return Void. (In fact, when we return
Void we can omit the arrow and the return.) The rest of the method is the two
lines of code we used in the first chapter to create the AVPlayer and start playing.
Now let’s call it and start playing music. Put the following at the bottom of
the file, after where we create the iPhone6 instance.
stylishswift/ClassesPlayground.playground/Contents.swift
if let url = NSURL(string: "http://armitunes.com:8010/listen.pls") {
iPhone6.playAudioWithURL(url)
}
The first line attempts to create an NSURL out of the provided string. We use
an if let because, if our string is garbage, what we get back from the initializer
could be nil. This is because the NSURL provides a failable initializer, one that
reserves the right to return nil instead of a new object. It’s denoted this way
in the documentation with the keyword init?, where the ? clues us in to the
fact that optionals are in play.
Wrapping this in an if let means that we will only enter the curly-braced region
if the initialization succeeds and assigns the value to the local variable url.
This is the proper practice for failable initializers and gets around the bad
practice we used in the first chapter when we just force-unwrapped the NSURL?
optional with the ! operator.
And once we’re safely inside the if let, we call the playAudioWithURL() method that
we just wrote, and the music starts playing. If we wanted to write a proper
stopAudio() method, that would look like this:
stylishswift/ClassesPlayground.playground/Contents.swift
func stopAudio() -> Void {
if let audioPlayer = audioPlayer {
audioPlayer.pause()
}
audioPlayer = nil
}
Again, we use an if let to safely unwrap the audioPlayer optional, and only if that
succeeds do we pause() it. Then we can set audioPlayer back to nil.
If you want to turn it off, just comment out the call to playAudioWith-
URL(). Swift uses the same comment syntax as all C-derived lan-
guages (Objective-C, C#, Java, etc.). That means you can either
put // on the start of a line to turn it into a comment, or surround
a whole range of lines with a starting /* and a closing */.
Protocols
Swift classes are single-inheritance, in that a given class can have only a single
superclass. We can’t declare that IOSDevice is a subclass of two different
classes and inherit the behaviors of both. (In practice, that kind of thing gets
messy!) Actually, IOSDevice isn’t currently declared as the subclass of anything,
so it’s just a generic top-level class.
Let’s try it out to do something useful. At the bottom of the file where we
create the iPhone6, and then again on the line that plays the music, the evalu-
ation pane on the right just says IOSDevice. That’s because those lines evaluate
to just the iPhone6 object, but the playground doesn’t know what it can tell us
about the object other than its class. We can do better than that.
Swift defines a protocol called CustomStringConvertible that lets any type declare
how it is to be represented as a String. Playgrounds use this for the evaluation
pane, as does NSLog() when using the \() substitution syntax, like in NSLog ("I
just bought this: \(iPhone6)"). To implement CustomStringConvertible, we just need to
provide a property called description, whose type is a String.
This method just uses string substitution to show the device name and its
dimensions. As soon as we finish writing this, the evaluation pane starts
using this description instead of the bare class name:
There are many other protocols we’ll be implementing throughout the book.
Some, like CustomStringConvertible, come from the Swift language itself, but most
are from UIKit and the other iOS frameworks we’ll be working with.
Returning Tuples
So far, we’ve taken a thorough tour of what Swift offers for object-oriented
development. In a lot of languages, that would be enough. But in Swift, it’s
only half the story.
Swift is great for object-oriented programming, but it also allows for more of
a functional programming style. In functional programming, there’s an
emphasis on passing data around, instead of maintaining state in classes
and mutating it all the time.
One significant trait of functional programming is that it’s better to pass values
to and from functions, rather than references. If we have an object of some
class, and two parts of our code can modify its data at the same time, it can
lead to confusing bugs. In functions, we generally want to pass the data itself,
not a containing object. In other words, we prefer pass-by-value to pass-by-
reference.
One thing that makes this difficult is the fact that functions can take many
parameters, but they can typically only return one thing. Sometimes, it’s
natural to want to return multiple values from a function, and in some lan-
guages the options to do so are either to define a new type solely to hold those
multiple values, or to use some kind of collection.
In Swift, we have tuples, which are just simple lists of values. One way to
think of it is that just as a function or method can take a list of values wrapped
in a pair of parentheses, a tuple lets us return a list of values wrapped in a
pair of parentheses.
Let’s give our existing IOSDevice class a computed property that returns a tuple
of the screenHeight and screenWidth. Up with the other computed properties, type
the following code:
stylishswift/ClassesPlayground.playground/Contents.swift
var screenHeightAndWidth : (height: Double, width: Double) {
get {
return (screenHeight, screenWidth)
}
}
This is a lot like our other computed properties, but the type of the variable
is in parentheses, which makes it a tuple. Inside the tuple definition, we
identify each member by a name (which is not required) and a type. So, this
tuple has two members, named height and width. Then we just use parentheses
in our return line to package these values into a tuple.
To use the tuple, just access it like any other variable. Outside the class, after
creating the iPhone6 variable, pull out the values like this:
stylishswift/ClassesPlayground.playground/Contents.swift
iPhone6.screenHeightAndWidth
iPhone6.screenHeightAndWidth.height
iPhone6.screenHeightAndWidth.0
For the first line, the evaluation pane shows all the values of the tuple, as (.0
138.1, .1 67). We can then access a value inside the tuple either by the name,
like height, or its index in the tuple, like .0. Both of these evaluate to 138.1.
One place that tuples really shine is in counting over collections. In the last
chapter, we said that iterating over a collection meant going either by index
or by object. Tuples let us have our cake and eat it too. That’s because Swift
defines an enumerate() function that returns members of a collection as tuples
of each member and its index. This lets us do a for-in loop where we have
access to both the member and the index inside the loop.
To try it out, we’ll need a few new IOSDevice instances and a collection. Add
the following at the bottom of the playground:
stylishswift/ClassesPlayground.playground/Contents.swift
let iPhone6Plus = IOSDevice(name: "iPhone 6 Plus",
screenHeight: 158.1, screenWidth: 77.8)
let iPhone5s = IOSDevice (name: "iPhone 5s",
screenHeight: 123.8, screenWidth: 58.6)
let iPhones = [iPhone5s, iPhone6, iPhone6Plus]
This creates an array of three IOSDevice objects. If you like, check them out in
the results pane with the QuickLook or Show Result button on the line that
creates the iPhones array. Now we’ll use enumerate() to count over them with a
tuple:
stylishswift/ClassesPlayground.playground/Contents.swift
for (index, phone) in iPhones.enumerate() {
NSLog ("\(index): \(phone)")
}
Inside the for loop, we now have access to the index and the phone object each
time, so we can easily log them out with NSLog(). In the console (View > Debug
Area > Show Debug Area, or BDY), we can see the output that shows each:
ClassesPlayground[2947:1037546] 0: iPhone 5s, 123.8 x 58.6
ClassesPlayground[2947:1037546] 1: iPhone 6, 138.1 x 67.0
ClassesPlayground[2947:1037546] 2: iPhone 6 Plus, 158.1 x 77.8
One reason I am so vocal in my defense of Swift is because this realization has completely changed
my reality. I used to think there was only one way of doing things. Well, I won’t say that. There
was one right way of doing things and then there was the “Dear god, what is this person thinking
by having this property controlled in four different places?!” way of doing things.
Being exposed to Swift and seeing that you don’t have to put everything in a class has been a
revelatory experience for me. It is forcing me to reevaluate everything I know about programming.
I never thought about what an object was before; there was no point because everything was
an object. Now I am trying to get a better understanding of what an object actually is. I wrote a
blog about the difference between structs and classes, and at the time I really didn’t understand
why you would want to use a struct instead of a class if they essentially do the same things. I
now understand that you want to try to use structs when possible because they aren’t objects.
Objects come with a lot of overhead. They let you do some more powerful things like subclassing
through polymorphism, but you don’t always need to do those things. Looking at how powerful
the enums are that Brad Larson (my boss and mentor) uses in his code, I am fascinated by how
confined my own view was when I thought everything had to be an object and exist in a class.
So, yes, I once did ask what an object is. I know most programmers worth their salt can tell you
the definition of what an object is, but I don’t think many of them stop to think about why we
use them and if they are the best way of doing things. Or if they bother to wonder if objects are
the only way of doing things.
To make a clean start, close this playground and create a new playground
called StructsPlayground.
Let’s think about the IOSDevice that we created as a class: it had some simple
properties for the device name and dimensions, and some methods that
operated on those properties. If it mostly serves as a container for data, if we
don’t care about inheritance, and if the data is small and not difficult to copy
around in memory, then it’s the kind of thing that functional programmers
would tell us doesn’t need to be a class.
This is a lot like the beginning of our old class: it’s just the property names
and their types. One thing has changed, though: we can define these properties
as non-optional types, and we don’t get an error message about how “IOSDe-
vice has no initializers.” That’s because the struct gets an initializer for free:
just pass in all the values, labeled by their property names in the structure.
That means we can create an iPhone6 like this:
stylishswift/StructsPlayground.playground/Contents.swift
let iPhone6 = IOSDevice(name: "iPhone 6", screenHeight: 138.1,
screenWidth: 67.0)
This shows an IOSDevice in the results pane, which means we’ve successfully
created an instance of the IOSDevice structure. That was easy!
“But,” the critic says, “you can’t really do anything with it, can you?” Well,
sure.
If this were C, our next step would probably be to write some global functions
that work with this struct, either taking it as a parameter or returning it as a
result. And the difference would be that the functions would receive copies
of all the members of the structure, not just a reference to an object in
memory (that some other part of the code might also be using, unbeknownst
to us).
But still, Swift can do a lot better than just making us write a bunch of
global functions.
Extensions
Swift gives us the ability to attach code to arbitrary types: structures, classes,
enumerations, and even numeric types. The bits of code are called extensions,
and they’re delightfully powerful. Let’s use them to beef up our IOSDevice.
To extend a type, we just write extension and the type we are extending, and
then in curly braces we put code for methods or computed properties. This
goes outside the struct’s curly braces. So we can give the IOSDevice structure
the screenArea computed property that the class had like this:
stylishswift/StructsPlayground.playground/Contents.swift
extension IOSDevice {
var screenArea : Double {
get {
return screenWidth * screenHeight
}
}
}
Now just call that with iPhone6.screenArea on a new line, and we’ll see 9,252.7 (or
perhaps 9252.699…) in the results pane.
The fact that we write the code as an extension outside the type’s definition
implies something very powerful: we can provide extensions for anything.
We’re not limited to extending the abilities of our own classes and structures;
we can extend classes in UIKit, basic types in Swift, basically any named
type. As a rather absurd example, we can add methods to Swift’s Int type:
stylishswift/StructsPlayground.playground/Contents.swift
extension Int {
func addOne() -> Int {
return self + 1
}
}
Joe asks:
Why Is the Keyword func When It’s Not Really a
Function?
The keyword func is so named because Swift does indeed have honest-to-goodness
functions: executable segments of code that take parameters and can return a value,
but that aren’t attached to an instance of anything. We’ve been using these already:
the NSLog() function is a global function that we’ve used to log messages to the Xcode
console pane.
Defining a function is just like creating a method, just outside the scope of a class.
Putting it inside the class makes it a method. Really, Swift methods are like a special
case of functions: being inside a class, they pick up the stuff inside the class and are
able to access its properties and other methods.
In fact, both functions and methods are a special case of the even more general-
purpose concept of “closures,” but we’ll hold off talking about them until Chapter 8,
Managing Time with Closures, on page 141.
Notice that for a read-only computed property, we can omit the get {…} and
just provide the code to compute the property in curly braces right after the
variable declaration.
Once you write this extension, the IOSDevice gets a nicer representation in the
results pane, just like before.
Start a new playground called EnumsPlayground, and delete the "Hello playground"
line. We’re going to use this playground to rethink our IOSDevice.
The different values for the enumeration are marked off as separate cases,
kind of like a switch statement. What’s really interesting here is the Retina case.
The two Doubles in parentheses are called associated values, and only exist
when a given ScreenType is Retina. The None case has no associated values, and
some other case might have completely different associated values; maybe a
hypothetical case CrystalBall would have a radius: Double for its associated value.
Now let’s create a new struct that can use this enum to represent its display, or
lack thereof:
stylishswift/EnumsPlayground.playground/Contents.swift
struct IOSDevice {
var name : String
var screenType : ScreenType
}
That’s easy enough; the enum acts like a new type, just like a String or Int. Now
let’s create some instances of this:
stylishswift/EnumsPlayground.playground/Contents.swift
Line 1 let iPhone6 = IOSDevice(name: "iPhone 6",
2 screenType: ScreenType.Retina(screenHeight: 138.1, screenWidth: 67.0))
3 let appleTV3rdGen = IOSDevice(name: "Apple TV (3rd Gen)",
4 screenType: ScreenType.None)
Notice that just like with the struct, we automatically pick up the syntax for
populating the associated values of the ScreenType.Retina case; we just label and
provide a value for each one, comma-separated, in parentheses (see line 2).
Concise Swift
Swift likes concision, and many things that are redundant can be
omitted. For example, the screenType variable in these initializers
can only be of type ScreenType, so it’s legal to omit the type and just
write the value with the leading dot character. So we could create
the appleTV3rdGen like this:
stylishswift/EnumsPlayground.playground/Contents.swift
let appleTV3rdGen = IOSDevice(name: "Apple TV (3rd Gen)",
screenType: .None)
As opportunities for omitting syntax occur throughout the book,
we’ll generally spell it out the long way first, mention what can be
left out, and use the concise version from then on.
tation to provide a pretty string for them. We can provide one with an extension,
and in the process we’ll see how to use the associated values we provided for
the screenHeight and screenWidth.
We begin on line 1 with an extension that says we are going to make IOSDevice
conform to CustomStringConvertible. This means defining a description computed
variable of type String.
The interesting part is on line 7, which starts the .Retina case. We use the let
keyword in parentheses to receive the associated values as local variables. If
we didn’t care about one or more of these values, we could use the underscore
character _ instead of let and the local variable name to say “I don’t need this
value.” But in this case, we want both the screenHeight and screenWidth as local
variables, so we can build a screenDescription that shows the dimensions.
Once this is written, the playground will immediately rebuild and rerun our
playground, and the IOSDevice, now that it conforms to CustomStringConvertible,
will pretty-print nice descriptions for our two devices in the evaluation pane:
Swift’s So Functional!
So, between structs, enums, protocols, and especially extensions, we can replicate
most of the power of object-oriented programming, without needing to use
classes and the usual practices of maintaining and mutating state. Swift isn’t
the most pure functional programming language by a long shot, but functional
programming (FP) fans have found much to like in it.
Keep in mind that most of the iOS frameworks are very object-oriented in
nature—they were written for use with Objective-C after all—so much of the
code we write will be of an OO style by necessity. Having said that, when we
see an option to do things with a lighter touch, we’ll try to do so.
Some developers use this as a means of performing logic on the optional, particularly
if we want to do something in the nil case. Rather than doing an if let and then testing
the value against some other logic, we can put the logic in a switch like this:
stylishswift/EnumsPlayground.playground/Contents.swift
let optionalString : String? = "iPhone6"
switch optionalString {
case .None:
NSLog ("nil!")
case .Some(let value):
NSLog ("some! \(value)")
}
when we unwrap optionals that turn out to be nil. But there’s more to robust
coding practices than that.
In Swift 2.0, methods (including initializers) can indicate that they signal
errors by including the throws keyword. To call code that may throw, we need
to do two things:
1. Wrap all related code in a do-catch block, where the catch will pull out and
handle any thrown object.
2. Explicitly put the keyword try immediately before each method or initializer
that can throw. Of course the compiler could figure it out for us; this is
meant as a means of annotating the code by explicitly calling attention
to parts of the code that can produce errors.
Let’s try an example. The iOS frameworks have a number of APIs that throw
errors in Swift. In many cases, these were implemented in Objective-C with
an “in-out” system where a caller would provide a pointer to an NSError object.
The caller would send in nil for this pointer, and check its value after the
method returned. If it was now a pointer to an NSError, it meant that an error
had occurred. All such “in-out” APIs are converted automatically to the throws
idiom in Swift, so don’t let the documentation scare you when you see all
those Objective-C asterisks (**NSError always freaked us out).
Line 3 tries to create the NSData from the provided myURL. This initializer throws,
so the initializer itself needs to have the try keyword right before it; when a
method or function throws, the try will be at the beginning of the line.
So, when this works, we’ll see the NSData and NSString represented over in the
result area: the data will be clusters of hexadecimal digits, and the string will
be the raw HTML.
Now let’s get ourselves into the error handling. We’ll do that by mangling the
URL string. A simple way to do this is to change the URL scheme from http to
some nonsense like foo. Do this, and the evaluation pane will go blank. Instead,
down in the debug console, we’ll see an error message:
ErrorHandlingPlayground[6258:1941577] NSError: Error Domain=
NSCocoaErrorDomain Code=256 "The file couldn’t be opened."
UserInfo={NSURL=foo://pragprog.com}
This is coming from the block on line 5 that catches thrown NSErrors. The later
catch on line 7 catches anything, although nothing in our code is declared
as throwing something other than NSError, so it will never be reached and is
shown only for demonstration.
Oh, and let’s please be sure to do more to recover than just logging an error
to the Xcode console. In real life, we would want to tell the user what hap-
pened, or maybe automatically retry, or something.
We’ve been able to do all this in the Xcode playground, which gives us a fine
place to try out our Swift code. Now it’s time to pick up our toys and move
into developing actual iOS apps. In the next chapter, you’re going to learn
how to build actual iOS user interfaces and start building our real app.
We’ll kick things off with a little secret about iOS development, something it
inherited from Mac development: Create the user interface first. This is totally
backward for a lot of seasoned developers. A lot of us think through an
application’s requirements and immediately start thinking of our data models
and strategies and…nuh-uh. Build the UI first. Build what users are going to
see, what they’re going to interact with, and start to understand how they’ll
experience it. Then figure out how the heck you’re going to do that.
In this chapter, we’re going to start building an app that will carry us through
to submitting to the App Store, which we do in the final chapter. And since
iOS apps start with the user interface, that’s all we’re going to do in this
chapter: we’ll familiarize ourselves with the tools for building UIs, and in the
next chapter we’ll start connecting it to code.
concerns about getting data from the Internet and presenting it in the iOS
user interface. Not to mention, Twitter clients are among the most-used and
most-loved iOS apps, and have been a hotbed of UI innovation: the familiar
“pull to refresh” gesture first appeared in “Tweetie,” which later became the
official Twitter app for iOS.
As we develop the app, you can use your existing Twitter account or, if you
don’t have one or would prefer to use a separate account for development
work, create a new Twitter account at http://twitter.com. While working on the
book, we’ve used a test account, @pragsiostest, which we’ll use for screenshots.
To begin work on a new app, we need to create a project using the menu
sequence File > New > Project (BDN). There’s also a button on the Xcode
greeting window for starting a project, so that’s another way to do it.
When we create a new project, a window opens, and out slides a sheet that
asks us what kind of project we want to create. This project template sheet,
shown here, has a list on the left side of project categories divided into iOS
and Mac OS X, watchOS, and “Other” sections. Since we’re building an iOS
application, we’ll select Application from the iOS section and then look at the
choices in the main part of the frame. We can click each to see a general
description of what kind of app to start on. For our first example, we’ll select
Single View Application.
After we click Next, the sheet then asks us for details specific to the project,
as shown in the following figure. Some of these change based on the project
type; in general, this is where we need to provide names and other identifiers
to the app, indicate which device formats (iPhone and/or iPad) it’s for, and
so on. For our first app, here’s how we should fill out the form:
• Product Name—A name for the product with no spaces or other punctua-
tion. Our product will be called PragmaticTweets here.
• Language—There are two choices for this pop-up menu: Swift and
Objective-C. We’ve covered Swift for the last three chapters, so let’s choose
that here.
three options. With the variety of iOS devices currently available, Apple
is pushing hard for developers to build universal apps that run and look
good on a variety of screen sizes, all four sizes of iPhone and two of iPad,
so select Universal here.
• Check boxes—Do not select Use Core Data, which is a data persistence
framework that is beyond the scope of this book. When we’re done, feel
free to move on to Marcus Zarra’s excellent book Core Data (2nd edition):
Data Storage and Management for iOS, OS X, and iCloud.1 Go ahead and
check both Include Unit Tests and Include UI Tests. These will make it
easier to expose our app to automated testing, which we’ll do in Chapter
6, Testing the App, on page 97.
After clicking Next, we choose a location on the filesystem for our project.
There’s also an option for creating a local Git source code repository for our
files. Source control is beyond the scope of this book, but in short: if you want
a local history of all your changes, select it. If you don’t need it, or (better yet!)
if you plan to check your code into an external source control system like
GitHub later on, leave it unselected. Once we specify where the project will
be saved, Xcode copies over some starter files for our project and reveals them
in its main window.
The window is split into five areas, although some of them can be hidden with
menu commands and/or toolbar buttons. These areas are shown in an
“exploded” view in the figure on page 63.
Toolbar
The toolbar at the top of the window offers the most basic controls for
building projects and working with the rest of the workspace. The leftmost
buttons, Run and Stop, start and stop build-and-run cycles. Next are two
borderless buttons collectively known as the scheme selector, which
chooses which “target” to run (currently PragmaticTweets) and in what
environment (a simulated “iPhone 6,” or the name of an actual iOS device
1. https://pragprog.com/book/mzcd2/core-data
Inspector Pane
Editor
Navigator
Library Pane
Variables View Console
Debug Utility
Navigator Area
The left pane (which may be hidden if the leftmost View button in the
toolbar is unselected) offers high-level browsing of our project’s contents.
It has a mini-toolbar to switch between eight different navigators. The
File Navigator (D1) shows the project’s source and resource files and is
therefore the most important and commonly used of the seven. Other
navigators let us perform searches (D3), inspect build warnings and
errors, inspect runtime threads and breakpoints, and more.
Editor Area
The main part of the window is the Editor area. This view cannot be hid-
den. Its contents are set by selecting a file in the Navigator area, and the
form the editor takes depends on the file being edited—when a source file
is selected, we see a typical source code editor, when a GUI file is selected,
the Editor area becomes a GUI editor, and when an image file like a GIF
or JPEG is selected, the Editor area displays the image.
The Editor Mode buttons in the toolbar switch the editor pane between
three modes: standard, which is the default editor for the type of file that’s
selected; assistant, which shows related files side by side; and version,
which uses source control to show current and historical versions of the
file side by side, a “blame” mode that shows the committer of each line
of code, or a log of commit comments alongside the code. The Editor area
also contains a jump bar, a breadcrumb-style strip at the top that shows
the hierarchy of the thing being edited; for a source file, this might read
“project, group, file, method.” Each member of the jump bar is a pop-up
menu that navigates to related or recent points of interest.
Utility Area
The right side of the window is a utility area that provides detailed viewing
and editing of specific selections in the Editor area. Depending on the file
being edited, the toolbar atop this area can show different tools in its
Inspector pane. Basic information about a selected file and quick help on
the current selection are always available. For GUI files, there are
inspectors to work with individual UI objects’ class identities (ED3), their
settable attributes (ED4), their size and layout (ED5), and their connec-
tions to source code (ED6). We’ll be using all of these shortly. At the
bottom of the Utility area, a library pane gives us click-and-drag access
to common code snippets, UI objects, and more.
Debug Area
The bottom of the window, below the Editor area and between the Naviga-
tor and Utility areas, is a view for debugging information when an app is
running. Its tiny toolbar has a segmented button that lets us switch
between the debugging-oriented variables view that allows us to inspect
memory when stopped on a breakpoint, a textual console view of logging
output from the application, or a split view of both. We’ll make use of the
right-side console view in a little bit, whereas the left-side variables view
will be our focus in a later chapter.
So that’s how Xcode presents our initial project to us, but what can we do?
Well, there’s a nice big Run button, and it’s not like it’s disabled. Let’s try
running the app. Make sure the scheme is some flavor of iPhone from the
iOS Simulator section (and not iPad or the name of an actual device); in Xcode
7, our choices range from the iPhone 4s to the iPhone 6 Plus. Click the Run
button. The status area will shade in with a progress bar that fills up as it
builds all the files and bundles them into an app, and when it’s done, it will
launch the iOS Simulator. The Simulator is another OS X application, which
looks and behaves more or less like a real iPhone or iPad. When our app runs
in the Simulator, the main screen disappears and is replaced by a big white
box that fills the Simulator screen.
test the purely UI parts of our app. Finally, the Products group shows the
files our build will create: in this case, PragmaticTweets.app for the app, plus
PragmaticTweetsTests.xctest and PragmaticTweetsUITests.xctest for the runnable unit
tests. Files shown in red indicate they haven’t been built yet; PragmaticTweets.app
is red in the figure because, although we’ve run it in the Simulator, we haven’t
built it for the actual device yet.
We said at the outset that iOS development starts with the user interface. By
focusing on what the user sees and how they interact with it, we keep our
focus on the user experience and not on the data models and logic behind
the scenes. We typically build our user interfaces visually and store them in
storyboards. The project has one such file, Main.storyboard, so let’s click it.
Storyboards
When we click on Main.storyboard, the Editor area switches to a graphical view
called Interface Builder, or IB for short. In iOS, IB works with user interface
documents called storyboards. Just like in movie-making, where a storyboard
is a process used to plan out a sequence of shots in a movie or TV show, the
storyboard of an iOS app shows the progression through the different views
the app will present. The initial storyboard looks like the following figure.
Our app uses a single view, so we follow the right-pointing arrow (which
indicates where the app starts) into a square that represents the visible area
of the screen. This is our app’s one view; if we were building a navigation-
style app, there would be one view rectangle for each screen of the navigation.
Click the view to show a header box with three icons. These are proxy objects
that represent objects that will work with the view at runtime: a view controller
that contains logic to respond to events and update the view; a first responder
that represents the ability to handle events; and an exit segue, used for when
we back out of views in navigation apps (something we’ll visit in a later
chapter).
Adding Buttons
So let’s start adding some UI elements to our view. We’ll begin by adding a
button to send a tweet telling the world that our first app is running. To add
components to our storyboard, use the toolbar to show the Utility area on
the right (if it’s not already showing), and find the Library pane at the bottom
right. There’s a mini-toolbar here that should default to showing user interface
objects; if not, click the little icon of a square in a circle (or press CED3).
The bottom of the pane has a button to toggle between list and icon views for
the objects, and a search filter to find objects by name. Scroll down through
this pane to find the icon that just says Button; we can tap once on any of
the objects to get its name, class, and description to appear in a popover.
Drag the button from the Object library into the iPhone-sized view in IB. This
will create a plain button.
It leaves a lot to the imagination, huh? Without the edge and back-
ground decorations of earlier versions of iOS, it doesn’t necessarily
look like a button at all. It could easily be mistaken for a text label.
The recent look of iOS, introduced back in iOS 7, has three stated themes:
deference, clarity, and depth. The first of these, deference, means that the UI
appearance focuses attention on our content rather than competing with a
bunch of pseudo-realistic effects.
So maybe our problem is a lack of content. iOS expects us to tell the user
what’s going on in our app, and we’re not holding up our end of the deal yet.
Let’s fix that. First, we’ll say what the button does. Double-click on the button
to change its name to Send Tweet. Now it says what it does, but it still doesn’t
exactly feel button-y.
Maybe we can fix that by contrasting the blue text of the button with a plain
label. Back in the Object library at the lower right, find the Label object, and
drag one above the button. Change its text to “I finished the first project.”
Drag both objects so that they’re centered in the view; a dashed blue line will
appear when we’re centered, and the drag will snap to this position. The view
should now look like the figure.
Go ahead and click the Run button to run this app again in the iOS Simulator.
We should just see the label and the button, right? Sure, but there’s a problem.
When we run the app in the Simulator, we typically start in portrait orienta-
tion. And right now, that’s going to be a problem, because our label and
button are not centered in portrait; in fact, they’re cut off on the right edge,
as seen in the following figure. Rotate to landscape with Rotate Left and Rotate
Right items in the Hardware menu (D← and D→, respectively), and it looks
a little better, but it’s still clearly not centered on tall models like the iPhone
5. What happened?
The problem is that we’ve been designing against a hypothetical square shape,
and we never explicitly said these labels were supposed to be centered. What’s
happened instead is that they’ve kept a constant distance from the top and
left sides of their parent view. In a way, it makes sense: iOS doesn’t know
what matters to us: a constant distance from the top or bottom, or being
centered, or some other relationship entirely.
Stop the Simulator, go back to Xcode, and select the label. On the right side
of the workspace, show the Size Inspector, by clicking the little ruler icon (or
pressing ED5). This inspector tells us about the size and location of elements
in our UI. There’s a section called Constraints, which currently reads:
The selected views have no constraints. At build time explicit left, top, width, and
height constraints will be generated for the view.
Autolayout
In iOS, our UI elements are placed onscreen with an autolayout system that
lets you determine where objects should go and how big they should be based
on constraints that we set on them. This allows our interfaces to adapt to
being rotated between portrait and landscape, and to handle the differing
screen sizes of the 3.5-inch models (original iPhone through iPhone 4s), 4-
inch models (iPhone 5, 5c, and 5s), the 4.7-inch iPhone 6, and 5.5-inch iPhone
6 Plus. Constraints allow us to express what matters to us—the size of com-
ponents, their alignment with or distance from other components, and so
forth—and to let other factors vary as needed. In this example, we want our
label and button to be horizontally centered, and we don’t care what the
resulting x and y coordinate values are.
Stack button: This button embeds one or more selected views into a “stack
view,” a container for other views (and something we’ll explore in a later
chapter).
Align popover: This lets us create constraints that align a view’s edges
or horizontal or vertical center with another view, or horizontally or ver-
tically center it within its containing view (its superview).
Pin popover: This lets us create constraints that specify a fixed value for
spacing from one or more edges to another view (possibly the superview),
and/or a fixed width or height.
Resolve menu: The options here will adjust a view position or size so it
matches its constraints, or do the opposite and create constraints based
on its current position and size. We can also clear all constraints and
start over with this menu.
So what we need to do is to just tell our label and button to be centered. Click
the label, and then click the Alignment button. This shows the popover in
the figure.
Click the check box next to Horizontal Center in Container. This will change
the button at the bottom of the popover to say Add 1 Constraint. Click this
Storyboard Zooming
If you need to zoom in or out of a storyboard…well, we hope you have a laptop. With
a trackpad, we can pinch zoom in and out to show more of the storyboard. Without
a trackpad, there are zoom menu items available via a Control-click in the Editor
area, or the menu item Editor > Canvas > Zoom.
button to dismiss the popover (note that if we tap outside the popover instead
of tapping the button, the popover will dismiss without creating the constraint).
This causes an orange line to appear down the middle of the view, and an
orange box around it, when the label is selected. In Interface Builder, orange
is a warning color, meaning there aren’t enough constraints. The label is under-
constrained because, although we’ve provided a horizontal constraint, we
haven’t provided a vertical constraint, meaning autolayout can’t know for
sure how high or low on the screen to place the label.
Since we’re happy with what the label looks like in Interface Builder, let’s just
tell it to keep this same distance from the top of the container view. We do
that by pinning its distance from the top. With the label selected, click the
Pin button to show the popover seen here.
The pinning popover lets us lock down values such as width and height or
distances from containers or other views, or force sides of multiple components
to stay aligned. The top section is called “Spacing to nearest neighbor,” and
if we click the top pop-up menu in this part, we can select the value Top
Layout Guide, meaning we want to lock the distance between the top of our
label and the area at the top of the screen reserved for the status bar (where
the battery level, signal strength, clock, and other indicators appear), or any
other menus at the top of the screen (like the navigation bar we’ll introduce
much later). When we select this menu item, the “brace” graphic under the
menu becomes solid, and the button in the popover again says Add 1 Con-
straint. Click this button to add the new constraint.
One other thing to watch for is orange markers indicating that an object is
not in its correct position. Typically, these warnings will also show a size or
distance number in a little bubble. When you see this, you can correct the
problem by selecting the object, going to the resolve pop-up menu at the
lower right, and choosing Update Frame. The only problem is that if the object
is out of position and underconstrained, the update could send it all the way
to one edge of the view, or radically change its size. If this happens, just undo
(DZ), and think about what other constraints you might need.
Size-Specific Constraints
When creating constraints, it’s important that the sizing bar at
the bottom of the pane is in “w:Any h:Any” mode. Although this
creates somewhat unrealistic square views, the danger is that
constraints created with any other sizes set for width or height
will only apply for those sizes. If we set width to Compact to preview
the appearance of an iPhone in portrait and add a constraint like
horizontal centering, that will only apply for compact width, so
there will be no constraint for landscape, or on an iPad, since those
aren’t cases of compact width.
We learned this one the hard way, when one of our buttons went
flying offscreen on the iPad. We’ll talk about the underlying size
concepts later, in a later chapter.
horizontally centered and maintains a constant distance from the top, while
the button continues to be uncentered in landscape orientation.
We can repeat the same steps to fix the button. First, select the button, click
the Alignment button, choose Horizontal Center in Container, and click Add
1 Constraint. This again gives us the orange line to tell us we’re not quite
done. Now click the Pin button, and show the menu for the top spacing. This
time we have a choice: we can pin either the distance to the Top Layout Guide
as before, or the distance from the top of the button to the label. This is the
power of constraints: we get to indicate what matters to us. Do we care about
the button’s distance to the top, or its relation to the label? In this case, the
label helps explain what the button does, so it makes more sense to keep
them together and pin the distance from the button to the label, rather than
the button to the top. So, select “Label - I finished the …” and click Add 1
Constraint. This gives us blue guides in Interface Builder, indicating that all
is well, including a blue brace between the button and the label.
Run the app again and both our components are centered regardless of ori-
entation, as seen in the Simulator screenshots. We can also change the device
type between 3.5-inch and 4-inch iPhone models in the scheme selector to
see the effect of the larger screen; it doesn’t matter much now because our
buttons’ vertical positions are measured from the top of the screen, but it
would be a big deal if we had anything pinned to the bottom, since we’d be
losing a half inch of space in the middle as we go from an iPhone 5 to an
older iPhone.
Now imagine if we had to explicitly set each object’s position and size in code:
it would be a nightmare! With autolayout, we get to describe size, shape, and
As layouts get more complex, autolayout has advanced features that we can
use to help resolve complex situations. Each constraint has a priority value,
so if we get into a case where conflicting constraints create an ambiguity, the
autolayout system can compare priorities as a tie-breaker. It’s also possible
to create constraints in code, so if we did have to create an arbitrary number
of views at runtime, we could still use autolayout on them and not resort to
doing our own math to position them. It’s a sophisticated system, but for
starters, it’s enough to just do the pinning and aligning supported by the
storyboard UI.
Next, we turned our attention to the user interface, since the best practice
for iOS development is to start with the UI and then build out the logic and
behavior behind it. Our UI is absolutely barebones at this point, but even
this is enough to make us come to grips with the differing sizes and shapes
of iOS devices, and what happens when the user rotates the iPad or iPhone.
To deal with this, we applied autolayout constraints to our UI elements, so
they put themselves in sensible places depending on how much room they
have to work with.
This chapter was a short diversion from our adventures in Swift in the first
three chapters, and now it’s time to bring it all together. In the next chapter,
we will connect the UI we’ve built in this chapter to new code we’ll write, to
make our buttons and other UI elements do their thing.
Obviously, there has to be some way to bring your two skill sets together, so
you can bring a user interface to life and have your code do more than just
produce log messages.
This chapter will let you close the loop by bringing these two worlds together:
you’ll connect user interface to code, so buttons can react to taps and your
code can update what’s on the screen.
Making Connections
So, how do we get the Send Tweet button tap to do something? After all, we’ve
been creating the user interface in the Main.storyboard file, but it doesn’t look
like there’s any place in this editor to start writing code.
In iOS, we use Interface Builder connections to tie the user interface to our
code. Using Xcode, we can create two kinds of connections:
The pane on the right has a jump bar at the top to show which file is in the
pane. After a pair of forward/back buttons, there’s a button that determines
how the file for this pane is selected: Manual, Automatic, Top Level Objects,
and so forth. Set this to Automatic and the contents of the file ViewController.swift
should appear in the right pane. We’ll have more to say about why ViewCon-
troller.swift is the file we need in the next few chapters, but for now, let’s take
the name at face value: this is the class that controls the view.
When we release the mouse in the source file, a popover asks us for the details
needed to finish the method declaration. On the first line, change the Connec-
tion from Outlet to Action. This is important—for a button tap, we want a
connection that goes from UI to code, and that’s what an action is.
We’re done with the Assistant Editor. Click the Standard Editor button in the
toolbar to return to one-pane mode. Select ViewController.swift in the Navigator
area amd you’ll see that Xcode has stubbed out a method signature for us:
connecting/PragmaticTweets-5-1/PragmaticTweets/ViewController.swift
@IBAction func handleTweetButtonTapped(sender: UIButton) {
}
Xcode has also made a change to the storyboard, but it’s not as easy to see.
Switch to Main.storyboard and bring the Utility area back, if it’s hidden. Click on
the button to select it. Then, in the Utility toolbar, click the little circle with
the arrow (or press ED6) to bring up the Connections Inspector. This pane
shows all the connections for an object in Interface Builder: all the outlets
from code to the object, and all actions sent by the object into the code. In
this case, one connection appears in the Sent Events section, from Touch Up
Inside to View Controller handleTweetButtonTapped. This connection, shown
in the figure that follows, is editable here. If we wanted to disconnect it, we
could click the little “x” button, and then reconnect to a different IBAction
method by dragging from the circle on the right to the View Controller icon
in the scene.
Honestly, we don’t break and remake connections very often, but if a connec-
tion ever gets inadvertently broken (for example, by renaming the method in
the source file), looking in the Connections Inspector is a good approach for
diagnosing and fixing the problem.
Logging
Back in Chapter 2, we learned about the NSLog() function for logging times-
tamped messages to the Xcode console. We can use that in our action to just
log a message every time the button is tapped, and thereby verify that the
connections are working. Select ViewController.swift in the File Navigator (D1) to
edit its source code and rewrite handleTweetButtonTapped() like this:
connecting/PragmaticTweets-5-1/PragmaticTweets/ViewController.swift
@IBAction func handleTweetButtonTapped(sender: UIButton) {
NSLog("handleTweetButtonTapped")
}
Run the app again, and tap the button. Back in Xcode, the Debug area
automatically appears at the bottom of the window once a log or error message
is generated, as seen in the following figure. Every time the button is tapped,
another line is written to the log and shown in the Debug area. If the Debug
area slides in but looks empty, check the two rightmost buttons at the bottom
of the Debug area, next to the trashcan icon; the left one enables a variables
view (populated only when the app is stopped on a breakpoint), and the right
(which we want to be visible) is the console view where log messages appear.
Another way to force the console view to appear is to press BDC.
The Social framework lets apps connect to social networks like Twitter and
Facebook easily. There are just three classes listed, one of which is SLCompose-
ViewController. Click that, and read its documentation:
The SLComposeViewController class presents a view to the user to compose a post for
supported social networking services.
Hey, that sounds perfect! When the user taps Send Tweet, we’ll just show the
SLComposeViewController, and let it do all the work of composing and sending a
tweet.
To start with, on line 2 we ask the SLComposeViewController class if it’s even pos-
sible to send tweets: it might not be if a given social network isn’t set up to
post.
On lines 5–6, we set the initial text of the tweet to "I just finished the first project in
iOS 9 SDK Development. #pragsios9" by calling the setInitialText() method on tweetVC.
This is all we need to do to prepare the tweet, so on line 7, we show the tweet
composer by telling self (our own ViewController) to presentViewController() with the
newly created and configured tweetVC, setting the animated parameter to true,
which makes the tweet view “fly in.” The third parameter, completion, specifies
code to execute once the view comes up; we don’t need that, so we send nil.
Finally, if isAvailableForServiceType() returned false, the else block on lines 8–10 logs
a debugging message that we can’t send tweets. As our skills improve, we’ll
want to actually show the user a message in failure cases like this.
And that’s it. We did all the work in IB to create the button and have it call
this method when tapped, so we should be able to just build and tweet at
this point, right? Let’s try running the app. Click the Run button and see
what happens.
Broken Builds
Let’s get a more detailed look at what’s going on. Visit the Report Navigator
using the rightmost button in the Navigator area toolbar, or just type D8.
This replaces the list of files with a list of our builds and runs, with the most
recent at the top. Click the top Build, and the Content area shows a build
log, as seen in the next figure. By default, the selected filter in this view is All
Issues, and aside from a possible warning about CODE_SIGN_ENTITLEMENTS (which
you’ll see as long as you aren’t set up to build for actual iOS devices), most
of the actual errors are Use of undeclared identifier 'SLComposeViewController' and Use of
undeclared identifier 'SLServiceTypeTwitter', which in turn cause the later errors.
This error means that the compiler doesn’t know we’re using the Social
framework, and therefore it doesn’t recognize the SLComposeViewController. Xcode
project templates only set us up to use the most common frameworks:
Foundation, Core Graphics, UIKit, and XCTest. Anything else has to be added
manually. So to tell the compiler about the Social framework, add the following
line near the top of ViewController.swift, after the import UIKit line:
connecting/PragmaticTweets-5-2/PragmaticTweets/ViewController.swift
import Social
The import directive tells the compiler to pull in another framework. This tells
the compiler and the linker about our dependency on the Social framework.
Once we add the import Social declaration, the red error icons on the side of our
code disappear. This is a good sign, so let’s try running again.
Tweeting at Last
This time the build completes without errors,
and the app will launch in the Simulator. Try
clicking the button; either the Xcode log will
say Can't send tweet, or the Simulator will show
an error alert saying that no Twitter accounts
have been configured, with buttons offering
to take you to Settings or to cancel.
Except that we’re still taking a lot on faith when it comes to actually calling
stuff in our code. We can search the documentation for cool-looking methods
all day, but first we should make sure we understand where all these classes
are coming from and how they’re organized.
The iOS SDK divides its functionality into a set of frameworks. We saw this
in the last section when we used import Social to add Social.framework to the
frameworks used by the project. Conceptually, we can divide the SDK’s
frameworks into four layers:
Media Layer
Graphics, sound, and video frameworks
Core Services
Frameworks for essential, non-UI functionality, like filesystem access, in-
app purchase (StoreKit), health-tracking device integration (HealthKit),
and so on
Core OS
Low-level frameworks and libraries needed by the upper layers, including
the BSD libraries that are the core of iOS and Mac OS X
In this book, we will spend most of our time working with the frameworks
that are included by default in the Xcode project templates: Foundation and
UIKit.
UIKit’s UIApplication class is the point of contact between our code and the rest
of the system. By accessing its sharedApplication() method, we can open other
apps by URL, receive remote events from Apple’s Push Notification service,
and set a number for our app icon’s badge. But a lot of apps don’t do any of
these things, so we don’t often use UIApplication directly. Instead, the Xcode
template sets up a UIApplicationDelegate class for us to customize; this class gets
callbacks when common events occur, like the app being started up or opened
via a URL from another app, or when it’s sent to the background by the user
tapping the home button.
The delegate pattern is frequently used in the iOS SDK, often as an alternative
to subclassing. The idea is that for certain responsibilities, usually the custom
behaviors specific to an app, an object can delegate its behaviors to another
object. In this case, the UIApplication class handles the activities that are common
to all applications, but for cases where different apps will want to do different
things, it makes callbacks to our AppDelegate. Delegates don’t need to be their
own classes like this: they are often classes with other purposes that just
implement one or two methods (usually collected as a protocol) in order to
serve as a delegate.
As for the app delegate itself, we’ll be revisiting it in a later chapter, when we
look into what apps can do in iOS 9 even when they’re not running.
The top-level UIView defines the common functionality of all views. All views
have visual properties, such as a backgroundColor, an alpha variable, and hidden
and opaque flags. As we’ve already seen, a view can contain other views; these
are accessible via a subviews property and can be added with convenience
methods like insertSubview(). A child view can access whatever view it’s a subview
of via the superview property. Subviews are layered on top of one another by
drawing them in the order of the subviews array, with the view at index 0 at the
bottom, then index 1 on top of it, and so on. For visual styling needs, UIView
also has a tintColor property that applies to all subviews, which makes it easier
to apply custom theming to all the UI components on the screen.
Views also have frame and bounds properties that indicate their size and location.
Each of these properties is a CGRect, a structure that defines an x-y origin (of
type CGPoint, a struct inherited from the Core Graphics framework) and a width-
by-height size (of type CGSize, another structure). The CGRect definition looks
like this:
struct CGRect {
var origin: CGPoint
var size: CGSize
}
The difference between a view’s bounds and its frame is that the bounds values
are in the view’s own coordinate system, while the frame is in its superview’s
coordinate system. So a subview’s frame’s origin is its top-left corner, relative
to its parent’s top-left corner at (0,0). Setting either property changes the
other as needed, and these interact with two related visual properties, transform
and center.
Along with views, UIKit provides the UIViewController class, which is meant as
the place where we put the logic for our user interfaces. The view controller
also has a number of life-cycle callbacks, telling it when its view is loaded
from the storyboard and when the view will appear or disappear as a result
of navigating to different parts of the app. We will look more at this relationship
in a later chapter.
Finally, UIKit provides classes for objects that are commonly needed by user
interfaces, such as UIFont and UIImage. Taken together, the UIKit classes provide
an extensive and extensible user interface toolkit.
Adding a UIWebView
Select Main.storyboard to bring up the UI in Interface Builder. We’re going to add
a Reload button at the top and a web view (a subview that renders web content)
to fill up most of the bottom of our view. While we’re at it, we can get rid of
the “I finished the first project” label; having a second button named Show
My Tweets, with an active verb, should provide enough context for users to
know that these are both buttons.
Using the Object library (CED3) at the bottom right, drag a new button above
the existing one, and give it the title Show My Tweets. Drag it until the center
guide appears. Now drag it toward the top of the view until the top margin
guide—another dashed blue line—appears. Click the autolayout Align button
Accessibility in UIKit
UIKit offers deep support for accessibility, the ability of a user interface to adapt to
a user’s needs, such as limitations in vision, hearing, and touch. Every UIView has
accessibilityLabel and accessibilityHint attributes, along with accessibilityTraits that describe the
view’s behavior, that the system uses to render it to users who need help. For example,
blind users can turn on the Voice Over feature to have the iOS speech synthesizer
speak the names of UI elements, using the provided accessibility values if they have
been set. These attributes can all be customized in the storyboard or in code.
Unfortunately, many developers don’t customize their UIs for accessibility. The good
news is, they often don’t need to: the default behavior of iOS makes typical UIKit
applications highly accessible. But it’s good karma—and a legal requirement in some
cases—to test the accessibility of our apps and customize these accessibility properties
as necessary. And if we were to create our own views, we would have to implement
these attributes on our own, so the system would know how to present our custom
view to a disabled user.
at the bottom of IB and use its popover to add a Horizontal Center in Container
constraint. Then click the Pin button and add a constraint pinning the distance
to the Top Layout Guide as 0, which should be the value that pops up auto-
matically because we dragged up to the top margin.
That’s enough to fully specify the new button’s constraints, but we still have
our old Send Tweet button. Drag it up or down to position it under the other
button, until a horizontal line appears between it and the Show My Tweets
button. Use the autolayout Pin button’s popover to pin a distance from this
button to Show My Tweets, at either the current distance or Standard. This
should turn the bottom button’s constraints blue, indicating it is now ade-
quately constrained.
Now we’re ready for the web view that will show our tweets.
Drag out a web view—as seen in the figure, its icon in the
Object library resembles the Safari app icon—and put it on
the bottom portion of the view. Use its handles to drag the
bottom and sides of the web view all the way to the bottom and sides of the
parent view, and drag the top until a horizontal guide appears between it and
the Send Tweet button. It may be easier to set the web view all the way at the
bottom first, then fix the sides, and then drag up.
We want this view to always fill the entire width of the screen, always stay at
the bottom, and always respect the distance to the Send Tweet button, so we
will need four constraints, all from the Pin button.
• 0 distance to the left and right sides of the parent View. Be sure to turn
off the Constrain to Margins check box to get all the way to the container’s
edges (the distance will initially come up as -20 points otherwise).
Click Add 4 Constraints and the web view will be properly constrained for
autolayout. It should look like the following figure.
For this screenshot, we’ve temporarily set the width to Compact to see it as
a portrait iPhone layout. It’s also possible to verify that our layout will work
in landscape. From the blue sizing strip, select a rectangle that is one box
tall and two boxes wide. The popover window describes this as “any width |
compact height,” and says it is for iPhones in landscape orientation. The
layout should now look like the figure on page 88. Notice that both buttons
maintain their expected spacing from the top, the web view, and each other.
We don’t have a lot of vertical space to work with in landscape, but for now,
the design is holding up. Just make sure to go back to Any/Any mode, so we
don’t inadvertently create any compact-specific constraints, a problem
explained back in Autolayout, on page 69.
At the beginning of the chapter, we talked about outlets, which are used to
connect objects in our code to objects in the storyboard. Since our view con-
troller is a Swift class, we can use a property of the class to refer to the web
view. The trick here is that preceding a property with the @IBOutlet modifier
tells Interface Builder that a property can serve as an outlet, and that will let
us connect the code to the UI.
Select Main.storyboard and switch back to Assistant Editor (via the “linked rings”
button on the toolbar, EDF, or the View menu). To make room for the split
view, we may want to hide the Utility area on the right. This will show the
storyboard on the left and ViewController.swift on the right; if this isn’t the case,
check the ribbon above the right pane and make sure it’s set to Automatic,
which picks the most appropriate counterpart file in the right pane given the
selection on the left.
To create an outlet property, we do the same thing we did to create the action
method for our button: Control-drag from the storyboard into the code. Start
a Control-drag from the web view in the storyboard and drag over to the
source code in the right pane, as shown in the following figure.
Drag over different parts of the source file without releasing the button; notice
that the tip “Insert Outlet or Outlet Collection” only appears when our drop
target is inside the curly brace that defines the class, and not within a method
inside the class. Anywhere in here, but ideally in the whitespace just below
the class declaration, release to end the drag. Xcode shows a popover to spec-
ify the outlet, much like it did when we created the action earlier. Make sure
Connection says Outlet and Storage says Weak, and give it the name twitterWe-
bView. When we click Connect, the following declaration is inserted into the
source at our drop point:
connecting/PragmaticTweets-5-3/PragmaticTweets/ViewController.swift
@IBOutlet weak var twitterWebView: UIWebView!
This one line of code is doing a bunch of things: it declares the attribute
IBOutlet (which lets us connect to it with Interface Builder), the weak keyword,
it has a var keyword to indicate the property’s value can change, and then
finally declares the name twitterWebView and type UIWebView.
Notice the UIWebView type is followed by the exclamation point character (!).
We saw that back in Maybe It's There, Maybe It Isn't: Optionals, on page 31
as an operator to force-unwrap optionals, while the optional type itself was
indicated by a question mark character (?). When used on a type, the ! marks
an implicitly unwrapped optional, an optional that does not have to be
explicitly unwrapped with an if let in order to use it. That’s super convenient,
and there’s only one catch: accessing an implicitly unwrapped optional is
exactly like using the ! operator to force-unwrap it, so if it’s nil, we crash
instantly. But it won’t be nil, so long as it’s been connected in the storyboard.
In fact, that’s the whole point: the twitterWebView property can’t be set before
the initializer is done, because loading from the storyboard happens later
(but prior to any of our code running). So, technically, it has to be an
optional, but it’s never really going to be nil unless we screw up something in
the storyboard, so we prefer to act like it’s a regular type.
Joe asks:
What the Heck Is the weak Keyword?
Earlier, we mentioned how Automatic Reference Counting (ARC) solves all our mem-
ory problems. Well, not quite. There are a few problems it can’t figure out for itself.
One is retain cycles, a problem that works like this:
• Our ViewController knows about the twitterWebView, so ARC can’t free the web view
from memory as long as the view controller exists. Otherwise, the view controller
might go looking for the web view and it would be gone.
• But if the twitterWebView also requires the ViewController to hang around in memory,
then neither can ever be freed from memory, even if we don’t need them anymore.
The way to break this is to declare one side of the arrangement as weak, meaning that
we don’t require an object to hang around in memory if ours is the only one that
knows about it.
The reason it works in this case is that the top-level view has strong references to all
its children (including the web view), and the view controller has a strong reference
to the view, so having an additional strong reference from the view controller to the
twitterWebView would be overkill. The rule of thumb is that only “top-level” objects in a
storyboard scene (like the view) need strong references, and everything else can be
weak. Xcode defaulted to this behavior when we made the connection, and it solves
the problem for us.
NSURL (which we assume to be an object that represents a URL), and from that
create an NSURLRequest.
But let’s start with getting the button-tap event in the first place. Select
Main.storyboard and again switch to the Assistant Editor. Make sure the right
pane shows ViewController.swift. Control-drag from the Show My Tweets to any-
where inside the class’s curly braces, as long as it’s not within an existing
method’s curly braces. When the drag passes over a viable area of the source
file, the drag point will show the pop-up tip “Insert Outlet or Action,” which
is what we want to do.
End the drag and fill in the popover, like we did earlier for Send Tweet. Change
Connection to Action, enter handleShowMyTweetsTapped for the method name, and
change the type from AnyObject to UIButton. Leave the defaults for event (Touch
Up Inside) and Arguments (Sender). Click Connect, and Xcode will stub out
a method for us:
connecting/PragmaticTweets-5-3/PragmaticTweets/ViewController.swift
@IBAction func handleShowMyTweetsTapped(sender: UIButton) {
}
Switch back to Standard Editor mode and select ViewController.swift. The method
that Xcode built for us with the drag says @IBAction, which just means that
Interface Builder, the storyboard editor, can work with it. It takes one
parameter, sender, which is the UIButton that sent the event (that is to say, the
button that was tapped). There’s no return type stated, so the method doesn’t
return a value.
On line 2, we create an NSURL from its initializer that takes an argument called
string:. We’ve used http://twitter.com/pragsiostest here, but feel free to put in your
own Twitter username. Notice this is in an if let, because the NSURL(string:) is a
failable initializer: its return type is NSURL?, reserving the right to return nil if
our string is garbage.
Next, line 3 takes this NSURL and makes a new NSURLRequest from it. We can
then use this urlRequest on line 4 to tell the web view to load up that page, by
using its loadRequest() method.
Back in if-else Statements, on page 29, we said that Swift now has a guard statement
that inverts the usual if-else flow, but it didn’t fit with how playgrounds work, because
guard statements require explicit returns, and we can’t return from a playground. Of
course, we can return from a method, and doing an early return if the NSURL string is
junk makes perfect sense here. So here’s what handleShowMyTweetsTapped() looks like
with a guard statement instead:
connecting/PragmaticTweets-5-3/PragmaticTweets/ViewController.swift
@IBAction func handleShowMyTweetsTapped(sender: AnyObject) {
guard let url = NSURL (string: "https://twitter.com/pragsiostest") else {
return
}
let urlRequest = NSURLRequest(URL: url)
twitterWebView.loadRequest(urlRequest)
}
We test the returned NSURL? against nil with a guard let, and if it fails, we do an early
return in the else block. Notice there’s no curly-brace block for the success case: it’s
just all the code after the guard. So that spares us a bunch of junky indentation: we
test everything up front, and then let the rest of the method speak for itself.
Apps built for iOS 9 or later are controlled by App Transport Security (ATS),
a feature introduced in iOS 9 to compel developers to adhere to safe, secure,
and private networking practices. If you’ve heard the phrase “https every-
where,” you get the gist: use secure connections wherever possible. Under
App Transport Security, any attempt to use a plain http-style URL fails
immediately. Our app is https, but as of this writing, not all of the twitter.com
SSL ciphers are up to Apple’s requirements, so either our NSURLRequest will
fail, or some of the images in the page won’t load.
App Transport Security allows us to carve out exceptions to its policies, and
since we’re still early in our study, we’ll use the simplest means possible. ATS
has a setting that basically means “allow everything,” so that’s what we’ll use.
This is where the App Transport Security settings go, but this UI is hard to
edit visually, and even harder to explain. (Trust us on this!) So instead, we’ll
go to the file where all these settings actually live. Under the Pragmatic Tweets
folder icon, select the Info.plist file. That shows the same metadata in the same
hard-to-use interface. Right-click or Control-click Info.plist to expose a pop-up
menu, and choose Open As Source Code. This lets us edit the raw XML.
Now, we can carve out our App Transport Security exception. Right before
the </dict> at the bottom of the file, add the following:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
What this does is basically turn off App Transport Security for the whole app.
We really shouldn’t ship an app with security turned off, and we won’t; we’ll
be able to replace this raw NSURLRequest later on. For now, this workaround
gets us out of security jail.
Along the way, you also learned a little more about Swift: weak properties that
make sure memory gets cleaned up when two objects reference each other,
and the guard statement that’s nice for early-return logic.
We’ve done a lot of work in this chapter, and that’s something to be proud
of. But what if something happens to our work? What if, while we’re developing
a new feature, we inadvertently break what’s already working? In the next
chapter, we’re going to see how Xcode’s testing tools will keep our app
behaving as expected.
Joe asks:
Why Isn’t Using https Good Enough for App
Transport Security?
Apple’s commitment to security and privacy is pretty serious, so just blindly using
https everywhere doesn’t actually cut it. In the case of Twitter, ATS flags us for not
providing “forward secrecy,” a more future-proof form of cryptography that can remain
secure even if some of the keys it uses later become compromised. As of this writing,
Twitter’s SSH support isn’t forward-secrecy compliant.
Turning off ATS is a blunt way around this. The nicer way to handle it is to tell ATS
to carve out a specific exception, just for the twitter.com and twimg.com domains, and
just for the forward-secrecy requirement. Here’s how we would declare that in Info.plist:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>twitter.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>twimg.com</key>
<dict>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>
</dict>
What this says to do is to turn off the forward-secrecy requirement for twitter.com and
all its subdomains. Beyond this exception, the rest of ATS remains in effect. Clearly,
if we were shipping this app, carving out a narrowly targeted exception like this would
be preferable to turning off ATS altogether. To learn more, visit http://developer.apple.com
and search for the “App Transport Security Technote.”
Well, how do we know that? We have run the app a few times, but have we
really pushed the limits of the app? Have we really tried everything that anyone
could possibly do to our app? How do we prove that our app is not going to
crash before we ship it off to Apple?
And as we start adding features, what proves that those changes work, or
that they’re not going to have weird side effects that break the stuff that had
been working?
The way we deal with this is to use unit tests. In this chapter, we’ll see how
we can use our Swift programming skills to make sure that the rest of our
code is doing what it is supposed to.
Unit Tests
Unit tests are exactly what they sound like. They are small, self-contained
segments of code that test very small, targeted units of functionality. Rather
than check to see if the whole application works, we can break the function-
ality into pieces to pinpoint exactly where errors and bugs are occurring.
Unit tests are designed to either pass or fail. Is this feature working the way
you want it to, yes or no?
The Parable of the Dinosaur
Here is an example of unit testing gone bad.
In Jurassic Park (the book, not the movie), Dr. Grant asks the scientists how they can be sure that
the dinosaurs are not breeding.
The scientists assure Dr. Grant that every precaution has been taken. They engineered the
dinosaurs to all be female. They had the island blanketed with motion detectors to count each
and every dinosaur every five minutes. They created a computer algorithm to check the number
and types of dinosaurs found by the motion sensors and the number only changed when a
dinosaur died. There had been no escapes. They knew everything happening on the island, and
they were completely in control.
Dr. Grant asks them to change the parameters of the computer program to look for more dinosaurs
than they were expecting to have. The scientists humor Dr. Grant and change the algorithm to
search for more dinosaurs. Lo and behold! There are more dinosaurs. After running the program
several more times with increasing numbers they eventually discover there are over 50 extra
dinosaurs on the island. Oops!
The program had been set up with the expectation that the number of dinosaurs could only go
down, never up. Once the program reached the number of dinosaurs it was expecting to find,
it stopped counting, and the scientists never knew there was an issue. The program anticipated
the outcome of dinosaurs dying or escaping the island but never the possibility that life could
find a way.
Unit tests also expose logic errors in our code. In the Jurassic Park example,
the code being run had a logic error that prevented the scientists from discov-
ering the problem until it was too late. We don’t want that to happen to you.
Writing tests also helps you write your code. Have you ever started writing a
piece of code only to figure out that one feature you spent days working on
wasn’t really going to work out in your project? By thinking critically about
what specifically you want your application to do, you can avoid writing
overly complicated and unnecessary code. They can inform the design of our
code: what part of the code has what responsibilities, and how we recover if
something unexpected happens.
• Debugging: These tests are built around bugs to ensure that when you
change the code these bugs do not reappear. Sometimes when we are
coding we make changes to the code that affect bugs that we have already
resolved. Since we do not want to see that bug again, we need to write
tests to make sure that the bug has not reappeared when we change
anything.
• Assert Success: We are testing to make sure you are getting a result you
want.
• Assert Failed: We are testing to make sure you are not getting a result
you don’t want.
We might wonder why you would need a test to assert failure. Isn’t the point
of testing to make sure that features we created work properly?
Think back to the Jurassic Park example. The scientists created tests to make
sure they were finding all of the dinosaurs they were looking for. They
asserted success once the number of dinosaurs they were looking for was
reached.
Click on the PragmaticTweetsTests.swift file in either the Project or the Test Naviga-
tor. There are four methods within this class: setUp(), tearDown(), testExample(),
and testPerformanceExample(). Every test class that we create will have a setUp()
and a tearDown() method. setUp() is used to instantiate any boilerplate code you
need to set up your tests, and tearDown() is used to clear away any of the setup
you needed to do for your tests. Whenever we find ourselves repeating code
in multiple tests, it’s a candidate for moving into setUp() and tearDown(). This is
the principle of DRY: Don’t Repeat Yourself.
Every test method we create will start with the word “test,” just as the testEx-
ample() and testPerformanceExample() methods demonstrate. The first of these is
an example of testing our app’s logic, and the second tests its performance
(that is to say, how long it takes to do something). Test classes take no argu-
ments and return no value—this pattern is how our tests are found and exe-
cuted by the test engine. A test passes if it returns normally, and fails if it
fails an assertion method before it returns.
For fun, let’s just run the test included in the template. There are several
ways to run your unit tests:
• Keyboard command: DU
• Main menu: Product > Test
• Clicking on the diamond icon next to either the test class or the specific
test in Xcode
The first two ways of running tests will run all of your tests, whereas the third
way will allow you to run selected tests. This is useful if you have one test
that’s failing and you want to focus on that one without having to run all the
others.
Well, we just changed the conditions of the test. XCTAssert must pass a true
condition in its first parameter for the test to pass. Since we programmed the
condition to be false, the test fails, and sends the string (which indicates what
was supposed to happen) to the test engine, which shows up as an error bar
next to the test method. Option-clicking on XCTAssert doesn’t give us nice doc-
umentation like most Swift methods, but we’ll cover the most useful XCTest
assertions later.
At first blush this might seem like a useless exercise. Why would we want to
write a test that always fails when you run it?
We run a test that is designed to fail so that we verify that the testing frame-
work itself is working properly. If we simply create nothing but tests that are
supposed to pass, we can’t know for certain that the tests are passing because
the code is correct. There could be an error and the tests would pass regard-
less. By prompting a failure, we now verify that when we write a test that
passes that our code is, in fact, working correctly. As one wise person put it,
“How do you know your smoke detector works if it never goes off?”
As we saw earlier, the method in XCTest to assert a true condition is XCTAssert(). The
assertions all take the form of asserting that some condition is true, and failing the
test if it is actually false. Different assertions make it easier to test numeric values,
whether or not optionals are nil, and so on.
There are about twenty different assertion methods in XCTest, but the ones we will be
using most often are
• XCTAssert()
• XCTAssertFalse()
• XCTAssertEqual()
• XCTAssertNotNil()
• XCTAssertThrowsSpecificNamed()
There is a complete list of every assertion in the “Testing with Xcode” programming
guide in your Xcode documentation, if you want to see how deep the rabbit hole goes.
Test-Driven Development
Now that we have a good handle on how to create a unit test, we are going to
delve into the realm of test-driven development (TDD). TDD, in a nutshell, is
figuring out the least number of objects you need to create in order to get
your application to work the way you want it to. TDD utilizes the idea that
you will write your tests first rather than after you have already completed
your application.
If we write tests for the app now, we’ll just be checking functionality we already
know works. In TDD, we write the test first, fail for lack of any working
functionality, then press ahead and actually create the functionality.
Why do we want to do all this extra work before we write a line of code? Let’s
jump in the Way Back machine and visit your elementary school English
class. Remember back when you were learning how to write stories your
teacher told you to write an outline. We write outlines for our stories so that
we have an idea about how our story is going to go. We want to figure out the
beginning, middle, and end so that we can write a tight and cohesive story
that follows a path and has an ending that makes sense. If you go into a
story not sure about what is going happen, you’ll wind up writing lots and
lots of plot where nothing happens.
Our time is valuable. It is in our best interest to figure out exactly which
features are important and which ones are not before you spend a week trying
to figure out and debug a feature that we figure out later doesn’t fit in with
what we want our app to do.
So let’s add a new feature, TDD style. Let’s say we want to have the web view
load itself when the app starts up, without having to tap Show My Tweets.
We’ll start by writing a test to make sure the web view got populated, initially
failing because it’s not being populated, then go back and add the feature.
When the test passes, our feature is good to go.
Xcode Targets
In Xcode, it is possible to create multiple applications based on the same codebase.
If we wanted to make, for example, a game where we had “full” and “lite” versions
where the only difference is how many levels are included, we could create two targets
that mostly differed by which level files were or weren’t included.
Since the primary application does not know what to do with a testing class, we don’t
want to include it in the codebase for that application; it would just take up space
on the end user’s device. Putting the test classes in their own target helps us segregate
them out.
Targets can also be used for other sophisticated build tasks, like running arbitrary
shell scripts prior to or after building our code. They can also be set as dependencies
of one another. For example, the tests target is dependent on the main app target,
so any time we run tests, any changes to the app’s code will be built first.
Creating Tests
What we need to do is to write a test method that can access the twitterWebView
property of the ViewController class. This actually presents a little bit of a hassle
that we haven’t had to consider before. Swift considers all the classes in the
PragmaticTweets target to be one module, and classes in a module can see each
other’s properties and methods by default. However, PragmaticTweetsTests is a
different target and thus a different module, so it cannot see the methods or
properties of our app’s classes. We’ll have to fix that before we can test any-
thing.
We can declare different levels of access for our classes and their members.
Swift has three levels of access, set by special keywords:
In the past, we had to make anything testable a public member, and make the
class itself public. That’s kind of ugly, because it would expose more of our
implementation than is really appropriate for good programming practices.
Fortunately, in Xcode 7, we get a nice new keyword, @testable, that relaxes the
access modifiers just for the purposes of unit testing, so that’s what we’ll use
here.
This will let our test code access the members of the ViewController class where
we wrote all our functionality in the last chapter. Now let’s write a test to let
us look inside that class.
Also, since we’re going to be writing our own test methods in this class, we
can delete the testExample() and testPerformanceExample() methods provided by the
Xcode template.
The test is really an outsider, so it doesn’t have direct access to the views on
the screen or the logic behind them. However, we can ask the UIApplication object
for the first view controller it’s showing (luckily, we only have one in our app)
and drill down from there. So let’s write a testAutomaticWebLoad() class like this:
testing/PragmaticTweets-6-1/PragmaticTweetsTests/WebViewTests.swift
Line 1 func testAutomaticWebLoad() {
- guard let viewController =
- UIApplication.sharedApplication().windows[0].rootViewController
- as? ViewController else {
5 XCTFail("couldn't get root view controller")
- return
- }
- let webViewContents =
- viewController.twitterWebView.stringByEvaluatingJavaScriptFromString(
10 "document.documentElement.textContent")
- XCTAssertNotNil(webViewContents, "web view contents are nil")
- XCTAssertNotEqual(webViewContents!, "", "web view contents are empty")
- }
Lines 2–4 are how we get to the ViewController object. The shared UIApplication
object has an array of UIWindows (one per screen, so usually just one unless
we’re doing AirPlay), and each window has a rootViewController. So we use a guard
let statement to try to get that rootViewController object as our ViewController class.
Not only does this object have to be non-nil, it also has to be of type ViewController;
we test for this with as?, the type cast operator. If we fail on either count, guard
let sends us to the early return on line 5.
If that works, then we want to inspect the contents of the twitterWebView. There’s
no method on UIWebView to just give us its contents, but there is the method
stringByEvaluatingJavaScriptFromString(), which lets us run any JavaScript string on
the contents of the UIWebView (seriously!). So on lines 8–10, we evaluate the
DOM property document.documentElement.textContent to get the text of the web page.
We are now ready to test whether or not this got anything. On line 11, we use
XCTAssertNotNil() to make sure the webViewContents is not nil. And then on line 12,
we use XCTAssertNotEqual() to make sure it’s not an empty string. If we survive
both of those test methods, the method executes normally and we pass the
test.
And we fail. We knew we’d fail, because we know the feature isn’t there. The
error message from the XCTAssertNotEqual() assertion appears next to that line
in the source to show us where the test failed. Our pass/fail results also
appear in the Test Navigator, and in the Report Navigator (D8), which has a
nice summary of all tests run, the Simulator or device we ran them on, and
which tests failed and where. See the following figure.
Go back to ViewController.swift. We want the web view to come up when the app
does, so all the things we do in handleShowMyTweetsButtonTapped() should happen
in viewDidLoad,() too. A good way to do that is to have them both call the same
thing. Copy everything currently in handleShowMyTweetsButtonTapped() into a new
method called reloadTweets(), and then make handleShowMyTweetsButtonTapped() just
be a call to reloadTweets(), like this:
testing/PragmaticTweets-6-1/PragmaticTweets/ViewController.swift
@IBAction func handleShowMyTweetsTapped(sender: UIButton) {
reloadTweets()
}
func reloadTweets() {
guard let url = NSURL (string: "https://twitter.com/pragsiostest") else {
return
}
let urlRequest = NSURLRequest(URL: url)
twitterWebView.loadRequest(urlRequest)
}
Now, add the line reloadTweets() to the viewDidLoad() method that Xcode provided
for us when it created the project. This method is called when the view loads
in from the storyboard, and, therefore, is the perfect place to do our automatic
web-page loading.
testing/PragmaticTweets-6-1/PragmaticTweets/ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
reloadTweets()
}
Run the app (not the test) with the Run button or DR to make sure it works.
The app comes up; the web page loads. We are good to go! Now run the test
with DU, and we will have finished our first TDD development.
Testing Asynchronously
Take another look at running the app. After the app appears, it takes a second
or two for the web page to load. But from the test’s point of view, as soon as
What we need is asynchronous testing, the ability to test things that happen
at unpredictable times. If we wanted to test that 2 + 2 == 4, or that a string
has a certain value, we could do that right away, because the value would be
there right when we asked for it. But with the web view, we don’t know when
(or if) its contents will be set. Asynchronous testing lets us test these kinds
of unpredictable events.
Prior to iOS 8, you could not run asynchronous unit tests using XCTest, so it
was impossible to do testing on the network calls, background tasks, or
anything else where the value to be tested was not immediately available.
The way to deal with these situations is a testing class called XCTestExpectation.
XCTestExpectation is an object that describes events that we expect to happen at
some point in the near future. We tell it how long it can wait, and then perform
test assertions elsewhere—in parts of the code that run asynchronously—final-
ly notifying the expectation when we’re done. And if we fail to do so in time,
that’s considered a failure.
Joe asks:
What the Heck Is an “Expectation Object”?
There is a wonderful quote by the late John Pinette that goes: “Salad isn’t food. Salad
comes with the food. Salad is a promissory note that food will soon arrive.”
Expectation objects are like salad. They are not the test; they are the promise to your
program that something is going to happen a little later.
If you went to a restaurant and got a salad, and then waited for an hour for food that
never arrives, you would realize something is terribly wrong. You were set up to expect
that another part of your meal was coming, and if it never arrived, your meal would
be a failure.
Back in the WebViewTests class, the first thing we will do is create an XCTestEx-
pectation object:
testing/PragmaticTweets-6-2/PragmaticTweetsTests/WebViewTests.swift
var loadedWebViewExpectation: XCTestExpectation?
This expectation object will start as nil (which is why it has to be an optional),
and we will populate it when we start the test. When we know the web view
has loaded, we can tell it that we’re done by calling its fulfill() method.
We’ll also need a way to know when the web view has loaded. Some general-
purpose techniques for doing asynchronous tasks are discussed later, but
for now, UIWebView can help us out. It has a delegate object that gets notified
when web pages load or fail to load, when the user submits a form, and when
other events occur. We can use that to know that the web page has loaded,
and then pass or fail the test.
On line 8, our test class becomes the web view’s delegate, so it can be notified
of events from the twitterWebView.
Next, lines 9–10 create the loadedWebViewExpectation and give it the name web view
auto-load test. If we have many expectations, the name helps us figure out which
one failed. We create as many expectations as we need—just one for now—and
kick them off with a call to waitForExpectationsWithTimeout() on line 11. If we don’t
call fulfill() on the expectation within 5 seconds we’ll get a timeout test failure.
Now that we have created our expectation object, we need to implement the
UIWebViewDelegate protocol. If you look in the documentation, you will see this
protocol has four methods that it can call: one to ask if it should start loading,
one to report an error, and one each when the page starts and stops loading.
We will implement two of these: if the web page load fails, our test fails, and
if it succeeds, we use the JavaScript call to see if the web view has any con-
tents. Let’s start with the easier failure case.
testing/PragmaticTweets-6-2/PragmaticTweetsTests/WebViewTests.swift
func webView(webView: UIWebView, didFailLoadWithError error: NSError?) {
XCTFail("web view load failed")
loadedWebViewExpectation?.fulfill()
}
If the web page doesn’t load, we use the always-fail XCTFail() to tell the test
suite we failed. Notice that we also have to fulfill() the expectation even though
we’ve already failed; if we don’t do this, we’ll get a timeout that looks like a
second failure.
This is like our first version in how it gets at the twitterWebView and runs the
JavaScript to get the web document’s contents as a string. The difference is
that all we do this time is fulfill() the expectation if we ever get contents that
aren’t an empty string. (It turns out we don’t want to fail on an empty string
because this will be called with an empty string when the app starts up; by
not failing at that point, we give the web view more tries to call us back with
some contents.)
Whew! That was a lot of code. Congratulations, you are now capable of doing
something that wasn’t possible before. Now click the diamond next to testAuto-
maticWebLoad(). This time, we pass the test, as shown by the green icon in the
Test Navigator in the figure.
However, one thing we haven’t really exposed to testing is the user interface.
If we broke the connection from a button to the method it calls, we would
never know, because we test the method, not the button itself.
Testing user interfaces has always been really hard to do, which is why a lot
of people don’t do it! The testing culture is much stronger among web devel-
opers—where you can always post the same HTTP request and scrape the
HTML you get back from a server—than among desktop and mobile developers.
Fortunately, Xcode 7 introduces a powerful new tool for testing iOS 9 user
interfaces. Let’s wrap up the chapter by trying it out.
Recording a UI Test
We currently test the functionality of the Show My Tweets button (if not the
button tap itself), but not the Send Tweet button. Let’s see how we can make
sure that button still does what it’s supposed to.
In the File Navigator, notice that after the PragmaticTweetsTests group we’ve been
working with, Xcode also created a PragmaticTweetsUITests group, with a single
file, PragmaticTweetsUITests.swift. Select this file and notice that it has setUp() and
tearDown() methods like before, although their contents are different from what
we saw in the regular unit test files. There’s also an empty testExample() method,
with a comment to get us started:
testing/PragmaticTweets-6-3/PragmaticTweetsUITests/PragmaticTweetsUITests.swift
func testExample() {
// Use recording to get started writing UI tests.
// Use XCTAssert and related functions to verify your tests produce
// the correct results.
}
“Use recording”? How do we do that? Notice that at the bottom of the content
pane (either above the Debug area or at the bottom of the window if that’s
now showing), we have a circular red button. That, as you might suspect, is
the record button. To see how it works, put the cursor inside the testExample()
method and start a blank line. Now click the record button.
This launches the app in the Simulator. After a few seconds, once the app is
up and running, tap the Send Tweet button. If the Xcode window isn’t covered
up, you may notice that code is being written inside the testExample() for us.
Once the SLComposeViewController view comes up to let us compose a tweet, click
the Cancel button. A few more lines of code get written for us. This is actually
all we need to record for now, so click the record button again to stop the UI
recording. Then stop the Simulator with the stop button on the top toolbar
as usual.
Take a look inside testExample() to see what the recorder has written for us:
testing/PragmaticTweets-6-3/PragmaticTweetsUITests/PragmaticTweetsUITests.swift
Line 1 func testExample() {
2 let app = XCUIApplication()
3 app.buttons["Send Tweet"].tap()
4 app.navigationBars["Twitter"].buttons["Cancel"].tap()
5 }
The code here is recognizable as Swift, even if the classes are not. And that
makes sense because we’re not writing code to create the UI here; we’re using
code to discover what’s on screen at a given time. The XCUIApplication object
created on line 2 is a sort of proxy that lets us discover what’s going on in
the app. We’ll use this to query for onscreen UI elements.
On line 3, our recorded code asks the XCUIApplication for an array of all buttons
currently on screen, and to find the one called Send Tweet. The first part of
this expression is of type XCUIElement, and it works as a sort of query. If it
resolves to exactly one object, we can programmatically tap() it. If there are
zero or more than one matching buttons, an error occurs and our test fails.
Line 4 does something similar, just more complicated: it asks for any onscreen
navigation bar with the title Twitter, and from that finds a button called
Cancel. Again, zero or more than one of either of these would fail.
This is a nifty way to discover our UI at runtime, and with further recording
we can discover how to interact with other parts of our app. We can also write
this logic by hand, or clean it up after the recorder is finished: for example,
we can access buttons by index rather than by name if that makes more
sense to us.
Writing UI Tests
Still, this isn’t much of a test: there’s no condition that we’re testing to be
true or false. If we click the diamond next to testExample(), or run all the tests
with DU, this test will pass, because there’s nothing to make it fail. So let’s
figure out what we want to test.
We know from writing the original ViewController class in the last chapter that
all the handleTweetButtonTapped() method does is show the SLComposeViewController.
So let’s make that the thing we test: on the last line that was recorded, we
don’t need to tap the Cancel button—we just want to make sure it exists.
This is pretty easy: the XCUIElement expressions that the recorder creates for
us have an exists property, which is true if there is one and only one matching
view. And something that’s true or false is something we can expose to the
XCUnit methods from the last section!
Change the name of the method to testSendTweet, and rewrite the method as
follows:
testing/PragmaticTweets-6-3/PragmaticTweetsUITests/PragmaticTweetsUITests.swift
func testSendTweet() {
let app = XCUIApplication()
app.buttons["Send Tweet"].tap()
XCTAssertTrue(
app.navigationBars["Twitter"].buttons["Cancel"].exists)
}
All we’ve changed from the recording is the last line. Now instead of tapping
the Cancel button, we just check that it exists, and we wrap this in a call to
XCAssertTrue(). Now we have a real test: if the button doesn’t exist, the assert
causes the test to fail, and then we need to look to see how this could have
broken.
Click the diamond next to testSendTweet() to run the test. We see the app run
in the Simulator, and, back in Xcode we fail with “XCAssertTrue failed.” Yay,
we finally have a failable test case! But, wait, shouldn’t this work? We didn’t
really change that much from the recording, after all.
It turns out recording can only take us so far. Look at the code: it tap()s the
Send Tweet button and then immediately looks for the Cancel button on the
tweet composer. But remember that when the app is running, it takes a second
or so for the tweet composer to slide in. Maybe the UI tester is trying to tap
the Cancel button before it’s even there, and that’s why it doesn’t exist yet.
Luckily, we can do just that. The NSThread class has a sleepForTimeInterval() method
that stalls execution for given number of seconds. That’s usually a bad idea
in an app, since we’d be stalling the app’s execution. But here, the app is
running in its own process, and what we’re stalling is the app runner. So add
a sleep statement right before testing for the Cancel button, like this:
testing/PragmaticTweets-6-3/PragmaticTweetsUITests/PragmaticTweetsUITests.swift
func testSendTweet() {
let app = XCUIApplication()
app.buttons["Send Tweet"].tap()
NSThread.sleepForTimeInterval(2.0)
XCTAssertTrue(
app.navigationBars["Twitter"].buttons["Cancel"].exists)
}
Run the test again, and this time we pass. Huzzah! Now we can extend our
XCUnit testing skills into the realm of the user interface, so if we accidentally
delete something from the storyboard, break a connection, or mess up the
event-handling method, tests will be able to detect it.
This is not idle speculation. Macs are generally more powerful than iOS
devices, so apps often run faster in the Simulator than on real iOS hardware.
A mouse or trackpad pointer is more precise than a finger touch, so running
in the Simulator might also blind us to usability problems in the app. And
Fortunately, our tests aren’t limited by the Xcode UI. Xcode provides a command-line
executable, xcodebuild, which can perform many of Xcode’s functions programmatically,
including building projects and running their tests. Combined with a scripting lan-
guage, this gives us all we need to run our tests automatically. In fact, this is how
popular continuous integration systems like Jenkins interface with Xcode.
For those who prefer an all-Apple solution, OS X Server offers Xcode bots, which are
Xcode-savvy services for building and testing Xcode projects. There’s more information
about bots in the “Xcode Server and Continuous Integration Guide” in the Xcode
documentation.
there is some functionality that simply doesn’t exist in the Simulator: the
Simulator won’t pretend that the Mac’s built-in webcam is either of the iPhone
cameras, and we can’t tilt our laptop back and forth to test motion-sensing
code.
To have full confidence in our app, we need to run it on the device. Let’s close
out the chapter by doing that.
Connect the device to the Mac with its USB cable. The first time you do this,
Xcode may present a dialog asking if you want to use the device for develop-
ment; be sure to approve this.
To put an app on a device, Apple wants to know who we are and what we’re
doing. That’s always been the case; what’s different in Xcode 7 is that they’ll
let pretty much anyone do it, not just paid-up members. Either way, the first
step is to sign into Xcode. Open Xcode’s preferences (D,) and select the
Accounts tab. We visited this tab back in Getting Local Documentation, on
page 13, in order to download local copies of the documentation. If you skipped
that step, well, do it now: click the + to add an Apple ID, and sign in with
your credentials (the same ones used for the Mac App Store to download
Xcode in the first place).
Just signing in isn’t enough. Go to the target’s General tab, where you’ll see
a warning that there are “No code signing identities found.” Code signing is
the process of using cryptographic techniques to provably verify that a known
developer is the one installing the app to the device, and therefore the opera-
tion can be allowed. Fortunately, we don’t have to do the underlying math;
there’s a handy Fix Issue button. Go ahead and click it.
If all goes well, this will use our Apple ID from the Accounts tab to download
and set up the needed credentials to allow us to put apps on the device. We’ll
talk more about how these actually work later, in Member Center, on page
290. Fixing the issue may also make you choose a Development Team; there
should only be one, with your Apple ID name followed by (Personal Team),
so use that.
Select this destination and run the app as usual (be sure to unlock the screen
first, since Xcode can’t enter your PIN for you). After a short delay in which
Xcode rebuilds the app for the device’s CPU (which is different than the Sim-
ulator’s, after all), it copies the app across the USB cable, onto the device.
The first time you run it, there’s one more wrinkle: Xcode trusts you and the
device, but the device may not trust you. This shows up as an Xcode error
sheet saying “process launch failed; Security.” If this happens, open the set-
tings app on the device itself and select General. You’ll see a new section
called Profiles & Device Management and, within it, the section Developer
App, which shows your Apple ID. Click this and, on the screen that follows,
tap to trust apps from your Apple ID, as shown in the following figure.
With this last security issue resolved, running the app in Xcode will make it
run on our device and behave just like before. At least we hope it does! If it
doesn’t, then we have a device-specific issue. And in cases where behavior
differs between device and Simulator, the device always wins, since that’s
what our users will run the app on. In fact, it’s so important to focus on the
device that the testing techniques we’ve learned throughout the chapter all
work on the device too; just choose a device in the scheme selector before
running the test.
We have explored Apple’s built-in unit testing suite XCTest, for testing both
application logic and user interface. We also saw how to test the user interface
and how it interacts with our code. And to top it off, we ran the app on the
device, so we’ll see exactly what the users will when they run it on their
iPhones and iPads.
You now have the tools to go forth into the world and test your apps so that
you can be sure your users will not have to deal with a crash or erroneous
behavior.
Now that you know how to write tests to ensure your features work as
designed, we’re going to start reworking those features. In the next chapter,
we’ll begin moving to the table view style of presentation that’s so common
on iOS.
In this chapter, we’re going start turning our Twitter application into one
that’s based around a table view. However, it’s going to take us a few chapters
to completely move away from the web view. First, we’ll put some fake data
into a table view, and then in the following chapters we’ll get real data from
the Twitter API and load it into the table view.
Tables on iOS
Coming from the desktop, one might expect a UITableView to look something
like a spreadsheet, with rows and columns presented in a two-dimensional
grid. Instead, the table view is a vertically scrolling list of items, optionally
split into sections.
The table view is essential for many of the apps that ship with the iPhone, as
well as popular third-party apps. In Mail, tables are used for the list of
accounts, the mailboxes within each account, and the contents of each
mailbox. The Reminders app is little more than a table view with some editing
features, as are the alarms in the Clock app. The Music app shows lists of
artists or albums, and within them lists of songs. Even the Settings app is
built around a table, albeit one of a different style than is used in most apps
(more on that later).
And while our Twitter app currently displays a web view of all the tweets we’ve
parsed, pretty much every Twitter app out there (including the official Twitter
app, as well as Twitterrific, Tweetbot, and Echofon) uses a table view to present
tweets.
So our task now is to switch from the web view to a table view–based presen-
tation of the tweets. We’ll build this up slowly, as our understanding of tables
and what they can do for us develops.
Table Classes
To add a table to an iOS app, we use an instance of UITableView. This is a UIS-
crollView subclass, itself a subclass of UIView, so it can either be a full-screen
view unto itself or embedded as the child of another view. It cannot, however,
have arbitrary subviews added to it, as it uses its subviews to present the
individual cells within the table view.
The table has two properties that are crucial for it to actually do anything.
The most important is the dataSource, which is an object that implements the
UITableViewDataSource protocol. This protocol defines methods that tell the table
how many sections it has (and optionally what their titles are) and how many
rows are in a given section, and provides a cell view for a given section-row
pair. The data source also has editing methods that allow for the addition,
deletion, or reordering of table contents. There’s also a delegate, an object
implementing the UITableViewDelegate protocol, which provides method definitions
for handling selection of rows and other user interface events.
These roles are performed not by the table itself—whose only responsibility
is presenting the data and tracking user gestures like scrolling and selec-
tion—but by some other object, often a view controller. Typically, there are
two approaches to wiring up a table to its contents:
It’s helpful to use the second approach when the only view presented by the
controller is a table, as this gives us some nice additional functionality like
built-in pull-to-refresh, or scrolling to the top when the status bar is tapped.
But if the table is just a subview, and the main view has other subviews like
buttons or a heads-up view, then we need to use the first approach instead.
Model-View-Controller
The careful apportioning of responsibilities between the view class and the controller
comes from UIKit’s use of the model-view-controller design pattern, or MVC. The idea
of this design is to split out three distinct responsibilities of our UI:
• Controller—The logic that connects the model and the view, such as how to fill
in the rows of the table, and what to do when a row is tapped
This pattern explains why the class we’ve been doing most of our work in is a “view
controller”; as a controller, it provides the logic that populates an onscreen view, and
updates its state in reaction to user interface events. Notice that it is not necessary
for each member of the design to be have its own class: the view is an object we cre-
ated in the storyboard, and the model can be a simple object like an array. At this
point in our app’s evolution, only the controller currently requires a custom class.
Still, some developers prefer the clarity of each role having its own class, so sometimes
you’ll see a class that exists only to implement UITableViewDataSource for a given table.
We’ll start by preparing our view controller to supply the table data. We can
do this by either declaring that we implement UITableViewDataSource, or by
becoming a subclass of UITableViewController. Since the table will be the only
thing in this view, let’s do the latter. In ViewController.swift, rewrite the declaration
like this:
tables/PragmaticTweets-7-1/PragmaticTweets/ViewController.swift
class ViewController: UITableViewController {
Select the view controller from the previously existing scene and press J to
delete the old scene. This leaves the storyboard with no entry point. Select
the Table View Controller, bring up its Attributes Inspector (ED4), and select
the Is Initial View Controller check box. The view gets an arrow on its left
side, showing our app once again has a place to start. The view itself shows
a status bar that says Prototype Cells above a Table View that has a single
Table View Cell as a subview, as seen in the following figure:
We can run this app…but it shows an empty table! That’s because the table
is not yet connected to a data source that can provide it with cells or even a
count of how many sections and rows there are. Let’s get to work on that.
While here, Control-click on the table view, or visit its Connections Inspector
(ED6), and notice that table view’s connections to the dataSource and delegate
properties are already wired up, connected to the view controller.
Notice that in our quick-and-dirty table code, three of our methods are called
tableView(). The reason these methods don’t get confused with one another is
because they’re differentiated by their named parameters: one takes titleFor-
HeaderInSection, another takes cellForRowAtIndexPath, and so on.
By convention, all these methods take the table view in question as their first
argument, so if we had multiple tables, a method would be able to figure out
which table it’s working with.
But as for why it has to be the first parameter, that’s more of a legacy of
Objective-C, where it was somewhat more natural to incorporate the name
of your first parameter into the method name, and differentiate with the rest
of the parameters. Swift came later, so we’re stuck with the old naming
schemes, at least for now.
In this book, when we encounter cases where the method name by itself isn’t
unique, we’ll include the parameters for clarity. That way, we’ll call out the
Chained Optionals
One other new thing to notice in the tableView(cellForRowAtIndexPath:) implementation is
this line:
This particular use of the ? operator is new, and quite handy. The textLabel property
of UITableViewCell is an optional, so ordinarily we would want to test it with an if let or
guard let. But in the middle of a chain of dot accessors, this is burdensome. And to
just force-unwrap with ! would be dangerous.
One alternative is to use the ? right before a dot operator. This syntax is called the
chained optional, and it works like this: the expression is evaluated left-to-right, and
all optionals marked with ? are tested against nil. If any optional is nil, processing stops
and the whole expression evaluates to nil. In an assignment like this, it’s OK for the
left side to be nil, because assigning a value to nil (instead of to a real variable) just
quietly does nothing.
If none of the optionals are nil, then we can get the value at the end of the chain, albeit
with one caveat: its type becomes optional, even if the last type in the chain wasn’t
optional. Again, that’s fine here, because the text property of the textLabel is also an
optional type: String?.
Lots of the changes since Swift 1.0 have made dealing with optionals easier. This is
one we’ll get a lot of mileage out of.
Anyway, while we’re in the ViewController.swift file, let’s delete the line that declares
the twitterWebView that no longer exists, and all of the handleShowMyTweetsTapped()
methods that populated it. We won’t need those anymore. Also, delete the
contents of reloadTweets(), but leave the method definition; we’ll rebuild that
one shortly. Finally, with no twitterWebView, there’s no need for the WebViewTests
test class, so delete that entire file.
In this implementation, we are telling the table that there are five sections,
that each section has one more row than the section index (that is to say,
there’s one row in section 0, two rows in section 1, etc.), and that any time a
new cell is needed, it should create a new UITableViewCell, get its textLabel property
(a UILabel), and set the text property of the label to a string that shows the row
number. When run, the table will look like the figure on page 125.
Now we have a table and a way to get data into it. What we need to do next
is provide a nontrivial implementation of the data source, one that actually
shows some tweets.
But an array of what? Well, one approach would be to just create a data type
including the parts of a tweet we care about—its text, the screen name of the
person who sent it, and so forth—and then have an array of those objects.
of the great things about Swift was getting away from classes and pass-by-
reference. Finally, we have a good opportunity to do just that: if we want a
data type representing the important parts of a tweet, a struct fills the bill much
better than a class. We don’t need this type to maintain state or have a bunch
of state-mutating functionality; we just need a container for values. That’s
totally perfect for a struct.
This takes us to the file dialog that indicates where to save the file and which
targets will build the class. For the filename, use ParsedTweet. As for the targets,
by default only the PragmaticTweets app will be selected, and not the Prag-
maticTweetsTests or PragmaticTweetsUITests testing targets. This is what we
want—we only need the file built for the app itself—so click Create to create
a ParsedTweet.swift file in our project.
The ParsedTweet structure doesn’t need to hold onto every field we could get
from Twitter for a given tweet—there are hundreds of them—just the ones we
want to show in the UI. For now, let’s figure we’ll want the tweet text, the
username, a created-at string, and a URL for the user’s avatar. So we define
public properties for those in ParsedTweet.swift.
tables/PragmaticTweets-7-1/PragmaticTweets/ParsedTweet.swift
struct ParsedTweet {
var tweetText: String?
var userName: String?
var createdAt: String?
var userAvatarURL: NSURL?
}
We’ll make all of these optionals, since we are in no position to populate them
when an instance of the struct is instantiated. The alternative would be to
assign a value like an empty string to one of these properties, but it’s more
expressive to use the absence of a value to say “this hasn’t been set yet,” even
if it might be a bit more work later to defend against the optional and make
sure the property isn’t nil.
The struct also gives us a memberwise initializer that takes all the fields at
once, so if it’s convenient to do so, we could do this:
let myTweet = ParsedTweet(tweetText : "Check out our new iOS book!",
userName: "@pragprog",
createdAt: "2015-08-31 08:19:00 EDT",
userAvatarURL: nil)
Keep in mind that if we use let, as in the second example, the myTweet structure,
and all its properties, are constants and cannot be changed. It’s good Swift
practice to make things constant whenever they can be, but if any of the fields
might need to change later, use a var.
ParsedTweet objects, and just come up with our own values for the tweetText,
userName, and createdAt strings.
Actually, let’s start with the URL. Twitter’s new user “egg” icon lives at a set
of URLs like https://abs.twimg.com/sticky/default_profile_images/default_profile_0_200x200.png,
where the 0 after profile_ can be any number between 0 and 6 inclusive, each
showing a different color, and the 200x200 has several replacements for different
sizes. For now, we’ll just use this one image over and over. At the top of View-
Controller.swift, after the imports and before the class declaration, add the following
constant:
tables/PragmaticTweets-7-1/PragmaticTweets/ViewController.swift
let defaultAvatarURL = NSURL(string:
"https://abs.twimg.com/sticky/default_profile_images/" +
"default_profile_6_200x200.png")
We had to split the URL string into two lines for the book’s formatting; feel
free to write it all on one line. We would if we could.
Now, we can create an array of ParsedTweet objects to serve as our data model.
After the curly brace that begins ViewController’s class declaration, declare an
array of ParsedTweets, and then inside square braces, use the memberwise
initializer to create as many tweet objects as you like (we’ll just use three to
save space). The beginning of the class should look like this:
tables/PragmaticTweets-7-1/PragmaticTweets/ViewController.swift
var parsedTweets: [ParsedTweet] = [
ParsedTweet(tweetText : "iOS 9 SDK Development now in print. " +
"Swift programming FTW!",
userName: "@pragprog",
createdAt: "2015-09-09 15:44:30 EDT",
userAvatarURL: defaultAvatarURL),
Now that we have an array that can serve as our data source, we can rewrite
the UITableViewDataSource methods to use the ParsedTweets in this array to calculate
the number of rows and the contents of each. Rewrite those methods as fol-
lows:
tables/PragmaticTweets-7-1/PragmaticTweets/ViewController.swift
override func numberOfSectionsInTableView(tableView: UITableView)
-> Int {
return 1
}
Also, go ahead and delete the titleForHeaderInSection() method; we’ll only have a
single section from here on, so the title is superfluous.
In this new version, we have a single section, and the number of rows in this
section is just the size of the parsedTweets array. Then we use the indexPath’s row
property to figure out which ParsedTweet to fetch from our parsedTweets array,
and put its tweetText into the cell’s text label.
Of course, the one line of text isn’t big enough for most tweets. So now that
we’ve got our data where it needs to be, let’s start improving the table’s
appearance.
have changed, so the table won’t do anything if we just edit the array. As a
controller, it’s our job to keep the view and model in sync. UITableView offers
methods to notify the table of distinct edits, like insertRowsAtIndexPaths() or
removeRowsAtIndexPaths(). Sometimes, it’s simpler to just do a full-on reload of
the table, with reloadData(). So let’s revise our old reloadTweets() method to do
that for us:
tables/PragmaticTweets-7-1/PragmaticTweets/ViewController.swift
func reloadTweets() {
tableView.reloadData()
}
Later on, this view won’t be the first thing we see in the app, so we want to
make sure to reload the table automatically any time it appears. Fortunately,
we’re already doing that with the call to reloadTweets() in the viewDidLoad() method.
The available styles are collected in an enumeration. Four cell styles are
defined in the UITableViewCellStyle enumeration, of which we’ve been using
UITableViewCellStyle.Default. Fortunately for our fingers, we don’t have to write the
whole type when we refer to an enumeration’s value; we can just write the
value itself, like .Default.
The figure on page 130 doesn’t show the cell’s other possible subviews; if an
imageView is set, it appears on the left side of the cell, and an accessoryView
(usually a Show Details button) appears on the right. Clearly, if all of those
subviews are present, the cell is going to get pretty crowded, and that’s
something we’ll have to deal with soon.
For now, we’ll try out the two-line presentation of the UITableViewCellStyle.Subtitle,
and along the way we’re going to fix a problem we’ve created for ourselves.
Grouped Tables
Another appearance option is to use grouped
tables, which is just an attribute we can set
on the table view in the storyboard. This sets
the table’s style to UITableViewStyleGrouped, which
in turn makes the table look like this figure.
Cell Reuse
Right now, we create a new UITableViewCell in
every call to tableView(cellForRowAtIndexPath:). If we
flick through a really long table, that might mean we create a cell that will
only appear for an instant before it goes off the screen and is no longer
needed. As it turns out, creating views is fairly expensive, so if we can avoid
doing that frequently, it will make our app faster and more responsive.
The UITableView class is actually built to cache and reuse cells. It provides a
method, dequeueReusableCellWithIdentifier(), that takes a string identifying a cell to
be reused. The idea is that we can create a cell in the storyboard as a sort of
template and identify it with a known string. In code, we’ll ask for a cell by
this name. If the table has already created a cell with this name and it isn’t
currently being shown—meaning it has scrolled off the top or bottom of the
screen—the table will give us the old cell and allow us to reuse it. Otherwise,
it will create a new cell from the prototype in the storyboard. This way, we’ll
create only as many cells as we need to show on the screen at one time, and
our scrolling performance will be much improved.
Go to Main.storyboard and select the table view. Notice that the table header is
Prototype Cells, and the table has a single Table View Cell as a child. The
table’s Attributes Inspector also has a field for Prototype Cells, currently set
to 1. We only need the one prototype cell, because new cells will be minted
from this prototype—if we had different layouts for different rows (like adver-
tising cells that were different from tweet cells), we could add more prototypes.
Select the Table View Cell, visit its Attributes Inspector (ED4), and set its
style to Subtitle. Then, for the Identifier, we need a string value that we can
use to fetch this cell from our code. Let’s use UserAndTweetCell.
Now we need to fetch this cell in code and customize it. We do this in table-
View(cellForRowAtIndexPath:), so go back to ViewController.swift and rewrite the method
as follows:
tables/PragmaticTweets-7-2/PragmaticTweets/ViewController.swift
Line 1 override func tableView(tableView: UITableView,
2 cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
3 let cell =
4 tableView.dequeueReusableCellWithIdentifier("UserAndTweetCell")
5 as UITableViewCell!
6 let parsedTweet = parsedTweets[indexPath.row]
7 cell.textLabel?.text = parsedTweet.userName
8 cell.detailTextLabel?.text = parsedTweet.tweetText
9 return cell
10 }
The result of dequeuing prototype cells instead of creating new cells every
time doesn’t have an immediate visual impact, although there is a subtle
performance improvement when scrolling a few hundred cells on the Simula-
tor, and this effect is much more pronounced when running on a genuine
iOS device.
While it’s nice to have both the tweet and its author, longer tweets could still
be truncated—we need a multiline label for those. How are we going to do
The alternative would be to do an if let or guard let, but what should we do if that fails?
dequeueReusableCellWithIdentifier() requires us to return a UITableViewCell of some sort, so the
failure case would seemingly have to just create a new, empty cell and return that.
Yet that wouldn’t do the user any good.
If we crash, it means we’ve messed up the storyboard in a way that isn’t recoverable.
With a programming error like this, the crash is arguably preferable: we’ll see it in
development and fix it, rather than ship an app with empty table cells.
that? It doesn’t look like it’s going to fit in the subtitle cell, and even if it does,
we still would like to have a third label to show the tweet’s timestamp.
Fortunately, we’re not limited to the provided cell styles. We can create our
own prototype cells with whatever views suit us—even tappable elements like
buttons—and then use them in our table.
Back in Main.storyboard, select the table view. We could edit the existing prototype
cell, but just to prove that we can juggle multiple prototypes, let’s create our
custom cell as a second prototype. In the Attributes Inspector, tap the up
button on the Prototype Cells field so the table has two prototypes. A second
prototype is created, a copy of the first. Select the second and, in the Attributes
Inspector (ED4), change its style to Custom and its identifier to CustomTweet-
Cell. Then, in the Identity Inspector (ED3), change its class to the
ParsedTweetCell class that we just created; the Module should default to Current
- PragmaticTweets.
Switch to the Size Inspector (ED5) and notice that the first field is Row Height,
currently shown with the placeholder text Default because the Custom check
box is not selected. If we want to pack a bunch of fields in here, it’s pretty
clear that the default row height is not going to cut it for us, so click Custom
and enter a height of 125. That should give us enough room.
Now we get to lay out a UI inside the cell pretty much the same way we built
the app’s original view with buttons and a web view in earlier chapters.
Within the cell, we can add labels, image views, whatever we like…provided
that we wrangle all the autolayout constraints to put them in their place (there
had to be a catch, right?). We’re going to add four subviews: labels for the
username, tweet text, and a created-at string, plus an image view for the
user’s picture. Feel free to play around; for the sample code, we’ve used the
following views:
• An image view (use the icon with the palm tree) with height
and width constraints to lock its size at 75×75 points, plus top
and leading constraints of 0 points each from the margin. The
image view will initially want to be much larger than this when
you drop it, but once you set the constraints, using Update Frames in
the autolayout Resolve menu (ED=) will clean it up.
• A label for the username, with the font set to System - Bold, leading con-
straint of 8 points from the image view, 0 point top, and trailing (i.e., right)
space constraints from the margins.
• A label for the tweet text, Lines set to 3, with the font set to System 14-
point, leading and top constraints of 8 points, and trailing constraint of
0 points from the margin.
• A label for the created-at string, with the font set to Caption 1, center-
aligned text, a Horizontal Center in Container constraint, and a bottom
constraint of 0 points to the margin.
Once we’ve created the layout, we’re going to connect this prototype cell to
the custom class we created earlier. Since we used the Identity Inspector to
assign the cell to our ParsedTweetCell, the Assistant Editor will let us connect
these subviews to new IBOutlet properties in that class. With the cell selected
in the storyboard, switch to Assistant Editor mode via the rings icon or EDF
(it may help to hide the utility pane too). Ideally, Assistant Editor should bring
up ParsedTweetCell.swift on the right, but sometimes it chooses ViewController.swift;
in that case, use the jump bar at the top of the pane to load ParsedTweetCell.swift
into the right side.
There’s one more thing we need to do in the storyboard: the cells know they’re
125 points tall, but the table doesn’t, and will continue to assume the default
row height of 44. Return to the Standard Editor mode, select the table, bring
up its Size Inspector (ED5), and set Row Height to 125. We could also provide
this height in code, which would in turn also make it possible for rows to be
of different heights, but this easy approach is fine for now.
Now it’s time to start populating these custom cells. Back in ViewController, we
again need to update our method that dequeues and populates cells. Rewrite
tableView(cellForRowAtIndexPath:) as follows:
tables/PragmaticTweets-7-3/PragmaticTweets/ViewController.swift
Line 1 override func tableView(tableView: UITableView,
- cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
- let cell =
- tableView.dequeueReusableCellWithIdentifier("CustomTweetCell")
5 as! ParsedTweetCell
- let parsedTweet = parsedTweets[indexPath.row]
- cell.userNameLabel.text = parsedTweet.userName
- cell.tweetTextLabel.text = parsedTweet.tweetText
- cell.createdAtLabel.text = parsedTweet.createdAt
10 if let url = parsedTweet.userAvatarURL,
- imageData = NSData(contentsOfURL: url) {
- cell.avatarImageView.image = UIImage(data: imageData)
- }
- return cell
15 }
The first big change here is on lines 3–5, where we dequeue the cell with the
identifier string CustomTweetCell, and since we know it’s our custom cell, we can
use as! to forcibly convert it to our ParsedTweetCell class. Then, on lines 7–9, we
set the values of the cell’s properties that we connected.
Then we have the avatar image. The UIImageView has an image property we want
to populate, of type UIImage. Unfortunately, UIImage can’t be created directly
from the contents of a URL. However, it will accept an NSData object containing
image data, and we can initialize that with an NSURL, so we can chain those
together with a chained if let on lines 10–11. If we make it into the curly braces,
we have the imageData, and use it on line 12 to populate the image view.
With the image loaded into the image view, we return cell to the caller, just
as before. The result looks like the figure on page 137.
This is looking a lot better. The avatar image is there, and the author name
is nicely set off from the tweet text and created-on date. The difference in
fonts lets us easily distinguish between username and their tweet, and the
smaller Caption 1 font downplays the timestamp label.
Pull-to-Refresh
There’s one other common table task we should attend to with our table: we
haven’t given the user a means of refreshing the tweets. We had this in the
last chapter with the Show My Tweets button, but now that our whole view
is a table (for now, anyway), there’s no way to expose this functionality. It
doesn’t matter now with our mock data, but it will matter a lot if we can’t get
new tweets from the Internet. How are we going to fix that?
This takes me back to a job I had back in 2002 or so, building a Java UI for a network
operations center. The idea was it would feed out video clips, and the engineers there
needed to see what was in the queue to go out, its priority, where it was going, and
so on.
The first cut of the UI used a Java Swing JTable and, yeah, it did look like an Excel
spreadsheet. I’d been playing with Mac OS X by this point—I was the only one in the
office using it, and took a lot of crap for doing so—and realized that this was a lousy
user interface. The wide table made it hard to trace a single item all the way across
the window, and reading vertically down columns wasn’t useful anyway. Also, JTable
defaulted to making columns the same width, even when some of our fields were 4
characters and others were more than 40. It didn’t need to be a table; it didn’t work
as a table.
So, I turned it into a list. I created custom cells that would group all the data
together, using layout, fonts, and color to call out the stuff that mattered most, and
how the items related to one another.
I liked it well enough that a decade later, with the company long gone, I still have
screenshots. Here’s one of the lists from the main status window:
The purple color scheme, programmer art, aliased text, and bland Windows NT fonts
are hard on the eyes 10 years later, but at the time, this was a real breakthrough for
us in how we thought about our in-house UI. You could easily see what was going
out when, which items had priority, and why the queue was arranged the way it was.
The cell layouts made the whole list more readable, and I’ve been a big fan of custom
cells in lists and tables ever since.
So let’s create and set up a suitable UIRefreshControl when our view controller
first comes to life, in viewDidLoad:
tables/PragmaticTweets-7-4/PragmaticTweets/ViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
reloadTweets()
let refresher = UIRefreshControl()
refresher.addTarget(self,
action: "handleRefresh:",
forControlEvents: .ValueChanged)
refreshControl = refresher
}
The action, meaning the method that’s invoked by the callback, is passed as
a selector, a string that uniquely identifies a method signature. In this case,
we’re promising to write a method called handleRefresh() that will take one
parameter, as indicated by the single colon character. By convention, these
action methods take a single argument to identify the sender, and have a
return type of IBAction, so they can be used for connections in the storyboard
(so we could also connect it with the Interface Builder GUI rather than with
code if we wanted to). So let’s write this action method:
tables/PragmaticTweets-7-4/PragmaticTweets/ViewController.swift
Line 1 @IBAction func handleRefresh (sender : AnyObject?) {
2 parsedTweets.append(
3 ParsedTweet(tweetText: "New row",
4 userName: "@refresh",
5 createdAt: NSDate().description,
6 userAvatarURL: defaultAvatarURL)
7 )
8 reloadTweets()
9 refreshControl?.endRefreshing()
10 }
Now that our user interface is ready, we can go out to the network to get real
Twitter data to populate the table. This will introduce a bunch of new chal-
lenges with how (and when) our code runs, but along the way we’ll solve the
problem with how the image loading slows down our scrolling.
And so on. And this will be great, until we get a prank phone call that doesn’t
respond to our robot saying hello. The script will wait on step 3. If the robot
is really literal-minded, it will get hung up there forever and won’t attend to
any of its other duties around the house.
We need the ability to express steps 4–6 as something to do once the waiting
in step 3 is done. These tasks will be saved away for the future, to be per-
formed when the other party finally responds (or maybe to be discarded when
the call simply disconnects), while the robot can continue with other tasks
in the meantime.
button tap or a rotation gesture will occur, or how long it will take to get data
from the network or write a document to the filesystem.
It’s very expressive to be able to say “when event foo occurs, do bar ,” or “do
foo , and if and when that finishes successfully, do bar , but don’t wait around
for it.”
A lot of the iOS APIs are written with an expectation of asynchronous behavior.
In this chapter, we’ll cover how to write “closures,” blocks of code that function
as completion handlers, meaning they do their thing only when some long-
running or indeterminately long task completes. To use the Twitter web ser-
vices, we’ll have to use them in two different scenarios: asking users to let
our app use their Twitter account, and making the network call to Twitter,
because one makes us wait for a user response and the other makes us wait
for the network.
Fortunately, iOS allows a user to sign in to her Twitter account from the
Settings app, and the iOS SDK will allow us to use that authentication. The
key to this is that the SLRequest includes an account property that represents
an authenticated user. All we need to do is to set that property before we send
off our request.
Let’s start by adding an import Accounts to the top of ViewController.swift, just like
we did when we added the Social framework.
asynchronicity/PragmaticTweets-8-1/PragmaticTweets/ViewController.swift
import Accounts
What...the...heck?
So what’s so great about closures? Well, for one thing, it means we get to take
some code and treat it as if it were any other type, meaning we can pass it to
and from functions, store it as a property, and so on. In this case specifically,
we get to write some code, give it to the ACAccountStore, and say “ask for permis-
sion to use the Twitter account, and run this code when done.” That’s precisely
what we want to do here: we want to write a closure that makes a Twitter
request if and when the user approves our use of her Twitter account.
Let’s begin by rewriting reloadTweets to get the current Twitter account. We’ll
take it slowly and just concern ourselves for now with getting the Twitter
account, not what to do with it yet.
asynchronicity/PragmaticTweets-8-1/PragmaticTweets/ViewController.swift
Line 1 func reloadTweets() {
- let accountStore = ACAccountStore()
- let twitterAccountType = accountStore.accountTypeWithAccountTypeIdentifier(
- ACAccountTypeIdentifierTwitter)
5 accountStore.requestAccessToAccountsWithType(twitterAccountType,
- options: nil,
- completion: {
- (granted: Bool, error: NSError!) -> Void in
- guard granted else {
10 NSLog ("account access not granted")
- return
- }
- NSLog ("account access granted")
- })
15 }
On line 2, we create an ACAccountStore object, which we’ll need for the next few
steps. The requestAccessToAccountsWithType() method will require us to get the
Twitter ACAccountType, which we do with a call to accountTypeWithAccountTypeIdentifier()
on lines 3–4. Once we have that, we can ask for access to Twitter accounts,
passing in the type (line 5), a set of options that can be nil for Twitter accounts
(line 6), and a completion handler closure that will be called with the result
of our request, which receives a Bool to indicate if we were granted access,
and an NSError that will describe any error associated with a failed request.
Our preliminary closure ranges from the opening curly brace on line 7 down
to line 14, where it ends with a telltale sequence of characters—})—which are
the curly brace to end the closure, and the close parenthesis that ends the
parameters to the requestAccessToAccountsWithType() call that began back on line
5. Line 8 is the signature that indicates what’s being passed into the closure
(the granted Boolean and an optional NSError), the return type (Void, meaning
nothing), and the keyword in that begins the closure’s logic. Inside this closure,
our first decision to make is whether or not we can proceed; if the variable
granted that’s passed into the block is false, then we don’t have access to Twitter
accounts and should just give up. For now, on lines 9–11, we will just log a
failure message and do an early return. Later on we should come back and add
a proper alert so the user knows what has happened.
On the other hand, if granted is true, we will be able to start talking to the
Twitter API, which means a lot more work! For now, we’ll just log a success
message (on line 13).
On iOS, the user may have set up several accounts of a given type; a fancier
app would show them and let the user pick one, but for now, we’ll just make
sure there’s at least one. We can use the ACAccountStore’s accountsWithAccountType()
to get all configured accounts of type twitterAccountType. Code the following,
directly before the }) that ends the closure.
asynchronicity/PragmaticTweets-8-1/PragmaticTweets/ViewController.swift
let twitterAccounts =
accountStore.accountsWithAccountType(twitterAccountType)
guard twitterAccounts.count > 0 else {
NSLog ("no twitter accounts configured")
return
}
Once again, we’re just using a guard and bailing out with an NSLog() message
to the console if there are no Twitter accounts configured. We could come
back later and give the user a helpful UIAlertController dialog in this case.
Continuing on the post-guard happy path, let’s assume the array contains at
least one Twitter account. What do we do with it? A while back—before we
had to work through getting access to the account—we noted there is an
SLRequest class that accesses the web service APIs of the social networks like
Twitter. It doesn’t have a lot of methods, and one that we should focus on is
performRequestWithHandler:(), whose docs say it “performs an asynchronous request
and calls the specified handler when done.”
Now we’ll be able to have our performRequestWithHandler() closure just call this
method, allowing us to put off for now just what’s in the Twitter response and
how we’re going to deal with it.
• requestMethod: The Twitter docs say we need an HTTP GET; for this, the Social
framework provides the constant SLRequestMethodGET.
Now we can go back into reloadTweets() and finish it up. To do so, we’ll build
up a call to performRequestWithHandler(), and have the handler block just call the
handleTwitterData() method we stubbed out. Continuing from after we did the
guard to make sure twitterAccounts has at least one member, this goes right before
the }) that ends the existing closure.
asynchronicity/PragmaticTweets-8-1/PragmaticTweets/ViewController.swift
Line 1 let twitterParams = [
- "count" : "100"
- ]
- let twitterAPIURL = NSURL(string:
5 "https://api.twitter.com/1.1/statuses/home_timeline.json")
- let request = SLRequest(forServiceType: SLServiceTypeTwitter,
- requestMethod: .GET,
- URL: twitterAPIURL,
- parameters: twitterParams)
10 request.account = twitterAccounts.first as! ACAccount
- request.performRequestWithHandler( {
- (data: NSData!, urlResponse: NSHTTPURLResponse!,
- error: NSError!) -> Void in
- self.handleTwitterData(data, urlResponse: urlResponse, error: error)
15 })
• Lines 4–5 convert a String representation of the Twitter web service URL
into an NSURL, the type needed by the SLRequest initializer.
• Lines 6–9 creates the SLRequest with the URL and parameters we’ve set up,
along with the constant for the Twitter service type, and the SLRequestMethod
enumeration value for GET requests.
• Line 10 gets the first object from the twitterAccounts array, and assigns it to
the request’s account property. Since the SLRequest wants an object of type
ACAccount, and the array turns out to be of type AnyObject, we forcibly convert
the value to the correct type with as! ACAccount.
Notice that by writing a closure within the closure we were already inside
of, when we’re done, we will have two consecutive lines of }), the telltale
end-of-closure syntax.
We’ve written a lot of code—and with closures two levels deep, this might be
the hardest thing in the whole book so far—so let’s run it to make sure it at
least builds and starts up in the Simulator. Nothing interesting will happen
in the Simulator, but the call from viewDidLoad() to reloadTweets() should produce
a message like the following in the Debug area at the bottom of the Xcode
window:
PragmaticTweets[6564:3175033] handleTwitterData(), 381073 bytes
This means the request is being sent to Twitter’s web service and being
responded to. Now it’s up to us to act on that response.
• Lines 1–3 are our method declaration. As explained a while back in Maybe
It's There, Maybe It Isn't: Optionals, on page 31, the bang characters (!),
indicate the parameters are implicitly unwrapped optionals: they’re still
optionals, but we can access their values directly without converting to
a non-optional type if we’re sure they can’t be nil. When Apple’s frameworks
call back to us like this, they usually send implicitly unwrapped optionals.
• This is followed by the guard let that we wrote earlier, which bails out if data
is nil. Otherwise, we have a non-optional NSData that we can parse in the
lines below.
• For the time being, we’re done: we’ve got an array or dictionary of parsed
tweet data in parsedJSON. On line 12, we’ll use NSLog() to print what we got
to the Xcode console.
Run this, wait for the table view to come up, and check the console at the
bottom of the Xcode window. The result will be a deeply nested structure, set
off by tabs and curly-brace blocks, showing each tweet as a set of name-value
pairs. The top of it will look something like this:
In the response, the first curly brace sets off all the data for one tweet, which
has keys named contributors, "created_at", and so on. Notice that the value for
entities is a curly brace, with its own child set of keys and values.
We can pick out interesting data like text, which is a string containing the
tweet’s text, and user, which is a dictionary of name-value pairs with items
like screen_name and followers_count. Everything we could want to know about
each tweet is in these entries, meaning we now have the data we need to
populate the UI.
We want to get at the raw Twitter data, so we ask for access to the user’s
accounts and wait for that to happen (since they might be blocked on an
Allow/Don’t Allow alert). If we are allowed to use the Twitter account, we send
off a request, wait for that to come back, and then use NSJSONSerialization to turn
the received NSData into an array of dictionaries, one entry per tweet. Both of
the waiting parts are done with closures, telling the iOS frameworks what
work we want them to do once they’re able.
We’ve written two closures, both using the “completion handler” pattern that
is common in iOS. In the next chapter, we’re going to use closures again, this
time to update the user interface. But instead of waiting for things to finish,
these closures will allow us to do multiple things at once, which opens the
door to a lot of cool possibilities.
Some tasks will require the robot’s limbs, some need its eyesight, and others
its voice and hearing. If we’re careful about how we divvy up tasks, the robot
can do several things at once: we can prepare dinner while talking on the
phone, and our robot should be able to as well.
So let’s imagine we have lists of what each part of our robot’s abilities can be
working on: a list of manual tasks, a list of visual tasks, a list of spoken tasks.
When it’s time for the robot to continue dealing with the phone call, it can
continue working on the manual tasks like cleaning or cooking, uninterrupted
by the voice task of handling the call.
Our robot is a multitasking genius. And, if we’re smart, our iPhone can be
too.
In this chapter, you’ll learn how Grand Central Dispatch offers the ability to
break up work into distinct units—closures—and parcel that work out to
whichever CPU core is most able to perform it at that point in time. You’ll
also see where the iOS frameworks force you to deal with concurrency, and
successfully do so. With this skill in your toolbelt, you’ll be able to keep your
user interface fast and responsive as you get long-running tasks out of the
way of the UI processing.
Developers from other platforms will see an analogy to threading, and the
queues are indeed performed by threads, but the difference in iOS is that the
threads and their queues are managed by the system, which is in a unique
position to best optimize the work. On other platforms, it’s hard to reason
about threads—if two threads are good, are four necessarily better? Maybe
that’s true on one CPU architecture, but not on another, and we can never
know when we’re coding. GCD takes responsibility for the problem and lets
us off the hook: “Give me work to do,” it says, “and I’ll figure out how to best
get it done.”
GCD provides functions to create queues and to put work on them. The
function we’ll use the most is dispatch_async(), which takes a reference to a queue
and a closure to execute on that queue. The async in the name means that
the call doesn’t wait for the closure to finish executing; the related dispatch_sync()
will actually wait until the closure finishes. Mostly, we’ll want to use dis-
patch_async() so our app doesn’t wait and instead can move on to other work.
But when we perform certain other tasks, GCD will put that work on other
queues. For example, since network calls are sometimes slow (and never
predictable in how long they’ll take), most of them are put onto other queues,
which allows the UIKit queue to get back to work processing user events and
redrawing the views.
First, since we’ll finally be putting real values in the parsedTweets array, we can
clear out the dummy values that we’ve used for the last few chapters. Replace
its definition at the top of ViewController.swift with an empty array of ParsedTweets.
Make sure this declaration is a var, because we will be modifying it.
concurrency/PragmaticTweets-9-1/PragmaticTweets/ViewController.swift
var parsedTweets: [ParsedTweet] = [ ]
Also, since we’re done with fake data, we can stop appending a fake tweet in
our pull-to-refresh method. So we’ll cut down our handleRefresh() method (in
the same source file) to just reload the tweets.
concurrency/PragmaticTweets-9-1/PragmaticTweets/ViewController.swift
@IBAction func handleRefresh (sender : AnyObject?) {
reloadTweets()
refreshControl?.endRefreshing()
}
Let’s go back to our handleTwitterData() method, specifically the if block that called
NSJSONSerialization.JSONObjectWithData and then just did an NSLog() to dump the
jsonObject to the debugging console. The first thing we’ll do is delete that NSLog().
Next, we want to start walking the JSON array of tweets, but we can’t know
for sure that it’s really an array, since NSJSONSerialization could have produced
a dictionary, if that’s what the root object in the encoding is. So we’ll use an
guard let to cast it to what we expect, and only enter the if block if it’s safe.
It’s not enough for something to be an array; Swift wants to know what’s in
the array. Put another way, it insists we say what it’s an array of. We said
earlier that the NSLog() output shows us it’s an array of dictionaries…which
to Swift just begs the question, “OK, buster, dictionaries of what?” We want
to say it’s a dictionary with strings for keys, but we really can’t guarantee a
consistent type for the values, since they can be strings, arrays, or dictionaries.
So, our answer is, “The array contains dictionaries, which themselves have
string keys and AnyObject values.” This is what we’re doing on line 1, but the
syntax merits an explanation.
That’s what the syntax [[String : AnyObject]] represents. The outer square braces
mean array, whereas the inner square braces with a colon represent a dictio-
nary, specifying its key and value types. We could also write this as
Array<Dictionary<String, AnyObject>>, but we think the square-brace syntax is eas-
ier to write and read.
Now we’re ready to walk the jsonArray. Our goal here is to pull out interesting
values for each tweet and put them in a new ParsedTweet object, which we’ll
then add to parsedTweets, the model for our table.
concurrency/PragmaticTweets-9-1/PragmaticTweets/ViewController.swift
Line 1 for tweetDict in jsonArray {
- var parsedTweet = ParsedTweet()
- parsedTweet.tweetText = tweetDict["text"] as? String
- parsedTweet.createdAt = tweetDict["created_at"] as? String
5 if let userDict = tweetDict["user"] as? [String : AnyObject] {
- parsedTweet.userName = userDict["name"] as? String
- if let avatarURLString = userDict["profile_image_url_https"] as? String {
- parsedTweet.userAvatarURL = NSURL(string:avatarURLString)
- }
10 }
-
- parsedTweets.append(parsedTweet)
- }
Starting on line 1, we count over each tweetDict in the array, which we know
is a dictionary because we only got into this if block by successfully casting
as an array of dictionaries. The first thing we do in the loop, on line 2, is to
create a new ParsedTweet. Instead of assigning its properties all at once with
the designated initializer like we did before, we’ll populate them one by one
as we pull them out of the tweetDict.
Some of the values we want to put in our ParsedTweet are at the top level of the
dictionary, so we assign tweetText and createdAt on lines 3 and 4, respectively.
These values could be nil, but that’s OK, since the ParsedTweet structure takes
optionals for its various properties, so at worst we’re just assigning them to
nil.
There’s a child dictionary with the key "user" that has user-specific details we
want, like the name and avatar URL. Of course, we don’t know for sure that
asking the dictionary for this key will give us something other than nil, so we
do an if let on line 4 to see if we can get that value as a dictionary with String
keys and AnyObject values.
Finally, with all the fields of the ParsedTweet populated, we add it to the
parsedTweets array on line 12. This allows our table model to pick it up when
it needs a cell for that row.
So now we should be good to go. Let’s go ahead and run the app.
Wait, what? At what point did anything we do here touch autolayout? This
makes no sense. Something is up.
Unfortunately, Swift won’t tell us which queue is running our code, so a def-
inite answer will have to wait until we play with breakpoints in Chapter 16,
Fixing the App When It Breaks, on page 275. But for now, there’s an easy way
to see how we’ve gotten ourselves in trouble. The main queue is run by the
lower-level “main thread,” and NSThread provides a class method isMainThread()
to tell us if the current thread (and therefore queue) is main.
To try it out, plop the following line of code in any of the app’s methods:
NSLog (NSThread.isMainThread() ? "On main thread" : "Not on main thread")
Inside reloadTweets(), this will print On main thread. But in handleTwitterData()—or the
completion handler closure of performRequestWithHandler() that calls it—it will say
Not on main thread. So, that’s key to our problem. And the reason we’re getting
that autolayout error message is that adding new cells to the table is indeed
performing layout operations, and we’re not doing it on the main thread.
Actually, we’re kind of lucky. The warning message says that this will become
an exception in a future version of iOS, which the user will experience as a
crash. But crashing or suffering 10-second delays while updating will get us
angry one-star reviews either way, so we need to fix it.
Our basic problem is that any calls to UIKit classes and their method must
be made on the main queue, and handleTwitterData() is being called on some
other queue. We’re only doing one thing that touches UIKit—reloading the
table—but that’s enough to get us in trouble. We need a way to move at least
that one line of code back to the main thread.
dispatch_async() takes two parameters: a queue to perform the work on, and a
closure with the work to be done. For the first, the point of this recipe is to
use dispatch_get_main_queue(), so all we ever have to change is the contents of the
closure.
Notice one important thing inside the closure: properties of the object, like
tableView, aren’t visible inside the closure, but self is, so we have to access our
properties in the form self.tableView.
Run the app again, and the table should reload about a second after the app
launches. This is much better. Our app does its network stuff on one queue
so that it doesn’t block the GUI; we can unpack our data on that other queue
and only touch the main queue for an update when we’re good and ready.
It’s concurrency in action, and when we’re smart about it, our apps can stay
nimble and responsive, which makes our users happy.
Joe asks:
Why Don’t I Have to Label the Second Parameter
to dispatch_async?
One of the signature traits of Swift is how the parameters of methods and functions
are labeled, except for the first one. And, usually, the method or function name will
indicate what the unlabeled first parameter is.
But this time, we didn’t have to provide a parameter name for the second parameter
either. Shouldn’t it say closure: or task: or something like that?
What’s going on here is that Swift parameters have “outside” names that callers see
and “inside” names that are visible inside the function. Consider the definition of the
JSONObjectWithData() method that parsed our JSON data:
The second parameter has both names: options is the outside name that we can see,
and opt is what the code inside Apple’s implementation would see. Notice the first
parameter has only one name, data, so that is both its inside and outside name.
The outside name here is the underscore character, _, which in Swift means “ignore”
or “omit.” So in this case, it means “do not provide the name block.”
Why does it work like this? We have a few guesses. Omitting the label makes this
more like its C version that existing iOS developers already know. It would also be
burdensome and messy to have to put the block: right before the curly-brace closure
syntax.
There’s also a related syntax that’s even more concise. When the last argument to a
method or function is a closure, you don’t need a label, nor do you have to include
it in the parentheses. Instead, just close the parens and put the curly-braced closure
right after it, like this:
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
We’ve left this syntax out of the book because we think it helps beginners to see that
the closure is an argument to the method or function, but some of our colleagues
really like the cleanness of this syntax.
Do-It-Yourself Concurrency
Actually, our app isn’t as fast as we might like. Try scrolling the table. The
scrolling is still choppy. This has been the case since way back in Custom
Table Cells, on page 133, where we started fetching the avatar images from
their URLs. So let’s think about what’s causing the problem and whether we
can fix it.
So, we’re blocking the UIKit queue on a slow network access. “Hey, wait a
minute,” you say, “isn’t that exactly what concurrency is supposed to fix?
And isn’t it exactly why the Social framework does the Twitter API call on a
different queue?” Exactly. And that means to fix our problem, we should do
what Apple does: get our network stuff off the main queue.
They Don’t Call It “Blocking” the Main Queue for Nothing
Lest anyone think the issue of keeping long-running tasks off the main queue is an academic
problem…well, do we have a story for you.
Years ago, one of the authors of this book was working at a company with a product that worked
with video. For a demo, we had to show that the application could copy this video to an analog
video tape recorder (VTR). Our solution was to connect the output of the video card to the VTR,
and to use an RS-232 cable to send “record” and “stop” commands to the VTR. It seemed easy:
to copy the video, we start the VTR recording and play the video from the PC, and then stop the
VTR when the video’s done. Easy peasy.
Except that the guy who wrote this didn’t know how threads work in Java, which is what the
application was written in. And desktop Java works almost exactly like UIKit: there’s a main thread
with an endless loop that looks for events like keypresses and mouse clicks, sends them to any
code that handles the event, and repaints the window.
So when the user clicked the Record button, the code to play the video and start recording on
the VTR was called…on the main thread. And that code effectively said, “Wait here until the
video is done,” which meant that the window didn’t update and no further events were processed
until the video was done playing.
Some of these videos were 15 minutes long. The application couldn’t do any repainting or event-
handling during this time, so if you covered up the window and then foregrounded it, it wouldn’t
repaint. On Windows, dragging the mouse over the window would leave a trail of unerased
mouse crud. Clicking a button did nothing. It was a disaster.
And this is pretty much where your author got to learn about threads, and had to completely
rewrite this part of the program so that all of the video stuff happened on another thread, freeing
up the main thread to immediately get back to work processing events and repainting, and then
having the video thread put UI work back on the main thread only when ready.
dispatch_async() comes to our rescue again. Recall that it takes two parameters:
the queue to put work on, and a closure with the tasks we want performed.
What we need now is a different value for that first parameter, one that isn’t
the main queue, but just some other queue. For this, there’s the GCD function
dispatch_get_global_queue(), which takes a constant that indicates the priority of
the system-provided queue we want. We’re not picky, so we can use
QOS_CLASS_DEFAULT to let GCD pick an ordinary background queue for us.
iOS gives us “quality of service” constants for GCD queue priorities. The pri-
ority names are meant to better express programmer intent than the
low/medium/high priority system from earlier versions of iOS. Unfortunately,
they’re not currently searchable in the Xcode documentation viewer, and are
only visible in a C header file. The following table shows the new constants,
and their older equivalents, which we’d have to use for code running on iOS
7 or earlier.
Lines 1–9 are one big dispatch_async() call. The difference here is that we want
to get work off the main queue, so on line 1, we use the GCD function dis-
patch_get_global_queue() with the constant QOS_CLASS_DEFAULT to let GCD pick an
ordinary background queue for us. That background queue gets the closure
that runs from lines 2–9. This closure contains the “get a UIImage from an
NSURL” logic from before, and then sets that image on the UIImageView. But since
updating the image view has to happen on the main queue, we use a second
dispatch_async() (lines 5–7) to wrap the UIKit work with a closure and put it back
on the main queue.
Race Conditions
What’s happened? A race condition, actually.
When a cell goes offscreen and is queued for
reuse, it will eventually get dequeued and
filled with new data. But the closure that fills
in the image doesn’t know that. In this case, there was some cell for one of
Janie’s tweets that went off screen, dequeued, and repopulated with one of
Chris’s tweets (for the first row in the figure), but then the closure finished
and filled in the image with Janie’s picture. This doesn’t happen often—we
had to request 200 tweets, plus simulate poor network conditions to get the
screenshot—but it is a bug, and if there’s any way to make it happen in
development, it’s for sure going to hit someone in the real world.
The fix is to figure out when a closure has taken too long. How do we know
that? Well, if the problem is that the cell has already filled in the contents
from a different tweet, we can look to see if the parsedTweet that the closure
started with has the same data that’s displayed by the cell now. So here are
the new contents for the if let url = parsedTweet.userAvatarURL block:
concurrency/PragmaticTweets-9-2/PragmaticTweets/ViewController.swift
Line 1 cell.avatarImageView.image = nil
- dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0),
- {
- if let url = parsedTweet.userAvatarURL,
5 imageData = NSData(contentsOfURL: url)
- where cell.userNameLabel.text == parsedTweet.userName {
- dispatch_async(dispatch_get_main_queue(), {
- cell.avatarImageView.image = UIImage(data: imageData)
- })
10 }
- })
Joe asks:
Can I Slow Down the Simulator Long Enough to
See the Cells Get the Wrong Image?
If your Internet connection is really good, you may load the image data too fast to see
the wrong-cells bug. This shows off one disadvantage of working with the Simulator:
its performance is unrealistically good, particularly for networking tasks. A Mac Pro
with Gigabit Ethernet is going to get a web service response a lot more quickly than
an iPhone with one bar of 3G coverage out in the woods somewhere.
Fortunately, a Mac can simulate lousy network conditions for this kind of testing.
From the Xcode menu, select Open Developer Tool > More Developer Tools to be
taken to Apple’s Xcode downloads page. After asking for a developer ID and password,
the page shows optional downloads for Xcode. Look for the latest version of the
Hardware IO Tools For Xcode, download it, and double-click the Network Link Condition-
er.prefPane to install it.
This adds a pane to the Mac’s System Preferences called Network Link Conditioner,
which adjusts the performance of the Mac’s current networking device (Ethernet,
AirPort, and so forth) to resemble real-world conditions an iOS device might face,
from Wi-Fi with good connectivity to the outdated Edge network experiencing packet
loss.
Keep in mind, however, that the Network Link Conditioner degrades all network
traffic on the Mac, not just the iOS Simulator application. So if we forget to turn it
off when we’re done testing, it will make everything we do seem like we’re getting one
bar in the middle of nowhere.
So we have a recipe for getting work onto and off of the main thread: just call
dispatch_async(), with the work to be done as a closure. For the queue, we use
dispatch_get_main_queue() to put work on the main queue, or dispatch_get_global_queue()
to get a system queue that can get our work off the main queue. Either way,
we’re exploiting concurrency, the ability of the system to do many things at
once, and now we’re smarter about how to let the main queue keep doing its
event-dispatching and repainting thing, while we do ours.
We’ve now got a pretty full screen with this table of tweets, but we still want
to do a lot more with our app. The only way to do that is going to be to start
having several screenfuls of information and navigate between them.
Once we decide we want to start doing that, we’re going to have more scenes
in our storyboard, and more files in our project. Each time we want a new
scene, we’re generally going to have to create a new UIViewController subclass to
provide its logic. And there are probably common tasks we’ll want to do in
many places, so we want to look for opportunities that can be split out into
utility functions or classes.
In this chapter, we’re going to reorganize the work we’ve done so far so it’s
more manageable as the app grows and so we can reuse some of the work
we’ve done in multiple places.
Usually, iOS tables do something when the user clicks on a table row. And
most of the time, that something is to show the details of the thing that was
clicked on, with a new interface appropriate to the detail view.
What’s happening in that case is that iOS is presenting a different view con-
troller, one that is designed specifically for the task at hand. So for our app,
that means we want to go from the view controller that shows all the tweets
to one that shows just the specifics of one tweet. From here, we could go to
another view controller: for example, we could click on the tweeter’s profile
image to go to a view controller that shows details about him or her. Each
view controller is built for one task—showing all the tweets, showing the
details of one tweet, showing the details of the Twitter user—which allows us
to divvy out functionality to different classes within our codebase.
To start adding new view controllers to our application, we’ll want to make a
few changes to our source code. We need the ability to arbitrarily grow our
application by adding new classes and new storyboard scenes, and we need
to start thinking about where we are going to put new code, and where we
have opportunities for code reuse. All in all, it’s a good time to tackle some
much needed refactoring.
Refactoring in Xcode
Refactoring is the disciplined practice of making small changes to a codebase
that alters its internal structure without changing its perceived behavior.
Xcode offers a handful of refactoring tools; the rest we’ll do by ourselves.
Where do we start? Well, we’ve sketched out our idea for using multiple view
controllers, and once we’ve gone ahead and done that, the default name of
our current ViewController is going to be a liability, since we might well ask,
“Which view controller?” Let’s rename it to clear up any future confusion.
Renaming
What should we call it? Looking at its functionality, we could call this some-
thing like TweetListViewController. However, in navigation-based apps, we typically
refer to the first view controller as the root view controller, so let’s use
RootViewController as our new name.
We might be tempted to just change the name of the file in the Finder or
Xcode’s File Navigator, but this would cause all kinds of breakage, since
other files in the project would still be looking for ViewController files. And finding
all those references, particularly in the storyboard, is a tedious and error-
prone process. Instead, we can ask Xcode to do the name change for us.
Oh no! An error message! Frustratingly, Xcode still isn’t able to refactor Swift
code, a year after Swift was released. We expect Apple to support this eventu-
ally, so keep in mind that the Refactor menu item is there, and maybe it’ll
actually work eventually. But for now, we’ll have to do it by hand, just like
we said we didn’t want to. Ugh!
With the class name ViewController still selected, change the name in the class
declaration to RootViewController. For consistency, we want the filename to match
its contents, so in the File Navigator on the left (D1), select ViewController.swift,
click again to edit the line, and change its name to RootViewController.swift
So far, that’s two steps that a working Refactor menu item would have saved
us, but there’s one more. The storyboard still thinks that its one scene has
a view controller of class ViewController. But that class no longer exists, so if we
run now, our table is empty and the debug pane shows the error message
Unknown class ViewController in Interface Builder file.
To fix this, go to the storyboard, select the View Controller in the scene list,
and visit the Identity Inspector (ED3) in the utility pane on the right. The
first section here is Custom Class, which we used before when we were telling
the storyboard to use our custom table cell class. Now we want to reconnect
it with our custom view controller class, so change the value of the Class field
to RootViewController. Save, run, and the app works again.
The lack of a working Refactor menu item for Swift is a hassle, but at least
we have a recipe: rename the class in its source file, rename the source file,
and then use the Identity Inspector to rename any occurrences of it in the
storyboard.
Navigator would still become hard to read, once we have dozens or even
hundreds of files.
One way to manage our code is to create groups. These are the folder icons
in the File Navigator, several of which were created for us by the Xcode tem-
plate when we started the project. We can move files into groups to organize
them and strategically show and hide them, to make our project easier to
manage. Note that these aren’t real folders on the filesystem; they’re just an
organizational tool within Xcode.
There’s also a quick way to create a group: select multiple files, and choose
File > New > Group from Selection.
Our group’s files are indented slightly and can be hidden entirely by turning
the disclosure triangle on the side of the group. As we create new view con-
trollers, we’ll put them in their own groups, and we can expand just the groups
we’re interested in at a given time, so we don’t see a bunch of files that we’re
not working on at the moment.
To do this, let’s think about how to make a more generic Twitter API caller.
The current reloadTweets() method uses the ACAccountStore to construct an SLRequest,
then calls its performRequestWithHandler(), which then calls back to our handleTwit-
terData() in the completion handler closure. The things that are specific to
RootViewController are the URL and the parameters sent to the SLRequest (in this
case, they specify the home_timeline.json call and its parameters), and the
response handling, which is all in another method (handleTwitterData()). Put
another way, everything in reloadTweets() other than the URL and the parameters
is something we would do for any Twitter request, and is therefore reusable.
The big difference is that the genericized Twitter request code should be in a
reusable location so that classes other than RootViewController can call it. This
class will go in the Twitter Utilities group, since it will work with the ParsedTweet
class we’ve already created.
What should go here? Do we want a class? Well, no, because the Twitter
request handler doesn’t have to manage any mutable state; it just gets an
account, makes a request, and runs arbitrary code when the data comes
back. The classic iOS approach from the Objective-C days would be to create
our own delegate: we could make a Twitter-calling class, and a protocol
declaring a delegate method for it to call back to.
This method declaration takes three parameters: the Twitter URL and
parameters for the request are what we need to create the SLRequest object that
makes our Twitter call. The third parameter is the SLRequestHandler type that
is sent to SLRequest’s performRequestWithHandler() method. We haven’t seen this
type in a while, but we’ve been using it constantly: it’s the method signature
for a closure executed when the request comes back. Specifically, it’s a closure
that takes the argument types (NSData!, NSHTTPURLResponse!, NSError!) and returns
Void. If that rings a bell, it might be because it’s precisely the signature we
pass through to our RootViewController’s handleTwitterData() method.
Now we’re ready to write the implementation. It’s a lot of code, but it should
look very familiar too:
growing/PragmaticTweets-10-1/PragmaticTweets/TwitterAPIRequestUtilities.swift
let accountStore = ACAccountStore()
let twitterAccountType =
accountStore.accountTypeWithAccountTypeIdentifier(
ACAccountTypeIdentifierTwitter)
accountStore.requestAccessToAccountsWithType(twitterAccountType,
options: nil,
completion: {
(granted: Bool, error: NSError!) -> Void in
guard granted else {
NSLog ("account access not granted")
return
}
let twitterAccounts =
accountStore.accountsWithAccountType(twitterAccountType)
guard twitterAccounts.count > 0 else {
NSLog ("no twitter accounts configured")
return
}
let request = SLRequest(forServiceType: SLServiceTypeTwitter,
requestMethod: .GET,
URL: requestURL,
parameters: params)
request.account = twitterAccounts.first as! ACAccount
request.performRequestWithHandler(completion)
})
}
That’s a lot of code. We can write it fresh, or just copy and paste the contents
from reloadTweets() in RootViewController, with the following changes:
Now we have a generic Twitter API request-maker that can be used by any
and all view controllers that will want to make Twitter requests. To try it out,
we’ll finish our refactoring by having RootViewController use this class.
We still set up our NSURL with a guard let, but after that, all the Twitter stuff
has moved to our utility function. Plus, our splitting off the response-handling
still pays off, as the completion closure is just a trivial call to handleTwitterData().
Run it and…nothing’s changed! And that’s exactly what we want! The point
of refactoring is to change the code while maintaining the same behavior, and
that’s just what we’ve done—only now, it will be easier to grow the project,
since much of the Twitter-specific code (and everything relating to the Accounts
framework) is no longer in this view controller class, and what’s left is
directly related to the specifics of this view controller’s Twitter request, and
the specifics of the handling the response. The latter is still in handleTwitterData(),
unchanged from where we started the chapter.
Now that we’ve completed this refactoring, any other view controllers we write
that need to call the Twitter API can use code much like what’s now in reload-
Tweets() in that they’ll just need to provide a URL and a parameters dictionary,
and parse the response in the completion handler closure. The details will be
specific to the Twitter API they’re calling (tweet details, user info), but that’s
exactly what we’d want anyway: genericize the common Twitter behavior, and
let callers specify their unique details.
Just for kicks—and with the understanding we’ll undo this silly code in a few
minutes when we’re done with it—take a look at AppDelegate.swift. This class
has to do with how our app interacts with the rest of the system, something
we’ll talk about more later. For now, notice there is a method called application-
WillEnterForeground(). This is called when the app is brought back to life from the
background. Let’s do a Twitter call every time this happens.
Let’s use a simple Twitter API: users/suggestions. This sends us some suggested
topics, based on who we follow. Thanks to our TwitterAPIRequest class, asking
for this is really simple; just rewrite applicationWillEnterForeground() as follows:
growing/PragmaticTweets-10-1/PragmaticTweets/AppDelegate.swift
func applicationWillEnterForeground(application: UIApplication) {
guard let url = NSURL (string:
"https://api.twitter.com/1.1/users/suggestions.json") else {
return
}
sendTwitterRequest(url,
params: [ : ],
completion: { (data, urlResponse, error) -> Void in
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions([]))
NSLog ("suggestions JSON: \(jsonObject)")
} catch let error as NSError {
NSLog ("JSON error: \(error)")
}
})
}
Our call is pretty simple. If the URL is legit, then we make a call to our
genericized sendTwitterRequest() that passes in the URL and an empty parameters
dictionary. Our completion handler tries to parse the JSON and, if that doesn’t
throw an error, dumps it to the console.
Now, run the app. Our table appears as usual. Our new code only runs when
the app returns to active duty from the background, so use the Simulator’s
Hardware > Home (BDH) to background the app, then bring it to the fore-
ground by tapping its icon, or select it from the running apps list by double-
tapping home (that is, BDH twice, quickly). The request will go out and
populate our debug pane:
PragmaticTweets[7147:692392] suggestions JSON: (
{ name = Sports;
size = 15;
slug = sports; },
{ name = Television;
size = 11;
slug = television; }, …
With about 10 lines of new code we fired off and handled a new Twitter API
request. That’s how we’re going to build out the functionality of our applica-
tion, creating new view controllers and letting them reuse this general-purpose
Twitter function we’ve created for ourselves. Of course, this was a somewhat
silly exercise—feel free to delete the changes in AppDelegate.swift—but it does
prove out our general-purpose sendTwitterRequest() function pretty nicely.
Now that we’ve genericized our Twitter code, we’re ready to move beyond the
single view and give the user the ability to navigate between scenes, each able
to take advantage of the work we’ve done so far.
In this chapter, we’re going to add view controllers that can show things like
tweet and user details, and use three different ways of navigating between
multiple view controllers. These are the common idioms we use for moving
around in an app on the iPhone and iPad, and they let us choose whether to
use the entire screen, split things on different parts of the screen, or even
have it both ways, depending on how much screen space we have.
Navigation Controllers
The most common way to work with multiple view controllers is to use a
navigation controller—a view controller that manages a stack of child view
controllers. This UINavigationController will become the new point of entry to the
storyboard. Our current RootViewController is the the first thing it shows. Then
we’ll add more view controllers, and the navigation controller will keep track
of which one we’re looking at and how to go back to earlier ones in the stack.
We won’t have to write a new class for this, as the UINavigationController is meant
to be used as is and is seldom subclassed.
Drag this into the storyboard. During the drag, it will appear as two views
connected by an arrow. Drop it close to the existing view controller, but above
it. Once you’ve dropped the icon, the storyboard will have three scenes and
three view controllers with their attached views: our original root view con-
troller, a navigation controller, and a table view controller, as shown in the
following figure. Note that we’ve zoomed out to get all three scenes on screen
at once, although all we can do at this zoom level is move scenes around, not
edit their views.
Run the app now and…nothing’s different! That’s because there’s no way in
the storyboard to reach the navigation controller or its child view controller.
We can change that by selecting the navigation controller—either in the scene
list or from the yellow ball in the bar under its view—and bringing up its
Attributes Inspector (ED4). Find the Is Initial View Controller check box and
select it. In the storyboard graph, the arrow that went into our root view
controller now goes into the navigation controller.
Run again and…now our tweets are gone, replaced by a table with the title
Root View Controller. What we’re seeing is that the app now enters via the
navigation controller, which in turn shows its first (root) child controller,
which is the empty table view controller that Xcode gave us when we dragged
in the navigation controller. But we don’t want this controller: all our custom
table cell work is back in our old view controller.
What we need to do is to tell the navigation controller to use our old view
controller as its root view controller. Notice that in the scene list, the last
entry in the Navigation Controller scene is Relationship “Root View Controller.”
That needs to change. Control-click on the navigation controller, or bring up
its Connections Inspector (ED6). Under Triggered Segues, there’s a connection
called Root View Controller. Starting from the connection’s circle, begin a
drag (which will stretch out a blue line) and drop the connection on our old
view controller, as shown in the following figure.
Run again and our app is pretty much back to normal, showing all our tweets
as before. The only change is that there’s now a big blank space at the top of
the screen. Still, progress!
1. A left-side bar button item. For anything but the root view controller, this
is typically a Back button, and a default back button will be provided if
we don’t set a different bar button item of our own.
We can easily customize all these things in the storyboard. First, as a bit of
cleanup, we can get rid of that empty table controller that Xcode gave us, the
one that was attached to the navigation controller. Select its view controller
and press J; the entire scene disappears. We can also grab the title bar atop
our root view controller and move it around the storyboard to get it closer to
the navigation controller; notice that as we do this, the arrow connecting the
two (representing the navigation controller’s “root view controller” relationship)
stretches and bends as needed to keep the two connected.
Zoom back into a full-size view of the Root View Controller and bring up its
Attributes Inspector. Notice that the top bar says Inferred. This is what it’s
always been set to; the storyboard figures out whether or not to show the
navigation bar based on whether the view controller has a navigation controller
as a parent, which it now does. Double-click in the center of the navigation
bar and it’ll turn into an editable text field. Type Tweets and press F to finish
editing. Notice that this changes the name of the scene to Tweets Scene, and
the view controller icon (the little yellow ball) to Tweets. Run again and our
tweets now have a nice title bar. Keep in mind that we haven’t added a UILabel
or UITextField. What we’ve done here is tell the navigation bar what title to use
for this view controller. To prove this point, notice in the scene list that what
we’re editing here are the properties of a “navigation item” within the Root
View Controller scene, not a label or any other sort of view.
The two bar button items in the navigation bar also give us an
opportunity to add functionality to our app. In fact, they give us
a very nice way to bring back our New Tweet feature! In the
Object library, scroll down to the smaller Item icon; this is the
bar button item, shown in the figure. The UIBarButton is very different from the
UIButton we’ve used before; in fact, it’s not even a subclass of UIView! It’s an
object that contains just enough state to be drawn in a bar and to be able to
call a method when tapped.
Drag the bar button item to the right side of the top bar in the Root View
Controller scene. A highlight to accept the drop will appear, and after the
drop, the bar button item will appear as Item in the bar. We can edit its text
in place, but there’s a better option. Select the bar button item and bring up
the Attributes Inspector. The second attribute listed is System Item, with a
default value of Custom. Custom bar button items are those that have custom
labels. However, there are about 20 other choices, representing common
actions like Search, Refresh, and Trash. From this list, choose Add. This turns
the bar button item into a plus (+) symbol, which is a reasonably intuitive
way to tell users that this is how they’ll compose a tweet, and more practical
in limited space than text like Compose Tweet would be. The next figure shows
our finished navigation bar.
Now that we’ve customized the button’s appearance, we need to give it some
functionality. Fortunately, our functionality already exists: it’s the handleTweet-
ButtonTapped() method we wrote way back in Making Connections, on page 75.
So we just need to wire up a connection. Control-click the add button to bring
up the heads-up display (HUD) showing its connections, and notice that
instead of the UIButton’s various events (Touch Up Inside, for example), there’s
just a Triggered Segues action and a Sent Actions selector. What we want
now is to just call a selector, so drag from the “selector” line over to the Root
View Controller icon (the yellow ball that now says Tweets). When we complete
the drop, a pop-up will show us the selectors we can connect to.
navigation/PragmaticTweets-11-1/PragmaticTweets/RootViewController.swift
@IBAction func handleTweetButtonTapped(sender: AnyObject) {
Now, back in the storyboard, select the “+” bar button item, Control-drag
from the selector to the view controller icon, and after completing the drop,
choose handleTweetButtonTapped(). Note that there’s a faster way to do this: rather
than bring up the pop-up, just Control-drag from the bar button item to the
view controller, which figures out that you want to connect the selector, since
the other kinds of connections don’t make sense here.
At any rate, run the project again and tap the add
button. The tweet compose view controller returns,
although at this point the default “I just finished the
first project” text seems totally out of date. We’ve
gotten a lot further since then! With the addition of
the navigation bar, our app much more closely
resembles the other major Twitter apps on iOS.
We can begin in the storyboard. From the Object library, choose the
generic View Controller icon, which looks like the rectangular UIView
icon inside a yellow circle, as seen in the figure.
Drag the icon (which turns into a view with the usual title bar underneath)
into the storyboard, dropping it to the right of the existing root view controller.
Once dropped, it appears as a completely empty view, and when not selected,
the title bar above the view simply says View Controller. This view controller
has no visible contents and cannot be reached from any other view controller,
but we can change that easily enough.
From the Root View Controller scene on the left, select CustomTweetCell,
which is the one we customized with the styled labels and the icon. Control-
drag from there to the new view controller (either its entry in the scene list
or its icon, or its view out on the storyboard; any of these will work). This
gesture indicates that you want to create a segue from the cell to the new
view controller when the cell is tapped. Optionally, instead of Control-dragging,
we can bring up the connections pop-up with a Control-click and drag the
Triggered Segues: Selection connection over to the new view controller.
Drag a Navigation Item icon from the Object library to the new scene.
You should now be able to double-click in the navigation bar and
change its name to Tweet.
The other thing that changed when we dragged the segue between the two
scenes is that there’s now an arrow connecting the root view controller to our
new view controller. In the middle of the arrow is a circular icon that repre-
sents the segue, which is the object managing the transition between the two
view controllers. We’ll have more to say about using segues a little later.
For now, let’s run the app and see what we have. Once the tweets table gets
populated, tap one of them. The tweets view will slide out to the left while the
new view slides in from the right. Although it’s empty, we can easily get our
bearings thanks to the navigation bar, which shows our Tweet title. The
navigation controller also provides a back button on the left, which by default
uses the title of the previous view controller: Tweets. Not bad, getting naviga-
tion for free without having written any code for it!
This is where the segue can help us. Prior to performing a transition between
view controllers, the current view controller gets a callback on the method
prepareForSegue(), passing in details of the transition in a UIStoryboardSegue object.
As inherited from UIViewController, this method does nothing, but we can override
it to take some interesting action, based both on our current state and details
of the segue.
When called, this looks at the segue argument to see if it matches the identifier
we put in the storyboard: showTweetDetailsSegue. If it does, it gets the selected
row from the table, looks up the corresponding tweet, and logs its text to the
console. Run the app and tap a row to verify this is working; if not, check the
spelling of the segue identifier in the storyboard and the code to make sure
they match exactly.
navigation/PragmaticTweets-11-2/PragmaticTweets/ParsedTweet.swift
var tweetIdString: String?
In the Twitter API response, the tweet’s unique ID string is identified with the
key id_str, so that’s what we need to get from the response dictionary and set
on the ParsedTweet. Put this assignment in RootViewController’s handleTwitterData(),
where we do the rest of our JSON unpacking. A good place for it is right before
the line with the if let that creates the userDict.
navigation/PragmaticTweets-11-2/PragmaticTweets/RootViewController.swift
parsedTweet.tweetIdString = tweetDict["id_str"] as? String
Now we’re ready to send the tweet ID to the second view controller, and let it
get more detailed tweet information.
This class will have one public property, a tweetIDString. When we set this
property, we want the view controller to immediately take that ID and fetch
the details of the tweet from Twitter. We can do that with the didSet keyword
introduced way back in Computed Properties, on page 40. Stub it out:
navigation/PragmaticTweets-11-2/PragmaticTweets/TweetDetailViewController.swift
var tweetIdString: String? {
didSet {
reloadTweetDetails()
}
}
This will cause the reloadTweetDetails() method to run anytime the tweetIdString is
set. Of course, that method doesn’t exist yet, so quickly stub out an empty
implementation so we don’t get build errors.
navigation/PragmaticTweets-11-2/PragmaticTweets/TweetDetailViewController.swift
func reloadTweetDetails() {
}
Now the pieces are all in place to deliver the tweetIdString from the first view
controller to the second when the transition happens. Go back to RootViewCon-
troller.swift, and rewrite the prepareForSegue:sender:() method as follows:
navigation/PragmaticTweets-11-2/PragmaticTweets/RootViewController.swift
Line 1 override func prepareForSegue(segue: UIStoryboardSegue,
- sender: AnyObject?) {
- if segue.identifier == "showTweetDetailsSegue" {
- if let row = tableView?.indexPathForSelectedRow?.row,
5 tweetDetailsVC = segue.destinationViewController
- as? TweetDetailViewController {
- let parsedTweet = parsedTweets[row]
- tweetDetailsVC.tweetIdString = parsedTweet.tweetIdString
- }
10 }
- }
The big change here is lines 4–6. First, we check to see that there is a non-nil
row selection (line 4), and then ask the segue for its destinationViewController (line
5), and attempt to cast it to a TweetDetailViewController (line 6). If this works, then
we can assign the tweetDetailVC’s tweetIdString on line 8, which will kick off the
setter method we wrote before.
• A button at the upper left, with fixed width and height of 60 by 60, the
top edge pinned 8 points from the top layout guide, and the leading edge
pinned 0 points from the margin. Use the Attributes Inspector to change
its type to Custom, which will allow us to change its image. Unfortunately,
this will make it effectively invisible in the layout view, but it can still be
selected, and it’s still present in the scene list. Our reasons for making
this a button will be revealed later in the chapter.
• A label called Screen Name just below the Real Name label, leading space
8 points from the button, with a top edge 8 points down from the label
above it (a fixed distance to the superview will also work), and trailing
space of 0 to the right margin. Use Title 3 for the font style.
• A label called Tweet Text, leading edge and trailing edges 0 points in from
the side margins, 8 points down from the button, and set to 0 lines so it
can grow as needed to accommodate the tweet text. Use text style Body.
• An image view, top edge 8 points down from the Tweet Text label, leading
and trailing edges 0 points from the margins, bottom edge 20 points in.
Use the Attributes Inspector to set its mode to Aspect Fit, which will scale
the image to fill one or both dimensions, without cropping or stretching
(but without necessarily filling the entire space).
When finished, the layout should look something like the screenshot on page
188 (we’ve selected the button for this figure so you can see it’s there).
Notice that since we’re overriding the viewWillAppear() method inherited from
UIViewController, we have to put an explicit override in the method declaration,
and call the superclass’s implementation as part of our own.
Now we’re ready to use the Twitter code we refactored in the previous chapter.
We’re going to call sendTwitterRequest() to ask for the details, and then parse the
response.
The Twitter API provides the statuses/show.json call to get details about a single
tweet, and takes a single parameter, id, with the unique ID of the tweet, so
that’s what we’ll call in our reloadTweetDetails().
navigation/PragmaticTweets-11-2/PragmaticTweets/TweetDetailViewController.swift
func reloadTweetDetails() {
guard let tweetIdString = tweetIdString else {
return
}
if let twitterAPIURL = NSURL (string:
"https://api.twitter.com/1.1/statuses/show.json") {
let twitterParams = ["id" : tweetIdString]
sendTwitterRequest(twitterAPIURL,
params: twitterParams,
completion: { (data, urlResponse, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
self.handleTwitterData(data, urlResponse: urlResponse,
error: error)
})
})
}
}
self.userScreenNameLabel.text =
(userDict["screen_name"] as! String)
self.userImageButton.setTitle(nil, forState: .Normal)
if let userImageURL =
NSURL (string: userDict["profile_image_url_https"] as! String),
userImageData = NSData (contentsOfURL: userImageURL) {
self.userImageButton.setImage(UIImage(data:userImageData),
forState: .Normal)
}
}
} catch let error as NSError {
NSLog ("JSON error: \(error)")
}
}
Once the response is received, the various labels and the image button are
all updated, including the tweet text, which, thanks to autolayout, can grow
or shrink to as many lines are needed to contain all the text. Try it now and
verify that everything’s OK. We can also see the effect of using the dynamic
type font styles by opening the Settings app, choosing General > Accessibility
> Larger Text, and adjusting the text size slider.
If the selected tweet has an image that was uploaded with Twitter’s own image-
hosting service (not a third-party service like TwitPic or img.ly), the tweetDict
will contain an entities dictionary with extra attachments. Within this, there
may be a media array, each describing one attachment as a dictionary.
Assuming this is the case, we can take the first element of this array, which
will be a dictionary, and look for a media_url_https (there’s also a media_url, but
for best practices, we should use the more secure URL).
We can dig into the tweetDict for any image attachments as part of the closure
we just wrote. Just after the part that set the button image, and still inside
the closing curly brace of the if let userDict block, add the following logic:
navigation/PragmaticTweets-11-2/PragmaticTweets/TweetDetailViewController.swift
if let entities = tweetDict["entities"] as? [String : AnyObject],
Wow! That’s a busy if let. It’s doing five things in a row, drilling down the tree
structure for keys entities, media, and media_url_https; trying to create an NSURL from
that; and then trying to create an NSData from the contents of that URL. If all
the keys are found and the URL and data objects are non-nil, we get to the
inside of the curly braces, where we create a new UIImage from the data and
assign it to the UIImageView. In Swift 1.0, these were five separate if lets, and a
textbook example of the “pyramid of doom” that Swift’s language changes have
cleaned up.
Modal Navigation
We’ll close out the chapter with a different approach for presenting view
controllers, and how it ties into one of the neatest things about navigating
through storyboards.
In the tweet detail controller, we used a button rather than an image view to
present the user’s icon, and this is where we’re going to use that: we’ll allow
the user to tap the icon to go to a third view controller, one that presents
details about the user.
Modal Segues
Add another view controller to the storyboard, to the right of the tweet detail
view controller (again, it may be necessary to zoom out to organize the views
nicely). Scroll so that both the tweet detail view controller and the new view
controller are visible at the same time, and Control-drag from the user image
button to the new view controller; this can also be done by Control-dragging
through these entries in the scene tree on the left. When we end the drag and
release, Interface Builder infers that we want to make a segue to the new view
controller, triggered by a tap on the button, and shows a pop-up asking what
kind of segue to create. This time, choose Present Modally.
Notice that this icon is different from the icon for the push segue we used
earlier. And there’s another thing to notice: the new view controller doesn’t
show a navigation bar. That’s because modal navigation is different. Showing
another view controller modally doesn’t require a navigation controller like a
“show” presentation does, so we’re always free to create this kind of transition
in a storyboard. This is handy because we often need to do something
modally, meaning we need to stop users to either show them something or
get some input from them before we continue.
On the other hand, no navigation bar means no back button, and we’ll have
to deal with that eventually.
While we’re thinking about the segue, click its icon and bring up the Attributes
Inspector, so we can give it an identifier string: showUserDetailSegue.
For this view, we’ll just have an image of the user, and then several labels on
successive lines: their real name, screen name, location, and so on. We could
lay these out with autolayout, carefully setting their spacing relative to each
other and to the side margins. But for such simple layouts, iOS 9 provides a
simple option: the stack view. The stack view is a container view that holds
other views. It lays out its subviews either vertically or horizontally, and
manages the spacing between them. Basically, all we need to do is decide
whether the subviews should be left-, center-, or right-aligned, and then just
add them to the stack view, one by one.
Start by finding the vertical stack view icon (shown in the figure)
and adding it to the new scene. This will be the only direct child
of the view, and thus it needs some layout constraints. Use the pin
popover to give it a top constraint of 0 to the superview, and leading
and trailing constraints of 0 to the margins. Don’t choose Update Frames to
force a layout just yet, because with no contents the stack view will assume
it has a height of zero. Even if you do, it’s not that bad, since we can still add
children by dragging them to the scene list.
Select the stack view and bring up the Attributes Inspector (ED4). Set the
alignment to Center so that any subviews we drop into the stack view are
centered within it.
Now drag and drop the following subviews into the stack view, customizing
as you go:
It’s nice not having to fiddle with lots of constraints for every view, right?
When finished, the layout should look like the following figure:
Now we want to tie this into code, so we’ll need a custom view controller, like
we did before with the tweet details scene. In the File Navigator, create a group
called User Detail VC. Within that group, create a new file, using the iOS
Cocoa Touch Class template, calling it UserDetailViewController and making it a
subclass of UIViewController, written in Swift.
Back in the storyboard, select the view controller icon for this scene (from
the scene list on the left or the scene’s top title bar), and use the Identity
Inspector (ED3) to set the class to UserDetailViewController. That will allow us to
bring up the Assistant Editor (making sure that UserDetailViewController.swift is in
the right pane) to Control-drag outlets for the image view and all the labels.
When done, the properties should look like this:
navigation/PragmaticTweets-11-3/PragmaticTweets/UserDetailViewController.swift
@IBOutlet weak var userImageView: UIImageView!
@IBOutlet weak var userRealNameLabel: UILabel!
@IBOutlet weak var userScreenNameLabel: UILabel!
@IBOutlet weak var userLocationLabel: UILabel!
@IBOutlet weak var userDescriptionLabel: UILabel!
Since we want to update the view whenever it appears, we’ll make our Twitter
call in viewWillAppear(). To get the user details, we’ll use Twitter’s users/show.json
request, which takes just a screen_name parameter. As before, we’ll fire off a
sendTwitterRequest() to make our request:
navigation/PragmaticTweets-11-3/PragmaticTweets/UserDetailViewController.swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
guard let screenName = screenName else {
return
}
let twitterParams = ["screen_name" : screenName]
if let twitterAPIURL = NSURL (string:
"https://api.twitter.com/1.1/users/show.json") {
sendTwitterRequest(twitterAPIURL,
params: twitterParams,
completion: { (data, urlResponse, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
This is just like the other calls we’ve made to sendTwitterRequest(), except for
having a different URL and parameters. As before, we’ll parse the result in a
new handleTwitterData() method.
navigation/PragmaticTweets-11-3/PragmaticTweets/UserDetailViewController.swift
func handleTwitterData (data: NSData!,
urlResponse: NSHTTPURLResponse!,
error: NSError!) {
guard let data = data else {
NSLog ("handleTwitterData() received no data")
return
}
NSLog ("handleTwitterData(), \(data.length) bytes")
do {
let jsonObject = try NSJSONSerialization.JSONObjectWithData(data,
options: NSJSONReadingOptions([]))
guard let tweetDict = jsonObject as? [String : AnyObject] else {
NSLog ("handleTwitterData() didn't get a dictionary")
return
}
userRealNameLabel.text = (tweetDict["name"] as! String)
userScreenNameLabel.text = (tweetDict["screen_name"] as! String)
userLocationLabel.text = (tweetDict["location"] as! String)
userDescriptionLabel.text = (tweetDict["description"] as! String)
if let userImageURL = NSURL (string:
(tweetDict["profile_image_url_https"] as! String)),
userImageData = NSData(contentsOfURL: userImageURL) {
self.userImageView.image = UIImage(data: userImageData)
}
} catch let error as NSError {
NSLog ("JSON error: \(error)")
}
}
The last thing we need to do is to set the screenName property. That’s something
the second view controller (tweet detail) will do as it begins the segue to the
Exit Segues
There are a few ways we could implement the back button, but the most
generally useful is the exit segue. With an exit segue, we can go backward in
a navigation, regardless of whether we came by way of push or modal segues.
What’s tricky about exit segues is that they don’t appear on the storyboard
the same way push or modal segues do. Instead, their existence is implicit.
We can only perform an exit segue if a previous view controller has exposed
a method for us to come back to. These methods, commonly called unwind
methods, have to follow a certain signature—they take a UIStoryboardSegue
parameter and have the @IBAction annotation—but they don’t have to have any
code—they just have to exist.
We want our user detail view controller to unwind to the tweet detail view
controller, so write the following method in TweetDetailViewController.swift:
navigation/PragmaticTweets-11-4/PragmaticTweets/TweetDetailViewController.swift
@IBAction func unwindToTweetDetailVC (segue: UIStoryboardSegue) {
}
An unwind method needs to have the @IBAction annotation (so we can make
connections to it in the storyboard), needs to take a UIStoryboardSegue as a
parameter, and should return nothing. This particular unwindToTweetDetailVC()
implementation does nothing, but if we did need to collect data from the
other view controller, this would be a great place to do so. In a sense, the
unwind method is a counterpart to the prepareForSegue() method, in that the
prepare method can send data to a view controller that we’re transitioning
to, and the unwind method can get data from it when it’s done.
Now run the app, drill down to the user details, and tap Done. The modal
transition unwinds and we’re back at the tweet details view controller. All
this with no code…well, no code that does anything, anyway. We could put
an NSLog() in the unwindToTweetDetailViewController() method to see that it’s being
called.
Perhaps more interestingly, we can unwind to any earlier view controller. For
example, if we go to RootViewController, write an unwindToRootViewController() there,
and connect to an exit segue that uses that method instead, our Done button
would take us all the way back to the root view controller, skipping over the
tweet detail view controller entirely. This can be immensely helpful in complex
storyboards where our navigation controllers get four or five view controllers
deep, and we find the user may want a nice “start over” or “go home” button;
exit segues make this really easy.
Programmatic Segues
It’s possible to perform segues programmatically, which can be useful if we
have a long-running action that should perform a segue when it’s completed,
like a login screen dismissing itself when a remote server sends us a response
that the password has been accepted.
This is how many popular apps work: navigating forward and backward
through view controllers, each specific to some part of the app’s overall
functionality. From here, we can add any new features we might think of.
In the next chapter, we’re going to look at another way of managing multiple
view controllers, one that’s particularly well suited to the iPad.
But, come to think of it, we haven’t tried running it on an iPad. And we did
make it a universal app in the beginning. So let’s see what that looks like.
Use the scheme selector in Xcode’s toolbar to change to a model of iPad—we
often use iPad 2, because as a non-Retina device it fits on the Mac screen—and
run the app. In landscape, it looks like this:
It’s…OK. Kind of. It’s not like any of the views are in the wrong place or any-
thing. And it works fine. It just doesn’t take any advantage of all the extra
room on the screen. In fact, it looks a lot like the Android screenshots that
speakers at Apple events used to use to demonstrate how stretching a phone
UI to a tablet screen doesn’t work.
So, let’s not do that. In this chapter, we’re going to take advantage of some
unique options that let us use the iPad screen to a better effect, while still
working the way we want on iPhone. We’ll adapt to larger screens—starting
with the iPad and then the iPhone 6s Plus—so that our app can have the best
of both worlds.
Apple calls this an adaptive user interface, one that adapts not only to the physical
factor of the device, but also to user preferences, like larger fonts for vision-impaired
users.
Several built-in apps on iOS use the split view. Mail shows message senders
and subjects in the left view and the message content on the right. Settings
has the master list of settings categories on the left and the UI for the selected
topic on the right. The split view lends itself well to this sort of a “master-
detail” metaphor: the main list of items is in a table on the left, and selections
in this table populate the contents of a detail view on the right.
Conveniently, this is also how our Twitter app works. We have a list of tweets,
and when we tap one of them, we bring up details on it in a new view. For
starters, let’s adapt our app to use the split view like this.
Post-drop, the default split view goes off in a couple of different directions,
as seen in the following figure. On the left, the split view controller scene has
one connection that goes up and right to a navigation controller, and from
there to a table view controller. The split view controller also has another
connection that goes down and right, to a plain and empty view controller.
We already have suitable view controllers to play both of these roles, so rather
than customizing the default scenes, we will delete them and replace them
with our own. Our RootViewController, the scene currently labeled Tweets, will
replace the default one, and our TweetDetailViewController will become the detail
scene. Here’s how we’re going to do that:
• Start by deleting the split view’s default table view controller, the one at
the upper right that says Root View Controller. We do this by selecting
the scene, or its view controller icon in the scene list, and pressing the
Delete key (J).
• The split view is going to control the relationship between the Tweets view
controller and the various detail view controllers, so we need to let it move
around the storyboard by breaking its existing connections. First, Control-
click our old initial navigation controller to bring up its connections HUD,
and click the X to break the connection to the root view controller. Next,
select the segue—the circle in the arrow between the Tweets scene and
the Tweet Details scene—and delete it as well.
• Now that it’s free, we can connect our Tweets table to the master portion
of the split view. Drag the Tweets scene up to the right of the split view’s
navigation controller. Control-click on the navigation controller and find
the connection called Root View Controller. Drag from this connection
over to the Tweets scene to make the connection.
• Now for the detail part of the split view controller. Delete the empty default
detail scene that came with the split view controller. Drag our original
navigation controller into the space that was just vacated. Control-click
the split view controller to see its connections. The Detail View Controller
connection is now empty; drag this to the Navigation Controller scene.
• But where does this navigation controller go? After all, we broke its con-
nection to the Tweets scene that it was originally connected to. Instead,
Control-click the navigation controller to bring up its connections HUD,
and connect the root view controller to the Tweet Detail View Controller
scene. Now the bottom half—the “detail” flow—of our split view controller
goes from Navigation Controller to Tweet Detail to User Detail.
• Finally, select the split view controller, bring up its Attributes Inspector
(ED4), and select the Is Initial View Controller check box so that the split
view will get to do its thing when the app comes up.
Wow! That’s a lot of clicky-draggy! Well, if nothing else, this should allay any
fears about deleting and reconnecting storyboard scenes. And if things ever
go truly bad, there’s always the Undo command. At any rate, the storyboard
should now look like the following figure, with the upper branch of the split
view going to a navigation controller and our Tweets table, and the lower
branch going to the Tweet Detail scene, and then on to User Detail.
Go ahead and run the app like this, with the scheme selector still set to some
flavor of iPad. In portrait, all we’ll see is the unpopulated detail view with its
empty labels for the username and tweet text. However, a left-to-right drag
gesture will reveal the master view, the list of tweets, on the left. Rotating the
Simulator to landscape (D← or D→) will cause the master list to always be
visible, as seen in the following figure.
So far, so good! The master view appears when it needs to in landscape and
can be brought up in portrait, and everything we did to populate the list of
tweets is still working as before.
There’s just one thing: tapping on the rows no longer does anything. Previously,
we had created a segue to connect the table to the Tweet Detail scene, which
gave us the navigation between scenes. But we deleted that segue, and now
there’s no way to send data between the scenes. So what do we do now?
The only thing these two view controllers have in common anymore is that
they’re both connected to the same UISplitViewController. As it turns out, that’s
exactly the key we need. The UIViewController class has an optional property,
splitViewController, defined as “the nearest ancestor in the view controller hierar-
chy that is a split view controller.”
Now let’s think about what we can do with that. The UISplitViewController has an
array property, viewControllers, that represents the child view controllers it
manages. So there should be two: a navigation controller in front of our
RootViewController, and another navigation controller in front of the TweetDetailView-
Controller.
bigscreens/PragmaticTweets-12-1/PragmaticTweets/RootViewController.swift
Line 1 override func tableView(tableView: UITableView,
- didSelectRowAtIndexPath indexPath: NSIndexPath) {
- let parsedTweet = parsedTweets[indexPath.row]
- if let splitViewController = splitViewController
5 where splitViewController.viewControllers.count > 1 {
- if let tweetDetailNav = splitViewController.viewControllers[1]
- as? UINavigationController,
- tweetDetailVC = tweetDetailNav.viewControllers[0]
- as? TweetDetailViewController {
10 tweetDetailVC.tweetIdString = parsedTweet.tweetIdString
- }
- }
- tableView.deselectRowAtIndexPath(indexPath, animated: false)
- }
This short method starts on line 3 by getting the ParsedTweet from our model
that corresponds to the clicked row, just like in the navigation segue case.
Next, we make sure the structure of the scenes is what we expect. Lines 4–5
test to see if the splitViewController property is non-nil (since we only need to do
these steps in a split-view scenario) and if the splitViewController has at least two
child view controllers, since we will need to work with the second one. Lines
6–7 check that the first VC in the detail flow—the bottom half of the split in
the storyboard—is a navigation controller, and that its root VC is a TweetDetail-
ViewController (lines 8–9).
If all of this works out, then line 10 assigns the tweetIdString property. As a
handy side effect, this kicks off the reloadTweetDetails() method called by the
didSet() property setter that we wrote back in Sending Data to the Second View
Controller, on page 185. In fact, this is the reason we needed to write that setter:
in the navigation case, we could always count on viewWillAppear() to call reload-
TweetData(), but in the split view scenario, the detail view will appear at launch
and just stay there, so we need to make sure that setting tweetIdString will
update the display.
Finally, outside all our storyboard-hierarchy logic, line 13 always runs at the
end of the method and deselects the tapped row so it doesn’t stay highlighted.
Run again and our selecting a tweet populates the detail view as expected.
With all the space afforded by the iPad, tweets that have images make partic-
ularly good use of the screen, as seen in the following figure.
Actually, though, the images introduce a small bug. Now that the detail view
is always visible, once we set an image in the image view, nothing ever un-
sets it. That wasn’t a problem in our navigation flow, which was creating and
populating a new detail view every time, but now if we click a tweet without
an image, the old one hangs around. We need to fix this in TweetDetailViewCon-
troller, in the part of handleTwitterData(urlResponse: error:) that sets the image:
bigscreens/PragmaticTweets-12-1/PragmaticTweets/TweetDetailViewController.swift
Line 1 if let entities = tweetDict["entities"] as? [String : AnyObject],
2 media = entities["media"] as? [[String : AnyObject]],
3 mediaString = media[0]["media_url_https"] as? String,
4 mediaURL = NSURL (string: mediaString),
5 mediaData = NSData (contentsOfURL: mediaURL) {
6 tweetImageView.image = UIImage(data: mediaData)
7 } else {
8 tweetImageView.image = nil
9 }
The only thing that’s new here is the trivial else block from lines 7–9. It says,
“if the tweet doesn’t have an image URL, nil out the image in the UIImageView.”
Why? The split view controller realizes there’s not enough space on the screen
for both view controllers, so it has switched into a navigation-like metaphor
for showing the two parts of the split on separate pages. This is actually a
handy feature, since it lets us use a split view controller for both iPad and
iPhone. Prior to iOS 8, we had to have completely separate storyboards for
iPhone and iPad. So this is a big win for building Universal apps—those that
run on iPhone and iPad—if only it did the right thing out of the box.
Notice that the back button at the top left says Tweets (the title of the master
view, which it gets from a navigation item), and we can tap it to go back to
the list of tweets. However, if we tap one of the tweets, it doesn’t populate the
detail view and take us to it. So we have two things to fix: we want to start
on the master view controller (the list of tweets) instead of the detail, and we
want tapping a table row to fill in the detail like it did in the old navigation
app, and in the iPad version of the split view.
All this does is check if there’s a splitViewController parent, just like we checked
when we handled the table row tap. If there is, we become its delegate.
Actually, removing the second view controller, the detail scene, is exactly
what we want. If we just return true, the split view controller will give up on
the detail view controller, leaving us with just the master view controller,
which is the list of tweets. So implement the method like this:
bigscreens/PragmaticTweets-12-2/PragmaticTweets/RootViewController.swift
func splitViewController(splitViewController: UISplitViewController,
collapseSecondaryViewController secondaryViewController: UIViewController,
ontoPrimaryViewController primaryViewController: UIViewController)
-> Bool {
return true
}
Run the app on one of the iPhone models now, and we come up on the list of
tweets.
But we can’t do that now, because there is no second view controller. We just
told the split view controller that it was OK to discard the second view con-
troller. So that’s just great.
Maybe we’ll just have to remake that view controller ourselves! Fortunately,
it’s pretty easy to do so with storyboards. There’s a UIStoryboard class that offers
just three methods, two of which are for creating scenes from within the sto-
ryboard. The one we need is instantiateViewControllerWithIdentifier(), which takes a
string and gives us back a UIViewController, with its view and all its subviews laid
out exactly like we created them in the storyboard.
For this to work, we need to give the Tweet Detail scene a unique ID string.
In the storyboard, select the Tweet Detail View Controller, and bring up its
Identity Inspector (ED3). In the Storyboard ID field, enter TweetDetailVC, as
shown in the following figure.
Perform a clean build (Product > Clean, or BDK), since changes to storyboards
aren’t always picked up by Xcode’s build process, and we want to make sure
this scene is findable by that string.
Now we can re-create this view controller when we need it. The place we’re
going to do so is in RootViewController’s tableView(didSelectRowAtIndexPath:). We want
to handle the case where the split view controller has only one child view
controller, so find the closing brace that matches if let splitViewController =
splitViewController where splitViewController.viewControllers.count > 1 {, and replace its
closing brace with the following else block.
bigscreens/PragmaticTweets-12-2/PragmaticTweets/RootViewController.swift
Line 1 } else {
2 if let storyboard = storyboard,
3 detailVC =
4 storyboard.instantiateViewControllerWithIdentifier("TweetDetailVC")
5 as? TweetDetailViewController {
6 detailVC.tweetIdString = parsedTweet.tweetIdString
7 splitViewController?.showDetailViewController(detailVC,
8 sender: self)
9 }
10 }
On lines 2–5, we check to see that we’re in a storyboard, that it has a scene
called TweetDetailVC, and that we can cast it to a TweetDetailViewController. If all
that works, then we’ve got our detail view controller, and its whole view hier-
archy, just as laid out in the storyboard. In turn, that means we can get it to
load its contents like we always have, by setting its tweetIdString (on line 6).
Then we just have to navigate to it. UISplitViewController gives us that ability with
the showDetailViewController() method, on lines 7–8.
And that’s it! Run the app on a simulated iPhone, and it works just like the
navigation version did from the previous chapter, perfectly well suited to the
small space of the iPhone. Back on the iPad, we get a side-by-side split that
makes better use of all the screen real estate. Best of both worlds, and with
the split view controller we get it all with one storyboard and this little bit of
tricky code.
Two things will help us here: the UISplitViewController has a UIBarButton that shows
its master view controller. We could add this on the fly, if we knew when to
do so. Fortunately, the UISplitViewControllerDelegate gives us that, too.
We’ll need to add the bar button in two places: when the UI first comes up,
and when the split view controller changes its display mode. So let’s stub out
an empty method in RootViewController for performing the fix:
func addShowSplitPrimaryButton(splitViewController: UISplitViewController) {
}
The first place we’ll call our helper method is in viewDidLoad(), right after we set
ourselves as the UISplitViewController’s delegate:
bigscreens/PragmaticTweets-12-2/PragmaticTweets/RootViewController.swift
if let splitViewController = splitViewController {
splitViewController.delegate = self
addShowSplitPrimaryButton(splitViewController)
}
The second time we need to call our helper is when we get rotated: we need
to show it in portrait orientation, which shows only the detail view controller,
but not in landscape, which shows both master and detail. A delegate method
called splitViewController(willChangeToDisplayMode:) helps us here. It passes in a UIS-
plitViewControllerDisplayMode, which is an enum whose values are .Automatic, .Primary-
Hidden, .AllVisible, and .PrimaryOverlay. When we rotate to portrait, this will get
called with the .PrimaryHidden value, and in landscape, it will be called again
with .AllVisible. So we need to call our helper when the primary view controller,
our list of tweets, becomes hidden:
bigscreens/PragmaticTweets-12-2/PragmaticTweets/RootViewController.swift
func splitViewController(svc: UISplitViewController,
willChangeToDisplayMode displayMode: UISplitViewControllerDisplayMode) {
if displayMode == .PrimaryHidden {
addShowSplitPrimaryButton(svc)
}
}
Now, for the main attraction: how do we actually get this button to show the
primary view controller? It’s available from UISplitViewController, via the method
displayModeButtonItem(), so getting it is easy. What to actually do with it is a little
trickier. Here’s the recipe:
bigscreens/PragmaticTweets-12-2/PragmaticTweets/RootViewController.swift
func addShowSplitPrimaryButton(splitViewController: UISplitViewController) {
let barButtonItem = splitViewController.displayModeButtonItem()
if let detailNav = splitViewController.viewControllers.last
as? UINavigationController {
detailNav.topViewController?.navigationItem.leftBarButtonItem =
barButtonItem
}
}
After we get the barButtonItem, the trick is what to do with it. Our detail view
controller has a navigation controller in front of it, which gives it a navigation
bar (and also the “Tweet” title in that bar). So what we’re doing here is asking
if the split view controller’s last view controller (the last member of its viewCon-
trollers array) is a UINavigationController. If it is, then we can ask for that navigation
controller’s first view controller, get its UINavigationItem, and set its leftBarButtonItem.
Run the app on an iPad simulator and rotate a few times. Whenever the screen
is in portrait orientation, there will now be a blue “back” chevron, as seen in
the following figure. Tap it to slide the list of tweets over the detail view. Now
it works just like the side-by-side presentation in landscape: select a tweet
from the list, and its contents pop up in the detail view. Also, tapping outside
the list of tweets (that is to say, in the detail view) dismisses the list of tweets,
but we can always get back to it with our handy left bar button.
Except, well, that the iPhone 6-series devices are really big. Maybe not iPad
big, but it at least makes you wonder whether it’s really appropriate to lump
all iPhones together. After all, iOS 9 runs on everything from the 4s (with its
320×480, 3.5-inch screen) to the iPhone 6s Plus (414×736, 5.5-inch screen).
Even within the iPhone range, there may be times we want to go to a side-by-
side mode with our split view.
To do so, we need to understand how iOS represents screen sizes and their
contents.
Size Classes
In iOS, screens, view controllers, and views all have a collection of sizing
information called a trait collection. These traits are collected by the UITraitCol-
lection class, and include things like the points-to-pixels scaling factor (2.0 for
Retina devices, 3.0 for the iPhone 6s Plus, 1.0 for the old iPad 2), the device
idiom (phone or pad), whether it supports 3D touch, and a very general way
to represent the available space in each dimension.
The available space is represented as a size class, and has two values: compact
and regular. Those should sound familiar, because they’re the values of the
sizing bar at the bottom of the storyboard pane, which we saw way back in
Managing an Object's Properties, on page 85. Let’s think back to the grid that
appeared in the pop-up, and the descriptions it provided: regular width and
height were described as an iPad, whereas compact width and regular height
represented an iPhone in portrait orientation.
Traits are inherited from the screen, to the one window that’s always on the
screen, through view controllers, and views, down to each individual view,
like a button or table. Along the way, they can be changed. So an iPad screen
will have regular width size class in either orientation, but the left side of a
split view will have compact width, since the layout of the split view constrains
how much space it can use.
The size classes are also used to support the new multitasking features in
iOS 9. To try it out, run the app in the Simulator for an iPad Air, Air 2, or
Pro, the devices that support iOS multitasking. Use the home button (BDH)
to go back to the home screen. Launch another app in the Simulator, like
Safari, and rotate to landscape. Drag a little bit from the right side of the
screen to enter multitasking mode. From here, you’ll see a list of apps and
their icons. Choose PragmaticTweets to open it in the right side of the multi-
tasking interface, running side-by-side with Safari, as seen in the figure.
Notice that as long as our side of the split uses only part of the screen, it is
considered to be in a horizontally compact size class, and so we get the iPhone-
style behavior of navigating from the list of tweets to their details. Only by
dragging out to consume the rest of the screen (and thus becoming the sole
foreground app) can we achieve our regular-width, two-pane split view.
Now let’s use size classes for our own purposes. When an iPhone 6-series
device is in landscape, there’s surely enough room to do the two-pane split
view, rather than just have table rows stretch across the screen. So let’s do
that. The trick to do this is simple: we have to convince the split view that it
has regular width to work with, not compact. Let’s do that.
Container Controllers
We can’t just tell the split view controller that it has regular width: it has to
inherit this from a parent view or view controller. We can do that by creating
a container controller, a view controller that contains other view controllers.
This parent will own the split view controller and will be in a position to give
it a trait collection that says it has regular width, if we decide we want to go
side by side.
To get started, go to the storyboard, look through the Object library (CED3),
and find the plain View Controller icon, a yellow ball with a dashed box inside
it. Drag this icon to the left of the split view controller.
The way we get this view controller to “own” the split view controller
is pretty weird. We need to use a container view, instead of the
usual UIView that comes with a view controller. So, in the scene list,
find the view that’s a child of this new view controller and delete
it. This will also delete the top and bottom layout guide objects from the scene.
Now, in the Object library, find the Container View icon, shown in the figure
as a gray box inside a white box inside a gray box. Drag it onto the new view
controller icon or its box in the storyboard; it will become the sole child of
the view controller.
This also adds a new view controller scene to the storyboard, connected by
a segue. What’s interesting is that this isn’t a segue that transitions between
scenes; it’s an embed segue that tells the container scene which view controller
to show first. So it’s great that Xcode gave us a default view controller to
embed, except we don’t want that one; we want to embed the split view con-
troller.
So, Control-click on the container view to show its connections HUD. The
first one will show that viewDidLoad() has an embed segue connected to that
blank view controller. Drag from the circle in this connection to the split view
controller. This will allow us to make a segue connection. At the end of the
drag, choose Embed as the segue type. We should always name our segues,
so click the segue icon between the two scenes, bring up its Attributes
Inspector (ED4), and give it the storyboard identifier embedSplitViewSegue.
Now the empty view controller that the container view supplied for us has no
connections at all, so we can delete it.
This scene is going to be the beginning of our app, so choose the view con-
troller, go to the Attributes Inspector (ED4), and select the Is Initial View
Controller box. This won’t visually change anything, since control will flow
immediately to the split view controller child. But it will ensure that this view
controller loads first, which we need. The beginning of the storyboard should
now look like the figure on page 215.
Now we’re at the point where we need to write some code, to grab the reference
to the split view controller and be able to change its trait collection. In the
File Navigator, select the Pragmatic Tweets group and use New Group to create
the group Size Class Override. Within this group, choose New File to create
a new Cocoa Touch Class, with the name SizeClassOverrideViewController, a subclass
of UIViewController written in Swift. Finally, in the storyboard, select the view
controller with the container view, bring up its Identity Inspector (ED3), and
change the class to SizeClassOverrideViewController. Now the container view controller
will be our custom class.
What we need to do with this code is grab a reference to the split view con-
troller, and send it our preferred size class traits. In SizeClassOverrideViewCon-
troller.swift, start by creating a property to refer to the UISplitViewController.
bigscreens/PragmaticTweets-12-3/PragmaticTweets/SizeClassOverrideViewController.swift
var embeddedSplitVC: UISplitViewController!
As implied by the name of the embed segue, our one look at the split view
controller happens when the view loads. This will make a call to prepare-
ForSegue()—just like when we’re navigating between scenes—so we override
that method to grab the reference to the destinationViewController.
bigscreens/PragmaticTweets-12-3/PragmaticTweets/SizeClassOverrideViewController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embedSplitViewSegue" {
embeddedSplitVC = segue.destinationViewController
as! UISplitViewController
}
}
Now we’re in a position to start changing the split view’s sense of how much
room it has to work with!
So, assuming that we don’t want to try to go side by side on the 4s, and we
definitely want to do so on the big iPhone 6 models, let’s pick a value in the
middle. We’ll just say that any width bigger than 480 is enough for us to try
side by side.
When the device is rotated, view controllers get a callback to the method
viewWillTransitionToSize(). That’s the perfect place to pull our trickery. We’ll override
that method, check the size we’re transitioning to, and if it’s wide enough for
us, we’ll override the split view controller’s trait collection. Here’s how we do
that.
bigscreens/PragmaticTweets-12-3/PragmaticTweets/SizeClassOverrideViewController.swift
Line 1 override func viewWillTransitionToSize(size: CGSize,
- withTransitionCoordinator coordinator:
- UIViewControllerTransitionCoordinator) {
- if size.width > 480.0 {
5 let overrideTraits = UITraitCollection (
- horizontalSizeClass: .Regular)
- setOverrideTraitCollection(overrideTraits,
- forChildViewController: embeddedSplitVC!)
- } else {
10 setOverrideTraitCollection(nil,
- forChildViewController: embeddedSplitVC!)
- }
- }
We look at the incoming width on line 4. If it’s greater than 480.0, lines 5–6
create a new UITraitCollection. The initializers for this class take either one of the
four traits—horizontalSizeClass, verticalSizeClass, userInterfaceIdiom, or scaleFactor—or an
array of already initialized trait collections to merge together. All we care about
is setting the horizontalSizeClass to the enum value UIUserInterfaceSizeClass.Regular.
Then all we have to do is pass this to our embeddedSplitVC. A parent view con-
troller can override a child’s trait collection with setOverrideTraitCollection(), which
is what we do on lines 7–8. This is only possible from a parent view con-
troller—other VCs can’t go changing each other’s trait collections willy-nil-
ly—which is why we had to go through the whole rigmarole of setting up our
custom container controller.
Finally, if our width isn’t big enough for the split view controller to go into
side-by-side mode, we use setOverrideTraitCollection() with nil, on lines 10–11, which
lets it inherit its traits as before.
With our sneaky override of the size class now complete, run the app again
on different models in the Simulator (keeping in mind you’ll have to sign into
Twitter on each one if you haven’t already, as they store their system settings
separately from each other). On a sufficiently large device, the split view
controller will now go into side-by-side mode when rotated to landscape, as
seen in the following figure.
So, we’ve done some work on how users see our app. Now it’s time to enhance
how they interact with it. In the next chapter, we’ll look at how to create and
handle touch gestures on our own, so our app won’t just look great, it will
literally feel great, too.
Gesture Recognizers
The first versions of the iPhone SDK gave us only low-level raw touch data
via the UIView methods touchesBegan(), touchesMoved(), touchesEnded(), and touchesCan-
celled(). These delivered sets of UITouch events, and from the raw geometry and
timing of these events, we could track events like swipes, using logic like “if
the touch moved at least 50 points up, and not more than 20 points to either
side, in less than 0.5 seconds, then treat it as an upward swipe.”
As one might expect, this was a huge pain in the butt to implement and led
to variations in user experience as different developers interpreted the touch
data differently, based on what “felt right” to them.
Segue Gestures
One handy trick for our Twitter app would be to give the user a better view
of a given tweeter’s avatar. From the user detail screen we built in the last
chapter, we could go to a new screen that shows the image in a larger view,
and allow our user to pinch-zoom and move around the avatar in detail.
To do this, we’ll need a new “user image detail” scene in the storyboard. Find
the View Controller icon in the Object library, and drag it into the storyboard,
to the right of the User Detail View Controller scene that is currently the end
of our storyboard. To this new scene, add the following:
• An image view, with width and height pinned to 280 points, vertically and
horizontally aligned in its container
• A button, with the title Done, pinned 20 points up from the bottom of the
container, horizontally aligned in the container
We’ll put some logic into that scene later, but for now, we just need to create
a way to get to it from the user detail scene. We could do that by replacing
the detail scene’s image view with a button and then adding a segue on the
button tap. But to show how flexible gesture recognizers are, we’ll do function-
ally the same thing by giving the existing image view the ability to handle
taps, thereby turning it into a de facto image button.
Scroll through the Object library and find the gesture recognizer
icons. They’re displayed as blue circles against dark gray back-
grounds, some with swooshes that represent movement. Find the
tap gesture recognizer, which is represented as a single static circle, shown
in the figure.
So it’s not that the recognizer refers to the view; instead, the view knows that
the recognizer is one of its potentially many gestureRecognizers. Now let’s address
the question of the what the recognizer does when it’s tapped. In the Connec-
tions Inspector, we see a few interesting properties: a triggered segue action,
a delegate outlet, and a sent action selector. The delegate doesn’t help us here:
the UIGestureRecognizerDelegate is meant to let our code adjudicate when two
gesture recognizers want to handle the same gesture. What’s useful for us
are the selector, which calls a method when the gesture begins, ends, or
updates, and the segue action, which takes us to a new scene.
We can try running now, choosing a tweet, and drilling down to user details,
but clicking the image won’t perform this segue yet. To see why, select the
image in the user detail scene, and bring up the Attributes Inspector (ED4).
Notice that User Interaction Enabled is unchecked, since image views by
default don’t handle user input. But this means that it won’t process touch
events, which in turn means our gesture recognizer will never fire. Simple fix
here: just check the User Interaction Enabled box.
Run the app now and we can drill all the way to our new scene, which at this
point only shows a Done button, since we haven’t populated the image view
yet. Moreover, the Done button doesn’t work, and we’re trapped on this scene.
Let’s fix that before we move on. The fix is to use an unwind segue. Back in
UserDetailViewController.swift, add an empty implementation for unwindToUserDetailVC():
gestures/PragmaticTweets-13-1/PragmaticTweets/UserDetailViewController.swift
@IBAction func unwindToUserDetailVC (segue : UIStoryboardSegue) {
}
Now, we can go to the image detail scene in the storyboard, and Control-drag
from the Done button to the orange Exit Segue button. At the end of the drag,
we have two methods we can unwind to: choose the unwindToUserDetailVC() method
we just created. Run again, and we can go back from the image detail scene.
But we’re just getting started. There’s a lot more we can do to the default
image view.
In the File Navigator, create a new group called User Image Detail VC, and
within that, use New File to create a new class UserImageDetailViewController, a
subclass of UIViewController. At the top of this new UserImageDetailViewController.swift
file, declare a property for the user image URL:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
var userImageURL: NSURL?
We’ll set that property every time we follow the segue to the new scene, so we
have some work to do in the storyboard. First, select the image detail scene’s
view controller icon (either from the frame below the scene or in the scene’s
object list), go to the Identity Inspector (ED3), and change the class to UserIm-
ageDetailViewController. Since we will need to know when we’re taking the segue
to this scene, select the segue, bring up the Attributes Inspector (DE4), and
set the identifier to showUserImageDetailSegue.
Now we’re going to be able to set the image URL when we segue to the new
scene. We do this back in UserDetailViewController.swift, where we will need to save
the URL of the image. Right now, the user detail scene just creates a UIImage
to populate this class’s image view, but there’s a good reason it should save
off the URL: it will let us get a higher-quality image. Currently, it uses the
key profile_image_url_https to get an image URL from the Twitter response. The
value is a URL string like https://pbs.twimg.com/profile_images/290486223/pp_for_twitter_nor-
mal.png. As it turns out, that _normal is used by Twitter to indicate an icon at a
standardized 48×48 size. That’s OK for the user detail view controller, but it
will be very blocky in the 280×280 image view in the next scene. Fortunately,
if we just strip the _normal, we can get the image in the original size uploaded
by the Twitter user, and that will look nicer in the next scene. So start by
giving the UserDetailViewController this new property:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserDetailViewController.swift
var userImageURL: NSURL?
You’re not seeing double; that’s the same property we previously added to
UserImageDetailViewController. We want to save the URL in this view controller, and
pass it to the next one. To save it here, down in handleTwitterData(), inside the
closure, change the last few lines (after the userDescriptionLabel.text = (tweet-
Dict["description"] as! String) line) so they save this property and use it to create
the userImageView.image, rather than use a local userImageURL variable.
gestures/PragmaticTweets-13-2/PragmaticTweets/UserDetailViewController.swift
if let userImageURL = NSURL (string:
(tweetDict["profile_image_url_https"] as! String)),
userImageData = NSData(contentsOfURL: userImageURL) {
self.userImageURL = userImageURL
userImageView.image = UIImage(data: userImageData)
}
Now we’re ready to send the good version of the user image URL to the UserIm-
ageDetailViewController by writing a prepareForSegue:() method:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserDetailViewController.swift
Line 1 override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
- if let imageDetailVC = segue.destinationViewController
- as? UserImageDetailViewController,
- userImageURL = userImageURL
5 where segue.identifier == "showUserImageDetailSegue" {
- var urlString = userImageURL.absoluteString
- urlString = urlString.stringByReplacingOccurrencesOfString("_normal",
- withString: "")
- imageDetailVC.userImageURL = NSURL(string: urlString)
10 }
- }
We begin with a big if let where that verifies that the destination view controller
is of the correct class (lines 2–3), that the userImageURL property is non-nil (line
4), and that the segue has the correct identifier (line 5). If all that is true, we
can then get a String version of the URL (line 6), and strip out the _normal sub-
string (lines 7–8). Finally, we make a new NSURL for the full-size image and
send it to the user image detail view controller on line 9.
Once the segue is performed, the UserImageDetailViewController will have the URL
for the full-size image. Now all we need to do is to populate the image view in
that scene. Start by going to the storyboard, going to the last scene (the User-
ImageDetailViewController), and selecting the 280×280 image view. Bring up the
Assistant Editor (the “linked rings” toolbar button, or EDF), with UserImageDe-
tailViewController.swift in the right pane, and Control-drag from the image view in
the storyboard to somewhere inside the class (perhaps right after the userImageURL
we created), to create an outlet that we’ll name userImageView.
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
@IBOutlet weak var userImageView: UIImageView!
Now that we can see the image view in code, go back to the standard editor,
visit UserImageDetailViewController.swift, and add a viewWillAppear() method:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let userImageURL = userImageURL,
imageData = NSData (contentsOfURL: userImageURL) {
userImageView.image = UIImage(data: imageData)
}
}
Run the app now, and we can navigate all the way to the image detail scene,
which will show the higher-quality user image and not the 48×48 icon. As
seen in the figure on page 225, we’ve drilled down for a look at Janie’s Twitter
avatar.
That’s a nice, normal-looking image for now. But we’re about to start letting
our fingers have some fun with it.
Let’s take a look at what gesture recognizers give us. Here’s a table summa-
rizing the concrete subclasses of UIGestureRecognizer and the important properties
and/or methods exposed by each:
As we look at the names of the gesture recognizers, we can start to get some
ideas: UIPanGestureRecognizer handles dragging a finger around, so we can use
that to move the image around. The UIPinchGestureRecognizer seems like it would
be a natural for pinch-to-zoom functionality. So it looks easy enough to rec-
ognize the gestures we want. Question now is: what do we do with it? How is
a scale or translationInView() going to help us change the appearance of the image
view?
Affine Transformations
The properties and methods provided by the gesture recognizers work well
with a trait common to all graphic objects in iOS: affine transformations. A
• The page is shrunk down (scaled) by about 50% (well, for a US letter page
anyway…legal or A4 would have slightly different math).
• The page is moved (translated) so that odd pages are left-aligned against
the edge of the portrait page, and even pages are left-aligned approximately
along the center fold of the page.
For our purposes, every UIView has a transform of type CGAffineTransform. This is a
struct, not a class, and consists of just six CGFloats: a, b, c, and d, tx, and ty.
These six values represent any combination of rotation, scaling, skewing, and
translation (movement) operations. Technically, they represent six members
of the matrix in the following equation.
[ ]
a b 0
, ,
[x y ]
1 = [ x y 1 ] × c d 0
tx ty 1
What this equation provides is the transformed values for any point, x' and
y', given their original values (x and y) and the contents of the affine transform
matrix. This works out to a pair of simple equations:
x’ = ax + cy + tx
y’ = bx + dy + ty
Notice that the tx and ty values stand alone as terms in the equations. These
are the “transform” values. If a, b, c, and d are all 1.0, then tx and ty can be
used directly along the x- and y-axes.
On the other hand, if we only work with a, b, c, and d, we can easily scale an
object while maintaining its aspect ratio: if we set a and d to 2.0, then every
coordinate value will double, and this transform will represent doubling the
size of an object. Or we can use sines and cosines to represent rotation. Or
we can use all the terms to combine scaling, rotation, and translation.
To make use of these transforms, we have a few options. UIView has a transform
property, so we can set that directly. The underlying CALayer that provides the
view’s appearance also has a transform property, although that one is of type
CATransform3D and works in three dimensions. A more advanced option would
be to write our own subclass of UIView or CALayer that draws its own contents;
the Core Graphics library used for drawing allows us to set an affine transform
on our drawing operations, and is the only way to use multiple transforms.
To keep it simple for now, we’ll just reset the UIImageView’s transform property,
which it inherits from UIView.
Now we need to give the recognizer a method it can call. Select the pan gesture
recognizer in the scene’s object list, and switch to Assistant Editor (EDF),
making sure UserImageDetailViewController.swift is in the right pane. Control-drag
from the gesture recognizer (either in the scene’s object list or from the bar
atop the scene), to any free space inside the class, perhaps down by the closing
curly brace. At the end of the drop, a pop-up asks for what kind of connection
to make—be sure to change from Outlet to Action—and for a name for the
action method. Let’s call it handlePanGesture(). Also, before clicking Connect,
change Type from the default AnyObject to UIPanGestureRecognizer.
This connection will call handlePanGesture() when a pan gesture starts, updates,
or ends on the image view. At least it would, if image views processed touch
events by default. Just as with the image in the previous view controller, we
have to explicitly enable user interaction with this image view to make it
respond to touch events. Switch back to the storyboard’s standard editor,
select the image view, bring up its Attributes Inspector, and select the User
Interaction Enabled check box.
For this to work, we need to understand what the gesture recognizer tells us.
If we look up translationInView() in the documentation for UIPanGestureRecognizer, we
find it returns “a point identifying the new location of a view in the coordinate
system of its designated superview.” There’s also an important note in the
discussion of the method:
The x and y values report the total translation over time. They are not delta values
from the last time that the translation was reported. Apply the translation value
to the state of the view when the gesture is first recognized—do not concatenate
the value each time the handler is called.
we should plan on saving the image view’s transform the first time we get
called. Define that as a property up in the top of the class:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
var preGestureTransform: CGAffineTransform?
Now we can assign that property the first time we’re called back by the gesture
recognizer. When we’re called back, we can ask the gesture recognizer for its
state, which can be started, changed, ended, canceled, or a few other admin-
istrative and error states. When the value is UIGestureRecognizerState.Began, we’ll
save off the initial transform of the image view. Begin the handlePanGesture:() like
this:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
@IBAction func handlePanGesture(sender: UIPanGestureRecognizer) {
if sender.state == .Began {
preGestureTransform = userImageView.transform
}
When a pan gesture begins, this if block saves the image view’s transform to
our preGestureTransform property, since all subsequent event coordinates will be
relative to this initial transform. Now we’re ready to handle moving the view
around. So, finish up handlePanGesture() with a second if, as follows:
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
Line 1 if sender.state == .Began ||
2 sender.state == .Changed {
3 let translation = sender.translationInView(userImageView)
4 let translatedTransform = CGAffineTransformTranslate(
5 preGestureTransform!, translation.x, translation.y)
6 userImageView.transform = translatedTransform
7 }
8 }
Does this work? Try it. Drill down to an user image detail and try dragging
the picture around. You should have total freedom to put it wherever you
like, even under the Done button or partially offscreen, as seen in the following
figure. Pretty cool, but we should clean up after ourselves before we go further.
Let’s give ourselves a “panic button”: if the user double-taps the image, it’ll
go back to its default position.
In the storyboard, add a new tap gesture recognizer to the image view. Select
the tap gesture recognizer icon from the scene’s object list or the title bar atop
the scene, bring up the Attributes Inspector, and set the number of taps to
2. This means it will take a double-tap for the recognizer to fire.
Next, switch to Assistant Editor, and Control-drag from the tap gesture rec-
ognizer into UserImageDetailViewController.swift to create a new action method. When
the pop-up appears at the end of the drag, call the method handleDoubleTapGes-
ture(), and switch the type from AnyObject to UITapGestureRecognizer.
through the earlier formulas and we find that makes x' equal x and y' equal
y. This “do nothing” is provided to us as the constant CGAffineTransformIdentity.
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
@IBAction func handleDoubleTapGesture(sender: UITapGestureRecognizer) {
userImageView.transform = CGAffineTransformIdentity
}
Restoring the identity transform on a UIView is a one-line call. Run the app,
drag the image around, and double-tap to send it back to where it started.
Easy peasy!
Back in the storyboard, go to the Object library and locate the pinch
gesture recognizer icon. As before, drag it on to the image view to
add it to the scene. Switch to the Assistant Editor with UserImageDe-
tailViewController.swift in the right pane, select the icon in the scene or the title
bar, and Control-drag to create a new action method. Name the action han-
dlePinchGesture() and change the parameter type to UIPinchGestureRecognizer.
What does the pinch gesture’s scale give us? According to the docs, it’s “the
scale factor relative to the points of the two touches in screen coordinates.”
And, as was the case with the pan recognizer, this value is relative to the
beginning of the gesture, not to the last time we were called. So, once again,
we need to make use of the preGestureTransform to hold on to our initial value.
gestures/PragmaticTweets-13-2/PragmaticTweets/UserImageDetailViewController.swift
Line 1 @IBAction func handlePinchGesture(sender: UIPinchGestureRecognizer) {
- if sender.state == .Began {
- preGestureTransform = userImageView.transform
- }
5 if sender.state == .Began ||
- sender.state == .Changed {
- let scaledTransform = CGAffineTransformScale(
- preGestureTransform!, sender.scale, sender.scale)
- userImageView.transform = scaledTransform
10 }
- }
As with the pan recognizer, we use the start state to save off the image view’s
initial transform, on lines 2–4. Then on lines 5–6, we deal with the scale value
Subview Clipping
Thanks to the natural pairing of the gesture recognizers and the affine
transforms, we’ve added the dragging and pinch-zooming functionality that
will be familiar to our users from many other apps they use. However, views
that are allowed to just sprawl all over the screen aren’t something we usually
see on iOS. It may be fun, but it feels wrong, and we hardly want to let the
user make a mess of our user interface.
Let’s rein in the madness a little bit. We’ll put the image view into another
view, and have it clipped off at that view’s edges. That will put an end to
sliding the image offscreen or under the Done button.
In the storyboard, select the image view and delete it (with the Backspace key
or the Edit > Cut menu item). Notice that the three gesture recognizers survive
this, because they are top-level objects in the scene, and not children of any
view or view controller.
From the Object library, find the plain view (the popover will show its class
as UIView), and drag it to the middle of the scene’s main view. Use the autolay-
out popovers to pin its width and height to 280 and to horizontally and verti-
cally align it in the container. Then go to its Attributes Inspector and check
the Clip Subviews box. What this does is to constrain (“clip”) drawing to the
bounds of the view, so if the contained image view goes beyond those
boundaries, it will just get cut off.
Next, drop an image view into this subview. It should allow itself to fill the
parent subview; one way to make this work for sure is to drag the image view
onto the subview’s entry in the scene list, rather than onto the storyboard
layout. Like its parent, create autolayout constraints pin its size to 280×280
and horizontally and vertically align it in its containers.
Time to fix our connections. Select the view controller from the scene members
list and open the Connections Inspector (ED6). The userImageView is no longer
connected, because we deleted the object it was connected to. Drag from that
connection’s circle to the new image view to make a new connection.
Try running the app and drill down to the image detail view. When we drag
around, any part of the image that goes beyond the bounds of the 280×280
view is simply cut off, as shown in the figure on page 234. We can also perform
our gestures anywhere in the view, not just on the image view, and have them
recognized. That means we can push the image entirely outside the bounds
of the container view, but we can also bring it back with a double-tap anywhere
in that view.
Apple suggests developers use 3D Touch for “peek” and ”pop” gestures, which preview
an item’s contents without fully opening it. For example, we could add a hard-press
gesture to user icons on the table of tweets, so we could pop up a little preview of a
user’s info without having to drill through two scenes to the user’s detail scene.
We’ve chosen not to create an example for 3D Touch, because the support in iOS 9
is pretty limited so far. In particular, it is not supported in the iOS Simulator, so the
only way to try it out is to actually get one of the new iPhone models. Still, it’s worth
looking at how it works.
None of the gesture recognizers know anything about 3D Touch. Instead, the only
access we have is through the UITouch objects delivered to touchesBegan(), touchesMoved(),
and touchesEnded(), the low-level methods we dismissed at the beginning of the chapter
as “a pain in the butt.”
To detect these touches, we would need to subclass UIView, and check its traitCollection
for a trait called forceTouchCapabililty (notice that the APIs use the term “force touch”
rather than the marketing name “3D Touch”). If force touch is available, an override
of touchesBegan() could go over the Set of UITouches it receives and measure their force
property. This value indicates how hard the touch was, from 1.0 for a “normal” touch
up to a predefined maximumPossibleForce.
If the code determines the force is strong enough, it should then take some appropriate
action, like showing a menu. However, good MVC says that kind of application logic
doesn’t go in the view, but rather in a controller. So we’d need a way to have a view
controller handle the force touch event, like by having the view call back to the con-
troller (the familiar delegate pattern), or having the controller give the view a closure
to run whenever it gets a force touch.
Maybe iOS 10 will have gesture recognizers that provide the force of their touches.
For now, handling 3D Touch is possible but kind of a hassle.
Fortunately for us, the iOS SDK offers access to the user’s photo library,
meaning we can get pictures taken with the Camera application, as well as
add new photos to the library. In addition, the Core Image framework gives
us powerful tools to edit the contents of images, so we can improve photos
long after they’ve been taken.
It has never been easier to customize your photos on the iPhone. We are just
going to scratch the surface of all the awesome things that Apple gives you
to express yourself and capture life’s moments.
Model objects contain data, and they provide access to and implement logic
on data. Photos and videos are just sets of data that we store on our phone
that can be accessed and modified by many different applications. Our photo
model objects are also read-only and thread-safe, so no worrying about
another application coming in and changing our photos out from under us.
Every photo and video we use is also considered an asset. Individual assets
belong to collections. These are the building blocks of how our photographs
are organized; it’s important to remember this structure when working with
PhotoKit.
For example, say you take a family vacation to Disney World every year. You
want to find a specific picture in your photo library, but you aren’t sure which
trip it was that you took the picture on. You can filter the photos by location
and whether they’re tagged as a favorite to narrow down which photo it might
be. If you know which trip it was on, you can filter them down further by
creation date.
Go to the Object library and choose a new Bar Button item to add to the sto-
ryboard. Locate the Tweets scene, the one with the table view, and add the
button to the top left. Over in the Attributes Inspector, you will have the option
from the drop-down menu to set the button to a camera icon.
Open the Assistant Editor and connect the button to an IBAction in the
RootViewController.swift to create a method that handles the button click.
photos/PragmaticTweets-14-1/PragmaticTweets/RootViewController.swift
@IBAction func handlePhotoButtonTapped(sender: UIBarButtonItem) {
}
One last thing we need to do in the root view controller before we move on is
import the Photos framework. Scroll to the top of the file and add the following:
photos/PragmaticTweets-14-1/PragmaticTweets/RootViewController.swift
import Photos
We just want to share photos with our Twitter followers, so let’s go ahead and
set up our fetch request to go and get access to our photos.
Fortunately, the Simulator’s Photos app comes with some built-in nature pictures
you can use for this chapter. Furthermore, there are ways to add your own pictures
to the Simulator’s photo library. One functionality the Simulator has is the ability to
surf the web. When you open your Safari app, you can search the web for images.
To save the photos to your photo library, simply press and hold on the image you
wish to download. You will see a pop-up asking you if you want to save the photo to
your photo library. Piece of cake, which is a lie.
And if you already have image files on your Mac, it’s even easier: just drag and drop
them to the iOS Simulator app while it’s running, and they’ll be added to the Simula-
tor’s photo library automatically.
recent photo taken. That will let us get our feet wet with the Photos framework
without having to write a bunch of UI code that’s not relevant to photos.
5. Finally, we check the result on lines 10–11 to make sure that there is, in
fact, a most recent photo in the library. If there is, we are going to create
a tweet from that photo. This calls a createTweetForAsset() method that we
will be creating next.
2. The requestImageForAsset() method will give us a UIImage for our photo library
PHAsset, and takes a few parameters to specify the image we get back. On
line 5, we set the size of our photo to tweet. We could just say we want
the photo to be the base size of the photo in our library, but the image
might be 4,000 pixels wide, which would take a really long time to upload
and probably be overkill for a simple tweet.
7. We are now calling our tweet view controller. We are passing our cus-
tomized tweet into the method and sending it out into the world.
Core Image
Now that we have our photos posting
properly to Twitter, wouldn’t it be fun
to jazz them up a little? There are all
kinds of apps out on the market to
apply filters to your photos, and it
would be really cool if we could do
that, too. Well, guess what, we can!
The first class we are going to explore is CIImage. CIImage does not contain
any image data. Rather, it contains a set of instructions to be sent to the
CIContext about how you plan to modify the components you are applying to
an image. If we took a source image and applied two filters to it, the CIImage
would basically just contain pointers to the source image data and the filters
to be applied.
The next class you need to know about for Core Image is the CIFilter class.
Filters, as we’ve discussed, are objects that operate on images and produce
a changed version of their contents. A CIFilter contains a dictionary that holds
all of the attributes for performing its work. For example, if you had a filter
that adjusted RGBA color values, your CIFilter instance would hold the values
for each of these attributes.
The final class we are going to talk about is the CIContext class. Most of the
frameworks in iOS that do drawing utilize contexts. If you were to move on
from here and work with Core Animation or OpenGL, you would encounter
their own flavors of context. A context is basically just the thing that the
drawing is being performed on. Since we want to draw the photo as altered
by the filter, we need a place to do that, and that place is the context.
Any time we want to add a filter to an image, we will go through the following
steps:
2. Create a CIContext to output your CIImage to. CIContext objects are buffers and
should be reused, so you don’t need to create a context for every single
thing you are doing.
3. Create a CIFilter instance to apply to your image. This is the step where
you will set all of the properties, the number of which will vary based on
which filter you are using.
4. Receive the filter output. This is the end of the processing pipeline where
you will take possession of your shiny new filtered image.
There are several ways of doing this, but since we already know that we are
not working on a video or exporting this to an OpenGL project, the best way
for us to take possession of our filtered image is to use CIContext’s createCGIm-
age(fromRect:). We could also use UIImage’s imageWithCIImage(), which is slightly
easier, but it performs badly. It’s important to consider the most efficient way
of doing something, not just the easiest way.
There are well over a hundred filters, but they are broken down into a few
categories. Here are some of the more useful ones:
• Blurs: These are the famous (or infamous, depending which way you look
at it) effects that were utilized in the iOS 7 design aesthetic but were not
implemented easily until iOS 8. Oops.
• Color Adjustments and Effects: These filters allow you to adjust your
colors in a controlled way to either correct your projects or let you do
complex effects with color.
A complete list of these filters is available in the Core Image Filter Reference
in the Xcode documentation. If you are interested in seeing the kind of code
associated with how these effects were created, check out GPUImage.1 GPUImage
is an open source framework for image processing that contains many similar
filters to the ones used in Core Image. The difference is that you can look at
how the shaders were written to get an idea of how that awesome effect you
are using was put together so that you can learn how to modify it and roll
1. https://github.com/BradLarson/GPUImage/
your own. Writing shaders is beyond the scope of this book, but if you are
interested in writing shaders, this is an invaluable resource.
We need to import the framework we are using. Go to the top of the root view
controller and import the Core Image framework:
photos/PragmaticTweets-14-2/PragmaticTweets/RootViewController.swift
import CoreImage
1. We need to start with a CIImage that’s based on the UIImage we received from
the Photos framework before. The CIImage initializer that takes a UIImage is
failable, meaning it could give us back nil, so we’ll add it to the if let on line
1. We’ll be using this to gather the pieces to apply our filter to our photo.
2. Lines 4–5 create and apply our filter. For this example, we chose the easy-
to-use (and easy-to-see!) CIPixellate, but there are over a hundred filters to
choose from. If you want to apply a different filter, feel free to do so; just
look it up in the Core Image Programming Guide, and replace its string
name and its required parameters in the imageByApplyingFilter() call.
4. A CIContext can’t create a UIImage directly, but it can provide its lower-level
bitmap equivalent, the CGImage, on lines 7–8. From that, line 9 can easily
create a UIImage, tweetImage.
Finally, we mustn’t forget to have the SLComposeViewController use our new tweet-
Image. Update the call to addImage() like this:
photos/PragmaticTweets-14-2/PragmaticTweets/RootViewController.swift
tweetVC.addImage(tweetImage)
However, ours is just one of many apps on the user’s device. It will come and
go as needed, with the typical user spending only a minute or less in our app,
or any other. That’s the nature of apps on mobile devices: App A does Thing
A, App B does Thing B, and never the twain shall meet. But as iOS has evolved
over the years, it has accumulated more and more ways in which apps can
work together to increase their mutual usefulness. Apps have gained the
ability to launch one another and exchange data and documents, albeit under
significant restrictions. It’s even possible to offer part of our application for
direct use inside other applications.
For a Swift app, iOS creates a UIApplication object to set up the app’s runtime
environment: its use of the display, its ability to handle touches and rotation
events, and so forth. This object is also how we can interact with the rest of
the iOS system, as we’ll see shortly. The UIApplication has a windows array, typi-
cally one per screen, and there’s only one screen unless we’re hooked up with
a video output cable or AirPlay. Each window has a rootViewController. And that’s
where the storyboard comes in: the application creates an instance of the
view controller in the storyboard’s initial scene, and puts its view into the
window.
Of course, at some point, users will leave our app by pressing the home button,
taking a phone call, accepting a notification from another app, and so on.
That’s not the end for us; they might come back. UIApplicationDelegate tells us
about these actions, with methods like applicationDidEnterBackground(), applicationWil-
lEnterForeground(), and so on. We used applicationWillEnterForeground() on a lark back
in Trying Out Our Function, on page 174, when we made a quick Twitter call
every time our app was foregrounded. There are also the related methods
applicationWillResignActive() and applicationDidBecomeActive() that tell us we’ve been
suspended so users can deal with an interruption like an alert from an
incoming phone call or SMS message. If they take the call or switch to the
Messages app, we’ll be backgrounded, but if they choose to ignore it, we’ll
become active again.
set up on a server, and which is beyond the scope of this book—we would get
it in the app delegate callback application(didReceiveRemoteNotification:fetchCompletion-
Handler:).
One way we can work directly with other applications on the device is found
in a rather unusual place: URLs. UIApplication offers the method openURL:(), which
will launch other applications and have them deal with the NSURL. Most web
page URLs, with schemes http or https, will open Safari. For example, we can
background ourselves and send Safari to this book’s home page with a one-
line call:
UIApplication.sharedApplication().openURL(
NSURL(string: "https://pragprog.com/book/adios3/ios-9-sdk-development"))
Not all URLs go to Safari. If the host is www.youtube.com, the YouTube app will
open instead, if present. phobos.apple.com iTunes Store URLs will open the iTunes
app. And other URL schemes can be used to launch default system apps—for
example, mailto:, facetime:, and tel: open the Mail, FaceTime, and Phone apps,
respectively. Check out the Apple URL Scheme Reference in the Xcode docu-
mentation for the exact syntax and more information.
What’s really cool is that third-party applications can also participate in this
system. Any app can create a new URL scheme that it handles, and then
other apps that open this URL will launch that app. Since our app does so
much with Twitter, let’s offer up our services to other apps.
Let’s think about what service makes sense to offer other applications. We
will receive a full URL from the caller, so we could design an API kind of like
REST endpoints, with different URL paths leading to different features within
our app. We’ll only implement one for now, but we could keep adding to it
later by just looking for different strings in the URL.
The user may already have an app to show recent tweets (or see them in
Notification Center), so let’s choose something unique that we offer. For this
example, we’ll let a URL take us straight to the user detail screen. If we get
a URL of the form pragtweets://host/user?screenname=name, we’ll go right to the
UserDetailViewController, as if we had drilled down on one of name’s tweets.
At the very top of the File Navigator, click on the top-level PragmaticTweets
project icon, and when the project settings come up in the Content View,
make sure the selected target is the PragmaticTweets app (as opposed to the
PragmaticTweetsTests target). This editor lets us configure how the build
process and the resulting app work: what versions of iOS we deploy to, whether
we have capabilities like iCloud or in-app purchase, which source files get
built and how, and so on.
The Info tab has metadata for our app such as its display name, what features
must be present on a device for it to work, and what kinds of documents it
accepts or produces. At the bottom of this view, there’s a URL Types section.
Expand the disclosure triangle, and then press the plus (+) button to show
the settings for a new URL type, seen in the following figure.
The important entry here is the URL Schemes text field, which takes a comma-
separated list of schemes we accept. Enter pragtweets here, without the trailing
colon character (:). The identifier should be a reverse-DNS style unique iden-
tifier, like com.pragprog.yourhandle.pragmatictweets, and the Role should be None,
so that we aren’t making any promises about what we do with the URL (the
Viewer and Editor values are more appropriate for dealing with documents
passed between apps).
After we run this app once, the system will know to send any URL with the
scheme pragtweets: to our app. Now we need to actually do something with this
URL when it arrives.
We could create a new segue from the split view directly to the user detail
scene. This would be totally legal. But there’s a problem: the Done button is
Universal links are well beyond the scope of this book, as they require uploading a
custom JSON file to your website at a specific https: path, and making a number of
declarations in your app’s metadata prior to submitting to the App Store. But all this
work does have another nice benefit: since the links are to your website instead of
being just arbitrarily chosen strings, there’s no risk of multiple apps claiming the
same link.
set to unwind to the tweet detail scene, but in the case where we come straight
from the split view scene, there won’t be a previous tweet detail scene, and
that will produce an error.
We have a couple of options. We could nuke the exit segue and instead set
up a button handler that calls UIViewController’s dismissModalViewController() method.
This would work for both cases, but it assumes we always come in via a modal
segue.
The option we’ll use is to just create another user detail scene, reachable only
from the first scene and used only for this URL handler. It makes our story-
board a little bigger, but it lets us customize too—an acceptable trade-off.
In Main.storyboard, select the yellow ball icon for the User scene and do a copy-
and-paste (DC, then DV). The newly pasted instance will be placed exactly
atop the old one. In the layout area, drag this scene by its title bar and notice
that it doesn’t have any segues associated with it. Move it closer to the
beginning of the storyboard (perhaps under the initial view controller), and
then use the name field next to its view controller icon in the scene list to
give it a unique name to keep things straight (like User Detail from URL Scene).
We’re going to need to run this segue manually when the URL comes in, which
means we’ll need an identifier string. Select the segue, bring up the Attributes
Inspector, and set the identifier to ShowUserFromURLSegue.
To do that, we’re going to rely on the NSURL class. It has several methods that
break down a URL into its various parts: scheme(), path(), query(), and so on.
We’ll take the segue if the path is /user and the query is of the form screen-
name=foo.
system/PragmaticTweets-15-1/PragmaticTweets/AppDelegate.swift
Line 1 func application(application : UIApplication, openURL url: NSURL,
- options: [String : AnyObject]) -> Bool {
- var showedUserDetail = false
- guard let query = url.query where url.path == "/user" else {
5 return false
- }
- let components = query.componentsSeparatedByString("=")
- if components.count > 1 &&
- components[0] == "screenname" {
10 if let sizeClassVC = self.window?.rootViewController
- as? SizeClassOverrideViewController {
- sizeClassVC.performSegueWithIdentifier("ShowUserFromURLSegue",
- sender: self)
- showedUserDetail = true
15 }
- }
- return showedUserDetail
- }
We start on line 3 with a showedUserDetail Boolean that will become true only if
we kick off the segue (way inside the ifs, on line 14). Then on line 4, we use
a guard let where to make sure url.query is non-nil and that the URL’s path is user.
Once we have the query part of the URL, we can split apart the name and value
on line 7 with componentsSeparatedByString(). That lets lines 8–9 verify that there
are two components, and the first is screenname. Note that we’re kind of
cheating because there is only one key-value pair; the more general case of
?key1=value1&key2=value2&… would take a lot more work to pick apart.
If we made it this far, then our URL is good. Lines 10–11 ask for our window’s
rootViewController as our SizeClassOverrideViewController class, the first scene in the
storyboard. If that cast works, then we can tell the view controller to manually
perform the ShowUserFromURLSegue segue, on lines 12–13.
At the very end, on line 17 we return a Bool to indicate whether we set showe-
dUserDetail to true. This is to uphold the contract established in the docs for
the application(openURL:options:), which expects us to return true or false to indicate
whether we handled the URL. For us, that’s determined by whether we made
it all the way to performing the segue.
This opens our app and goes immediately to the user detail scene. None of
the fields are filled in yet because we haven’t sent the values to that view
controller. Let’s take care of that.
Our AppDelegate can’t see the UserDetailViewController when it kicks off the segue.
But the first scene will get a look at it, in prepareForSegue(). That scene will need
to know about the screen name to show, so switch to SizeClassOverrideViewCon-
troller.swift and add a screenNameForOpenURL property:
system/PragmaticTweets-15-1/PragmaticTweets/SizeClassOverrideViewController.swift
var screenNameForOpenURL: String?
This class already has a prepareForSegue() method, to deal with the embed-
SplitViewSegue at startup, so just add an else if to handle the ShowUserFromURLSegue:
system/PragmaticTweets-15-1/PragmaticTweets/SizeClassOverrideViewController.swift
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embedSplitViewSegue" {
embeddedSplitVC = segue.destinationViewController
as! UISplitViewController
} else if segue.identifier == "ShowUserFromURLSegue" {
if let userDetailVC = segue.destinationViewController
as? UserDetailViewController {
userDetailVC.screenName = screenNameForOpenURL
}
}
}
Now that this view controller is ready for the segue, go back to AppDelegate.swift
and, on a new line right before we performSegueWithIdentifier(), send the screen
name to the view controller that needs it.
system/PragmaticTweets-15-1/PragmaticTweets/AppDelegate.swift
sizeClassVC.screenNameForOpenURL = components[1]
Run again, switch to Safari, open the pragtweets: URL, and this will show the
user detail scene with the real name, description, and avatar image.
The last thing we have to do is to fix the Done button, which still thinks it
can unwind to the tweet detail scene (since that’s what was in the scene we
copied over). First, in SizeClassOverrideViewController.swift, create an unwind method
that we can go back to:
system/PragmaticTweets-15-1/PragmaticTweets/SizeClassOverrideViewController.swift
@IBAction func unwindToSizeClassOverrideVC (segue: UIStoryboardSegue) {
}
Then, back in the storyboard, go to the new User Detail From URL scene and
Control-click or right-click the Done button to show its connections (or bring
up the Connections Inspector, ED6). Use the x-button to delete any existing
unwind segue, close the connections pop-up, and then Control-drag from the
button to the orange Exit icon in the title bar atop the scene; when the list
of unwind methods appears, choose unwindToSizeClassOverridingVC().
Run again, and open the user detail URL from Safari. Now, not only can we
view the user details, but we can also use the Done button to return to the
first scene of the app. Now not only is our app useful to our users, but it also
offers other apps (or web pages in Safari) the ability to send users over to us
with just a simple URL.
App Extensions
Using URLs to bring users into our app, and even to a specific feature, is a
very handy feature. But it does mean leaving the app the user was in and
coming over to ours. Throughout the history of iOS, each app has lived in its
own sandbox, prohibited from directly interacting with other apps. Each app
has its own section of the filesystem and can’t see anything outside its folders.
Each app runs in its own process and cannot share resources like frameworks
or dynamic libraries. This can lead to a lot of duplication of effort across apps.
Recent versions of iOS have poked some holes in the walls between applica-
tions by allowing apps to create app extensions. From the user’s point of view,
extensions are packaged with an app and allow it to extend some of its func-
tionality into other apps. From the developer’s point of view, the extension is
another target in the Xcode project, one that can share code and certain
runtime resources.
iOS defines several extension points, which are different kinds of functionality
that an app extension can provide, and a hook into that functionality via an
API or some aspect of the user interface. In iOS 9, the following extension
points are provided:
In addition, Apple Watch apps use app extensions: a watchOS app target is
just a storyboard for the user interface, and all the work of populating the UI
(including “glances” and “complications”) and communication back to the
host iPhone app is done with extensions. To learn more about developing for
Apple Watch, check out Developing for Apple Watch, Second Edition.1
The idea of an extension is to offer something that our app can do, that would
be useful to other applications. Our app knows a lot about getting data from
Twitter, so maybe there’s something we can do related to that.
Well, with our extension, we’re going to offer a custom keyboard that is a
table of all our user’s Twitter friends. That way, when users are in this sce-
nario, they can just switch to our keyboard, tap the name of their friend, and
have that text inserted directly into the host application.
Start a keyboard extension by clicking on the project icon at the top of the
file and choosing File > New > Target. This opens the Target Template sheet
shown in the figure on page 261. From the list on the left, choose Application
Extension, and on the right, choose the Custom Keyboard template and click
Next. On the following page, name the product PragmaticTweepsKeyboard, and
ensure the language is Swift.
Once the new target is created, Xcode will ask if we want to “activate” the
PragmaticTweepsKeyboard scheme. This means that the Build (DB) and Run
(DR) commands will build and run the keyboard code, not the main app.
That’s what we want to do for now. Later, when we’re done with the extension
and ready to turn our attention back to coding and debugging the main app,
1. https://pragprog.com/book/jkwatch2/developing-for-apple-watch-second-edition
that switches between keyboards when more than one is available, either
through internationalization settings or custom keyboards like ours. Click
the globe to cycle through keyboards. Eventually, one of them will be a gray
space that just says Next Keyboard. This is our keyboard, and it’s providing
the one thing all keyboards must offer: a button to advance to the next key-
board. It may not be very interesting, but it’s a start!
Building our UI in code isn’t something we’ve done before, and isn’t necessar-
ily a good idea in most cases, so let’s give ourselves the ability to lay out our
keyboard with a storyboard instead.
We don’t add our UI as a scene to Main.storyboard, since that’s part of the main
app and won’t be available to our extension. Instead, we need a new story-
board. Select the PragmaticTweepsKeyboard group, and choose File > New >
File. When the template chooser comes up, select the iOS User Interface group
and the Storyboard template, as shown in the following figure. Name the file
PragmaticTweepsKeyboard.storyboard, and in the list of targets in this dialog, make
sure that the PragmaticTweepsKeyboard is checked and PragmaticTweeps is
not; this will include the storyboard in the keyboard extension, but not in the
main app (which doesn’t know anything about it).
This scene looks like any other scene, and that’s actually a problem. Our
usual storyboard scenes take up a whole screen, but a keyboard should only
fill part of the screen. We’re going to do some custom sizing to make things
fit. At the top of the view controller’s Attributes Inspector, change Size to
Freeform, and in the Size Inspector (ED5), change the height to 204. This
doesn’t lock us into that height; we’ll have to set that with our views’ autolay-
out constraints. What this setting does give us is a more realistic sense of
our custom keyboard’s size.
We’ll keep the UI simple but still distinct so users know it’s a custom keyboard.
Drag a Navigation bar from the Object library (CED3) to the top of the view,
and use the Pin button to set its height to 44, its top constraint to 0 and its
left and right to 0, with the Margins check box deselected; this lets us stretch
all the way to the side of the screen. Edit the title to say Pragmatic Tweeps,
and add a bar button item on the right. The button will be our Next Keyboard
button; you can change the text to Next, or paste in a globe emoji if you want
to get fancy (Xcode makes this easy with the menu command Edit > Emoji
and Symbols).
Immediately below the Navigation bar, drop in a table, pinning its top con-
straint to 0 points from the navigation bar, and its leading and trailing con-
straints to 0 points from the superview with margins off. That leaves 160
points vertically in our current simulated size for the table, although the
system will decide how much space to give the table, so it may need to grow
or shrink as needed. Good thing that tables are good at that.
Finally, custom keyboards usually use different color schemes to set them-
selves off from the app content. A simple way to do this is to play with the
tint and the title color of the Navigation bar in its Attributes Inspector. In the
following figure, we’ve used the slate gray color scheme of the http://pragprog.com
home page banner.
Now we have to get the keyboard extension to use the storyboard instead of
building the UI in code. Under PragmaticTweepsKeyboard > Supporting Files,
open Info.plist, which is a special kind of XML file called a property list. This
file opens with the custom editor seen in the following figure. Use the disclo-
sure triangle to expand the NSExtension group, select it, and use the minus (–)
button to remove the NSExtensionPrincipalClass line. This entry described which
class provided the keyboard, and implied we would build our UI in code.
Once we’ve added the keyboard, go back to Safari and cycle through keyboards
with the globe key. We’ll be able to see our keyboard from its customized
Navigation bar. It doesn’t do anything yet…in fact, we’re trapped because the
Next Keyboard button doesn’t do anything. Still, we’re getting close!
Also, Control-drag from the bar button item to create an action (not another
outlet!) called nextKeyboardBarButtonTapped(). We’ll write this in a bit to handle
switching keyboards.
Advancing to the next keyboard turns out to be easy. Our superclass gives
us a method for it: advanceToNextInputMode(). So we just call that in our nextKey-
boardBarButtonTapped():
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
@IBAction func nextKeyboardBarButtonTapped(sender: UIBarButtonItem) {
advanceToNextInputMode()
}
Run the keyboard target, and, once we bring up the keyboard, we should now
be able to cycle through all the keyboards, including our own, now that its
Next Keyboard button works. Yay, integration!
Our strategy is going to be that viewDidLoad() will call the Twitter API and request
the list of friends, which we’ll put in an array that will serve as a table model.
This means our class needs to implement the usual table protocols, UITable-
ViewDataSource and UITableViewDelegate. Add these protocols to the class declaration:
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
class KeyboardViewController: UIInputViewController,
UITableViewDataSource, UITableViewDelegate {
We said we’d keep our list of tweeps in an array for the table methods to use,
so add that as a property near the top of the class.
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
var tweepNames: [String] = []
For that to work, we need to do a few things in the storyboard. First, select
the table, and in its Attributes Inspector, change the number of Prototype
Cells to 1. This creates a table cell in the storyboard. Select it and change its
identifier to DefaultCell, since that’s what the tableView(rowForCellAtIndexPath:) method
we just wrote expects. Next, right-click or Control-click the table (or show its
Connections Inspector), and connect the delegate and dataSource properties back
to the Keyboard View Controller icon, so our methods actually get called.
Our last task for now is to get the friend names from Twitter. There’s an API
for that, https://api.twitter.com/1.1/friends/list.json, which returns a richly detailed
dictionary for each of our user’s friends.
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
let twitterParams = ["count" : "100"]
guard let twitterAPIURL = NSURL(string:
"https://api.twitter.com/1.1/friends/list.json") else {
return
}
sendTwitterRequest(twitterAPIURL,
params: twitterParams,
completion: { (data, urlResponse, error) -> Void in
dispatch_async(dispatch_get_main_queue(), {
self.handleTwitterData(data, urlResponse: urlResponse, error: error)
})
})
}
This makes a call to sendTwitterRequest(), just like our others throughout the
book, asking for up to 100 responses, with a completion-handler closure that
calls a yet-to-be-written handleTwitterData() method to deal with the response.
handleTwitterData() gets the JSON back from Twitter, from which it needs to pull
out screen names and populate the tweepNames array that the table uses. Let’s
go ahead and write that.
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
Line 1 func handleTwitterData (data: NSData!,
- urlResponse: NSHTTPURLResponse!,
- error: NSError!) {
- guard let data = data else {
5 NSLog ("handleTwitterData() received no data")
- return
- }
- NSLog ("handleTwitterData(), \(data.length) bytes")
- do {
10 let jsonObject = try NSJSONSerialization.JSONObjectWithData(data,
- options: NSJSONReadingOptions([]))
- guard let jsonDict = jsonObject as? [String : AnyObject],
- usersArray = jsonDict ["users"] as? [ [String : AnyObject] ] else {
- NSLog ("handleTwitterData() can't parse data")
15 return
- }
- tweepNames.removeAll()
- for userDict in usersArray {
- if let tweepName = userDict["screen_name"] as? String {
20 tweepNames.append(tweepName)
- }
- }
- dispatch_async(dispatch_get_main_queue()) {
- self.tableView.reloadData()
25 }
- } catch let error as NSError {
- NSLog ("JSON error: \(error)")
- }
- }
That was a fair amount of work, but now we have live data in our keyboard.
Run the keyboard target again and check out the results. Our keyboard should
show a table of our friends’ screen name, as seen in the figure. Awesome!
The extension classes provided by the Xcode templates provide objects that
give us a hook into our host application. These vary by the type of extension.
For custom keyboards, the UIInputViewController we subclass has a textDocumentProxy
object that is our gateway to the text component that we’re typing into. This
object conforms to a hierarchy of three protocols, which together give us
everything we need to insert text into the document:
The one that will help us the most here is UIKeyInput. We can use that to insert
the text of the selected row directly into the text component. So add the fol-
lowing tableView(didSelectRowAtIndexPath:) method:
system/PragmaticTweets-15-2/PragmaticTweepsKeyboard/KeyboardViewController.swift
func tableView(tableView: UITableView,
didSelectRowAtIndexPath indexPath: NSIndexPath) {
let atName = "@\(tweepNames[indexPath.row])"
textDocumentProxy.insertText(atName)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
“What shortcut?” you might be asking. It’s back where we added our Twitter
utility code, ParsedTweet.swift and TwitterAPIRequestUtilities.swift, to both the Pragmat-
icTweets app target and the PragmaticTweepsKeyboard extension target. That
means this code is built twice, and two copies of it exist in memory at runtime.
Sure, it’s small, so it’s not a big deal for now. But as our app grows, we can
do better.
When we share code between an app and one or more extensions, the right
thing to do is to put that code into a framework, which can be called by the
app and the extensions. On iOS, a framework is a shared code library and
any resources it needs, such as graphics, localized strings, and documentation.
Creating a Framework
To finish this chapter, we’ll move that code into a framework, and then have
both the app and the keyboard extension call it. To create the framework,
select the top-level project icon in the File Navigator. The content view shows
properties for the main project, as well as the four current targets (the app,
its unit tests, its UI tests, and the keyboard extension). Use the plus (+) button
again to create a new target. From the Framework & Library group, select
Cocoa Touch Framework. When asked, call it PragmaticTweetsFramework, and
accept the various defaults, making sure the Embed in Application pop-up
is set to the PragmaticTweets app target.
This creates a new group after the test and extension groups that contains
an Info.plist metadata file and a PragmaticTweetsFramework.h C header. We won’t
need to work with either of these. Instead, drag ParsedTweet.swift and TwitterAPIRe-
questUtilities.swift into the new group, and delete the now-empty Twitter Utilities
group.
Start in the source files for the app and the extension. Every file that uses
our utilities needs to import the framework. So, in RootViewController.swift, Tweet-
DetailViewController.swift, UserDetailViewController.swift, and KeyboardViewController.swift add
the following line:
system/PragmaticTweets-15-3/PragmaticTweets/RootViewController.swift
import PragmaticTweetsFramework
Earlier, when we imported frameworks like Social or Accounts, this has been all
we needed to get our code running, but neither the app nor the extension
builds yet. Apparently, we have more work to do.
Even though we’ve imported the framework, building the app still indicates
it can’t find ParsedTweet or the sendTwitterRequest() function. The reason for that
can be found way back in Creating Tests, on page 104 when we talked about
access modifiers. The struct and the function both default to internal accessibil-
ity, making them visible only within their own module. Now that they’re in
the PragmaticTweetsFramework module, they’re no longer visible to the app or
extension targets.
To fix this, we need to declare anything we want our callers to see as public.
Start by putting the public modifier on the sendTwitterRequest() function in Twitter-
APIRequestUtilities.swift:
system/PragmaticTweets-15-3/PragmaticTweets/TwitterAPIRequestUtilities.swift
public func sendTwitterRequest (requestURL: NSURL,
Now we also have to make everything in the ParsedTweet structure public (well,
anything we want callers to see; we could still have private helper properties
or methods). So add the public modifier to all the properties in ParsedTweet, and
make the struct itself public.
system/PragmaticTweets-15-3/PragmaticTweets/ParsedTweet.swift
public struct ParsedTweet {
public var tweetText: String?
public var userName: String?
public var createdAt: String?
public var userAvatarURL: NSURL?
public var tweetIdString: String?
}
It turns out the no-argument initializer for ParsedTweet that we got for free won’t
be visible outside the framework module, so we need to explicitly provide a
public version of that, too, just before ParsedTweet’s closing curly brace:
system/PragmaticTweets-15-3/PragmaticTweets/ParsedTweet.swift
public init() {
}
This eliminates nearly all our build problems, but there’s one last error: the
keyboard extension still doesn’t seem to know about the framework. To fix
this, select the top-level project, and let’s go back to the target’s properties.
Select the PragmaticTweets app target and look under the Build Phases tab. In
the Target Dependencies section, there are two entries: one for the extension
and one for the framework. This means that the app will build those first,
before even attempting to build the app.
As our codebase grows, properly factoring out reusable code like this is an
important consideration. In fact, we might eventually create a project just for
the framework, and then add that project itself to other app projects that
need it as a dependency.
frameworks into your own apps and extensions. You might download a
framework from a site like GitHub, or use a dependency-manager application
like CocoaPods (http://cocoapods.org) to both download framework project code
and keep it up-to-date.
Thanks to iOS extensions, we now have the ability to go the other way: users
can stay in another application but use functionality we provide through
extension points like custom keyboards. We’ve also seen how frameworks let
us share our code between app and extension, and how frameworks can also
be used to share reusable code with other projects entirely.
Now that we’ve got a pretty complete application, our next task is going to be
learning how to take a step back to figure out what to do when things go
wrong.
Bugs happen. Even the most awesome rock-star programmer writes bugs. In
truth, developers spend only a fraction of their time writing code. A lot more
time is taken up by debugging that code. So one of the single biggest favors
we can do for ourselves is to become fast and efficient debuggers.
In this chapter you’ll learn about several methods for debugging, starting
with the most basic one, NSLog(). We’ll cover the various kinds of breakpoints,
and then we’ll take a nickel tour of LLDB, Xcode’s default debugger. You will
learn how to print to the console using a breakpoint and how to monitor a
variable for changes. Finally, you’ll find out how to make your app crash in
the place that the problem exists and not several steps afterward.
By the end of this chapter you’ll have the skills for dispatching bugs fast so
you can move on to bigger and better things.
The gist of NSLog() is that it will print a time-stamped message to the console
that only we will see. It might seem counterintuitive to create output that
only we will see, but it is vitally important to have some means of verifying
what is happening in our program. NSLog() output also goes to a system log
file, so we can collect it from beta testers to illuminate problems with our app.
Let’s put ourselves in a situation where we might want to use NSLog() to find
our way out. In RootViewController.swift, find the reloadTweets() method, and change
the URL it uses, like this:
debugging/PragmaticTweets-16-1/PragmaticTweets/RootViewController.swift
guard let twitterAPIURL = NSURL(string:
"https://api.twitter.com/1.1/statuses/foo_bar.json") else {
return
}
If we didn’t know the root cause was the bad URL, we’d have to think of rea-
sons this might be happening. We might be parsing the JSON incorrectly. It’s
possible the table is not connected to the view controller, causing self.table-
View.reloadData() to do nothing. Or the delegate might not be connected, so we’d
never get callbacks to tableView(rowForCellAtIndexPath:). We can mentally walk along
the path our code takes from the refresh to the updated table, as shown in
the following figure, in order to figure out where the problem might be, but
we can’t verify it without running some kind of test.
As you can see from the figure, there are at least four points in the operation
where something could have gone wrong—maybe more, when we consider
that some of these steps have multiple steps within them. We don’t need to
add an NSLog after the last operation because we already have observed that
the table did not update.
By setting up feedback for every step in the process, we can now observe at
what point in this chain the message breaks down: the request is sent, and
the response is parsed (to some degree), but we never update the table. By
throwing down a bunch of NSLog()s, we can at least focus our search on han-
dleTwitterData(), since we reach that method but it fails to update the table.
Breakpoints
At this point, you might be looking at this and thinking, “There is something
wrong with this. My Spidey sense detects Code Smell.” Trust your Spidey
sense. This solution is fraught with potential problems for your project.
Look at all those nasty NSLog statements all over our project. Ideally, we should
never include an NSLog command in code that we send to Apple. Although
they may help for debugging, NSLog statements are inefficient and slow our
app for absolutely no reason. Additionally, any code we add to our project
opens up the possibility of breaking something. What’s the point of using
something that might break our code in order to figure out how to fix it?
Wouldn’t it be great if we could still print all our commands to the console
without having to sift through all our code looking for those sneaky NSLog
statements?
Breakpointing Bad
The answer to our conundrum are breakpoints. You have probably inadver-
tently already created a breakpoint when you clicked on an error icon to try
to see what it said. Now we are going to create breakpoints on purpose.
Breakpoints are part of the Low-Level Debugger. LLDB is the debugger for
Xcode. Many of its functionalities have been built into the user interface, such
as the ability to create and edit breakpoints, as we’ll see shortly. It also has
many other commands that are not included in the user interface and need
to be entered via the Xcode console. By learning these, we can become efficient
debuggers…plus, we can do things that look like magic and we can impress
our friends and family.
code to create a breakpoint on any line. Create a breakpoint in the first few
lines of handleTwitterData(), inside the do catch block.
Right now when we create a breakpoint, it is kind of limited. It will just signal
the code to pause on this line. That’s helpful enough, as it will let us know
the app got that far, which is what we were tempted to use NSLog()s for. Fortu-
nately for us, breakpoints can do so much more than that.
• Add a condition.
• Ignore the breakpoint a variable number of times.
• Add an action.
• Determine if we want the program to pause or not after the program hits
the breakpoint.
Breakpoint Logging
Rather than burdening our code with lots of NSLog() statements, breakpoints
offer something easier to remove when we have finished debugging our code.
Click the Add Action button. Notice that one of our options is Log Message,
as seen in the following figure.
Log Message lets you do exactly that: log a message to Xcode’s debug console.
Since we are attaching this behavior to a breakpoint, it is easier to go back
later and filter out all of our debugging tools. Instead of a lot of messy code,
we have some nice, neat breakpoints, as seen in the following diagram. In
fact, it gets better: breakpoints are saved only in the local user’s Xcode con-
figuration. So, if we send this project to our colleagues, there will be nothing
for them to clean up.
At this level of debugging detail, we can do better. Go ahead and run the app.
The usual startup routine will call reloadTweets(), eventually resulting in a
callback to handleTwitterData(), which is where it hits our breakpoint and the
Mac automatically switches the foreground application from the iOS Simulator
to Xcode. By default, stopping on a breakpoint also causes two debugging-
related panes (shown in the following figure) to appear automatically.
• Debug Navigator (D6)—Shows the app’s usage of CPU time, memory, and
other resources. When the app is stopped on a breakpoint, it also shows
the state of active threads.
In the bottom right of the Debug area are three important icons: a trashcan
and two little boxes. The trashcan clears logged text from println() or breakpoints
that log messages. The two boxes show or hide the two panes of the Debug
area: the left shows a variables view, and the right shows the log messages.
The next three buttons are the step buttons. The first, Step Over, allows the
app to continue to the next statement in the current method and then stops
again. Further right, the down and up arrow icons represent Step In and Step
Out, respectively. Step In means that we will enter the statement on this line
of code and stop on its first line. Usually, this is only useful if the statement
is in code we’ve written, as the debugger can’t show us the source for Apple’s
framework code (or third parties’). Step Out does the opposite: it lets the app
Right above that, notice that the first thread we’re not on is called com.apple.main-
thread (serial). That’s obviously the main thread, meaning that the code we’re currently
executing is not main, so it cannot touch UIKit methods or properties, unless it puts
its work back on the main queue…and we’ve surely learned by now that the way to
do that is via dispatch_async().
continue until the current method returns, and stops on the first line in the
calling method after returning.
Our progress may go back and forth on the call to JSONObjectWithData() a few
times, but it will eventually reach guard let jsonArray = jsonObject as? [ [String:AnyObject]
], and then enter the else block. Here, it logs the message handleTwitterData() didn't
get an array, and it does an early return out of the method.
Progress! We now know we are failing because our JSON response isn’t an
array of dictionaries like we expect. That, of course, begs the question “what
the heck is it then?”
To figure that out, we need to make another trip through this method. Press
the Continue button (between the Breakpoints and Step Over buttons) to let
the app continue normally. It finishes its work and fails to update the table.
In the Simulator, do a pull-to-refresh on the table, which will make a new
Twitter request and hit our breakpoint again. Press Step Over until we’re
sitting on the guard statement again.
Use the pane buttons next to the trashcan icon to make sure that both the
variables and console panes are showing. The variables view shows us all the
variables currently in scope: the parameters that were passed in to handleTwit-
terData(), local variables we’ve created, and self. Variables that have public
properties have disclosure triangles that we can use to inspect those variables;
we could look at the self.parsedTweets array this way.
Since we know we aren’t getting past the guard on this line, we know that
jsonObject isn’t an array of [String : AnyObject] dictionaries. But why not? We can
see in the variables view that there is an error that was passed into us, but
it’s nil, so that’s probably not the problem. Also, if the try had failed on NSJSON-
Serialization.JSONObjectWithData, we would have been thrown to the catch block, so
parsing the JSON isn’t the problem either.
We need details! Fortunately, LLDB is here to help us out. Any of these vari-
ables can be inspected in multiple ways. Let’s hypothesize that the Twitter
API has used its response to tell us about an error. That would be in the url-
Response. Right-click (or Control-click) urlResponse, and from the pop-up menu,
choose Print Description Of "urlResponse", as shown in the following figure.
The Debug area will fill in with a log of the object, in this case formatted like
a dictionary (only the first few lines are shown here, reformatted to fit the
book’s layout):
Printing description of urlResponse:
<NSHTTPURLResponse: 0x7f83341298e0> { URL:
https://api.twitter.com/1.1/statuses/foo_bar.json?adc=phone&count=100 }
{ status code: 404, headers {
"Content-Encoding" = deflate;
"Content-Length" = 80;
"Content-Type" = "application/json;charset=utf-8";
Clear as a bell, we can pick out status code: 404. We asked for an endpoint that
doesn’t exist, and that leads us back to the underlying problem.
For images, an “eye” icon on the debugging toolbar sometimes lets us even
see a popover of an image variable selected in the variables list.
Exception Breakpoints
Another class of breakpoints happens when dealing with uncaught exceptions.
Certain problems, instead of failing or crashing immediately—which would
at least let Xcode show us which line of code blew up—will throw an NSException
object. The exception bubbles up through calling methods until someone
deals with it. If nobody does, we usually end up seeing it on a page of scary-
looking machine code with a message like libsystem_kernel.dylib`__pthread_kill:. Lot
of good that does us.
Pretend that Xcode is a dinosaur. Xcode goes about its merry way grazing on
a bunch of leaves until it accidentally eats some poisoned berries. Xcode
starts feeling kind of sick but decides to keep walking and consuming leaves,
even though it knows it is sick. It finally succumbs to the poison and falls
over dead a mile away from the poisoned berries.
As the caretakers of the Xcode dinosaur, we want to make sure we don’t keep
poisoning it, and it would be helpful to us if the Xcode dinosaur knew not to
wander away from the berries so that we can figure out where they are and
clear them out.
That is what exception breakpoints are for. When we set an exception in the
code, we are telling Xcode that if it encounters something that is going to
eventually kill it, we want it to stop going and show us where the bad stuff
is. In more concrete terms, we want a breakpoint when the exception is raised,
not 20 returns later after it hasn’t been caught.
Choose the Add Exception Breakpoint option. This will create a breakpoint
that will automatically stop the program at the exact location where an error
will occur. It is a good idea to set the exception breakpoint at the beginning
of our program to deal with any issues we might encounter while we are
coding. We only need one exception breakpoint.
Symbolic Breakpoints
If we look at the list of breakpoints we can create with the plus button in the
Breakpoint Navigator, we observe that there is an option called Add Symbolic
Breakpoint.
The following figure updates our hypothetical control flow to use a symbolic
breakpoint. If we put a breakpoint on UITableView’s reloadData() method, we’d
stop on any call to it, whether directly from our code or as a side effect (for
example, from navigating between scenes).
We have covered a lot of different ways that we can use breakpoints in our
program. At this point, you might be wondering which is the best way. The
best way is the one that works for you. We have debugged our program using
several kinds of breakpoints, so it is possible to do the same task many differ-
ent ways. Pick which one you like best or what works best for your specific
issues.
Think of your debugging tab like you would your kitchen. When you go to
your kitchen to cook, you can get a recipe started right away because you
know where all your tools are. If you didn’t know where to find your measuring
cups and the food processor, it would take a lot longer to get something
started.
First thing we’ll do is to create a dedicated debugging tab. You can create a
new tab by pressing DT, just as you would in a web browser, but there is a
better way to create a dedicated debugging tab.
We want to make sure that we can see all of our testing tools while we are
running and debugging our application. Find the Running section of the list.
Instead of just waiting to hit a breakpoint, we want to see the debugger when
the app starts, when it pauses, and when it generates output.
Joe asks:
How Do You Get Line Numbers in the Editor?
Maybe you’ve noticed that our screenshots of the breakpoint gutter on the left side
of the code editor show line numbers and wondered why your Xcode isn’t showing
line numbers. We are big fans of showing line numbers. It’s a nice reminder that once
one of our files hits 1,000 lines, it’s clearly time to refactor that code. Also, it makes
the breakpoint gutter a little wider.
To turn on line numbers, go to Xcode’s preferences and select the Text Editing section.
The first check box under the Editing tab is Line numbers, and that’s where you can
turn them on.
While here, there are a number of other preferences you can set, and the Indentation
tab lets you stake out your position in the never-ending tabs-versus-spaces war
(although this is also available as a setting on your project as a whole, and the project
setting takes precedence over your local preference).
Another useful Xcode preference group is the “Fonts and Colors” group, which features
various themes for styling your editor’s color scheme and font sizes.
Click Starts and look at the options available to us. You will see an option
that says “Show tab named empty text box in drop-down menu .” Click on the
check box to ensure that it is selected. In the text box, name it something
appropriate, like Debugging. Lastly, go into the drop-down menu and select
Active Window.
The last thing we need to do before moving on to other parts of the run cycle
is to make sure our debugger is showing. If we look at the option two below
the Show Tab option, we will see one that says “Show debugger with drop-
down menu .” Again, click on the check box to ensure this option is selected
and choose Variables & Console View from the options. Our Behaviors for
the Starts menu should look the way it does in the following figure.
Next, let’s move on in the left column to the behaviors we want when we pause
our program. We want to show the debugger with Variables & Console View,
but now we also want to select the option above that one, which selects a
navigator window to show, and we want the Debug Navigator. Make sure your
options look the way they do in the figure on page 288.
Lastly, in Generates Output make sure that you have it set to show the
debugger with Variables & Console View.
There! Now we have a handy debugging environment that will always be there
for us when we need it. Since this is your debugging environment, make sure
you go through and look at all the options you think you might want or need.
There is nothing that says everyone must have the same options, so feel free
to customize this to suit you.
Next, we tackle our final challenge. We have our awesome bug-free app. Now
we need to do the most important thing of all: publish the app on the App
Store so that we can rake in the dough, or at least the accolades of our col-
leagues!
In this chapter, we’re going to get our app out of the Xcode build-and-run
cycle and into the world where people can actually see and use it. We’ll start
by packaging the app for submission to the App Store and letting testers try
it out before we release it to the world. We’ll finish up by talking about what’s
next, both for our app and for our journey through iOS development.
A Change of Pace
The material in this chapter is mostly about working with Apple’s
developer websites. Because that’s something that we can’t
reproduce in a downloadable code example, and because Apple
can change it at any time, we’re taking a slightly different approach
to this material.
As of February 2016, membership costs US$99 per year, and covers develop-
ment for all Apple platforms: iOS (including iPhone, iPad, Apple Watch, and
Apple TV), Mac OS X, and Safari extensions. Along with the ability to publish
apps through the App Store, membership benefits include TestFlight testing
services (which we’ll cover shortly), and access to pre-release versions of iOS,
OS X, and Xcode. Members also get two technical support incidents per year,
which provide answers from Apple support engineers to problems in your
code. These are great for really tough problems that aren’t easily fixed by
searching Stack Overflow or Apple’s own developer forums.1
Once you’ve joined the program, there are two sites you’ll use to handle your
development and publishing needs.
Member Center
The Apple Developer Member Center2 is where you manage assets specific to
your development process. The front page has links to helper sites like the
Apple Bug Reporter3 and the forums, but the essential resource here is “Cer-
tificates, Identifiers, and Profiles.” These are the electronic assets that identify
and authenticate both you and your apps.
Profiles are used for two distinct purposes. A development profile allows an
app to be run on one or more specific devices. Combined with a matching
1. https://forums.developer.apple.com
2. https://developer.apple.com/membercenter
3. https://bugreport.apple.com
certificate, it tells the iPhone “it’s OK for this developer to install this app on
this device.” On the other hand, a distribution profile asserts your identity to
Apple itself; it’s used in the distribution process to prove to Apple that “we
know this person, and it’s OK for them to submit apps for review.”
iTunes Connect
If the Developer Center is the heart of development for the App Store, iTunes
Connect4 is all about distribution.
As a new member, your first task in iTunes Connect will likely be agreeing to
multiple legal agreements for Apple to distribute your apps for you, and setting
up banking information (so you can get paid!). Later, you can come back here
to check out sales reports on published apps, and see how the app is being
rated and reviewed on the App Store.
In fact, we haven’t done any of those things yet, so let’s go back to Xcode and
get our app ready for the store.
App Icons
By default, our app has no icon, and it has the name we gave it
when we created the project. It looks like the figure—not pretty.
Let’s get to work on that. We’ll start with the name being cut off. Click on the
project icon at the top of the File Navigator, select the Pragmatic Tweets target,
and click the Info tab. We can set some of the app’s metadata here, including
4. https://itunesconnect.apple.com
the Bundle Name, which defaults to the internal PRODUCT_NAME. Instead, just
change the bundle name to Prag Tweets.
Now about that generic icon. The first step here is to bring in a real designer.
“Why?” you might ask. Why slow things down by bringing in someone else?
Deadlines are a fact of life. We have all been forced by one deadline or
another to do something we didn’t want to do. But when that happens, skip
features; don’t skip design. The biggest mistake developers make is not having
a designer in the loop from the beginning. The design of your app is the way
that users will perceive it. After spending countless hours thinking about the
internal workings of your app, you don’t want to leave the users’ interaction
to chance. Just as classes need to be designed, user experiences need to be
designed.
Programmers fight for control; designers fight for the user. Make sure your
app has someone fighting for the users. Don’t ship an app that has not been
designed from start to finish. If the idea is worth your time and energy, then
it’s worth getting a designer involved.
So, eating our own dog food, we had Scott Ruth of BraveBit App Studio5 design
a proper icon for Pragmatic Tweets.
Scott delivered our icons in the form of an Assets.xcassets file, as seen in the
figure on page 293. This file has been present, albeit empty, since we started
our app. By default, it has a single entry called AppIcon, with blank spots for
all the known image sizes and pixel depths. Aside from providing app icons,
this file can also be used for any images shown by the app (such as those
5. http://bravebit.com
With a proper name and icon, our app looks a lot more polished
on the home screen, ready for our users to open it.
Launch Images
Another bit of visual polish we can attend to is what users see
at the instant the app is launched. When they tap the app icon, iOS presents
a launch image until the app is fully initialized and showing its first view.
Prior to iOS 8, the launch image was a static .png file. Initially, Apple’s guidance
was that the launch image should look exactly like the app’s first screen so
that the user wouldn’t notice the time it took to create and populate the first
view. In practice, though, many apps used the static image as a “splash
screen,” displaying a logo for the app or perhaps its developer or publisher.
The problem with this scheme is that designers had to create static launch
images for every combination of screen size and portrait-versus-landscape
orientation, meaning they potentially needed a dozen or more different, yet
related, launch screen designs. And that was before iPhones came in four
different sets of screen resolutions and iPads in two.
To upload an app to the store, we’ll need a distribution profile. For that, we
usually need an App ID. We say “usually” because there are certain edge
cases where this isn’t necessary. The trade-off is that while certain features
like iCloud require a unique identifier for each app that uses the feature,
there are a few scenarios where multiple apps can share a “wildcard” identi-
fier and work together. The latter case is rare and hard to do, so it’s best to
just always create App IDs for our apps.
With your browser, log in to the Developer Center and visit the Certificates,
Identifiers, and Profiles section. In the Identifiers section, choose App IDs,
and press the + button to create a new App ID. We just need two entries here:
a name (which cannot have spaces or special characters), and the app’s
bundle ID. We created the bundle ID way back in Our First Project, on page
59, when we combined a reverse-DNS style unique string with the name of
the app. You can check the bundle ID in Xcode by going to the ..xcodeproj in
the File Navigator and looking at the App target; it should be something like
com.pragprog.yourhandle.PragmaticTweets.
So, in the form, enter a memorable string for the App ID (we used PRAGMAT-
ICTWEETSIOS9), and under Explicit App ID enter the bundle identifier. We don’t
need any of the listed App Services for Pragmatic Tweets, but keep in mind
this is how you would signal to Apple that your app uses features like Apple
Pay or Push Notifications.
Two takeaways here: App IDs are universal, and they’re pretty
much forever.
Next, we choose which of our App IDs we’re creating the profile for (the one
we created in the previous section, of course), and on the following screen,
which of our signing certificates will be used to prove our identity as registered
iOS developers (there should only be one certificate, so choose that). After
these screens, the profile file is created and can be downloaded to your com-
puter. Click the button to download it, and then drag the downloaded
.mobileprovision file onto the Xcode app icon to install it into Xcode.
By doing these steps, we’ve prepared Xcode to send our app to Apple. Now
we need to tell the App Store what we’re sending it.
There are only four fields that need to be set to create a basic iOS app in
iTunes Connect: whether the app is for iOS or tvOS, its user-readable name
(like “Pragmatic Tweets”), its bundle identifier, and a SKU. The SKU is an
identifier unique to you and your organization. It’s not visible on the App
Store; it’s just a way to track this app versus any others you put on the store.
Now that we have prepared a distribution profile and an App Store record,
we’re finally ready to upload our app!
Archiving
It’s possible to use the scheme selector to create a release build for the device
—and this is a good practice for a final round of pre-submission testing—but
let’s cut to the chase. Select a connected iOS device or the Generic iOS Device
from the scheme selector, then choose Product > Archive. The Archive com-
mand does two things for us: performs a release build, and packages it in a
format that’s suited for distribution.
Uploading
Now we’re ready to upload our app to the App Store. For this to work, we need
to have at least a minimal record entered into iTunes Connect that matches
our app’s bundle identifier, something we did back in Creating an App Store
Entry, on page 296.
Lucky for us, uploading our app gets us a lot closer to typical users. Previous-
ly, we could only run the app on a device directly connected via USB cable
to our Mac running Xcode. But now, from iTunes Connect, we can send the
app to testers all around the world and have them try it out.
To do that, we’re going to use a testing platform called TestFlight. This service
allows us to send our app to testers of our choosing, lets them install it prior
to its release on the app store, and lets them send us their feedback. All they
need is their own iOS devices.
Beginning a test cycle sends out invitation emails to all members of your
team. The invitation includes two important links: a download link to your
app, and a link to the TestFlight iOS app. Once you receive the email, open
it on your device, and download TestFlight from the App Store. When you run
it, you’ll also have to install a special profile; this is like the provisioning
profiles that let Xcode put apps on your device, but in this case it lets the
TestFlight app install apps.
Once the TestFlight app is set up, return to the email and click the Start
Testing link. This will download the app and install it on your device. Run
the app as usual, and look for any bugs you missed. Back on the Mac, the
internal testing page will show that you’ve installed and run the app.
This is all well and good, but our own developers and colleagues aren’t always
going to be the most rigorous critics. For that, we need to go outside our
organization.
External testing works like internal testing in a lot of ways: you choose a
version and build to test, and an email is sent to all the testers, allowing them
to install the app via the TestFlight app and try it out. There are two big dif-
ferences:
• You can have up to 2,000 external testers of your choosing. These can
include your mom, your college roommate, your lover, your worst enemy
(hey, they’ll give good feedback)…all they have to have is an iOS device
an an email address.
• Apps sent out for external testing must pass a brief review by Apple before
they’re made available.
Let’s try it out. Get one or more friends to agree to help test the app and collect
their emails. Click External Testing from the left-side menu; this brings up
a page showing which version/build combination we’re testing, and who our
testers are. For our screenshots, Janie isn’t on Chris’s team in iTunes Connect,
so she’s the external tester. Click the + next to External Testers to add testers
by name and email.
In the iOS section, click the Add Build to Test link to choose any build we’ve
uploaded (after a brief processing delay immediately following the upload).
When we pick one, we go to a screen in which we describe what the app is
(mostly for the benefit of Apple’s TestFlight reviewers), and what we want our
testers to focus on. Also, provide an email where testers can contact you.
Approval can take as little as 30 minutes, though when we tried it, we added
the build on a Sunday and didn’t get approval until the next day. At any rate,
once approved, your external testers all receive an email linking them to the
TestFlight app and our app to test, just as we saw with the internal testing.
Fixing Problems
And now we wait. Our testers will be notified
by email that a new build is available, and by
installing the TestFlight app, they can install
our app. If they find bugs, they can return to
the TestFlight app and click the Send Feed-
back button.
The app does nothing at startup? Oh darn, that’s right. Way back when we
started parsing tweets, we just wrote out errors to the console, and left our-
selves a mental to-do to fix that.
Let’s take a moment to fix that, because we said we would pop up an error
alert or something, and we’ve never seen how to do that. If we clear out our
Twitter accounts in the Settings app and run again, we see that the message
no twitter accounts configured is written to the console. We can search and find that
this message is sent from sendTwitterRequest() in TwitterAPIRequestUtilities.swift.
We can’t really put up an error alert in that part of the code, because that
utility function has no references to any part of the user interface, and because
it’s a general-purpose function that’s called from many places in our code.
We should give this general-purpose method a way to the send the error to
the UI part of our codebase if there’s a problem.
Let’s do that. In sendTwitterRequest(), replace the guard that checks our Twitter
accounts as follows:
publishing/PragmaticTweets-17-3/PragmaticTweets/TwitterAPIRequestUtilities.swift
guard twitterAccounts.count > 0 else {
NSLog ("no Twitter accounts configured")
completion(nil,
nil,
NSError (domain: "PragmaticTweets",
code: 1000,
userInfo: [NSLocalizedDescriptionKey :
"no Twitter accounts configured"]))
return
}
This calls the completion closure with no data, no response, and an NSError of
our own devising. The error takes a string that indicates the module or
framework that spawned the problem, an arbitrary error code as an Int, and a
dictionary with further details. As we create many different errors, we would
do well do formally collect the error codes, perhaps in an enum. As for the
dictionary, the most useful key to provide is NSLocalizedDescriptionKey, as this
provides a human-readable error message.
There are other places we should employ similar techniques to pass errors
back to callers, but for now, let’s show this error in the UI. Back in RootView-
Controller, go to the beginning of the handleTwitterData() method and handle the
NSError if there is one.
publishing/PragmaticTweets-17-3/PragmaticTweets/RootViewController.swift
if let error = error {
dispatch_async(dispatch_get_main_queue()) {
let alert = UIAlertController(title: "Error",
message: "An error occurred: \(error.localizedDescription)",
preferredStyle: .Alert)
let ok = UIAlertAction(title: "OK", style: .Cancel, handler: nil)
alert.addAction(ok)
self.presentViewController(alert, animated: true, completion: nil)
}
return
}
our testers. Testers will get an email telling them about the new build, and
the TestFlight app will let them install it over the old one and continue testing.
There is a lot of material that needs to be provided for an App Store submis-
sion. The App Information section contains the basics of the app that we
already provided, like its name, bundle identifier, and SKU. We can also assign
one or two categories here, set a rating, and provide a custom license agree-
ment (if we don’t, a standard Apple license is used).
In Pricing and Availability, we set a price for the app. Prices are arranged in
“tiers” that are similar across different regions and currencies. Tiers are shown
in your local currency; once you select a non-free price, click Other Currencies
to see how the app will be priced around the world.
Screenshots
Depending on whether the app is built for iPhone, iPad, or both, we have to
provide screenshots showing the app running on those devices, at various
sizes. For an iPhone app, we need to provide at least one screenshot on a 3.5-
inch device (iPhone 4 series), and a 4-inch device (iPhone 5). If we support
larger screens, there are tabs for 4.7-inch (iPhone 6 and 6s) and 5.5-inch
(iPhone 6 Plus and 6s Plus). For iPads, we have a tab for the regular iPad
screen size, and another for iPad Pro.
There are two easy ways to get screenshots. From the Simulator, we can
choose File > Save Screen Shot (DS) at any time to save a screenshot to the
desktop. So by just switching devices in the Xcode scheme selector, we can
collect screenshots at the needed sizes. If we want to get a screenshot from
a device instead of the Simulator, we can use the Organizer window, which
has a Save Screenshot button that grabs the device’s current screen and
saves it to the desktop.
Descriptive Metadata
The Description is where we get to make the case for our app to shoppers on
the App Store. The challenge is that although we can enter up to 4,000
characters of text, only the first line or two is visible by default, and only
intrepid readers will click the More button that shows the rest. So it’s critical
to make our case with a catchy first line, like a witty slogan that captures the
essence of the app, or a quote from a rave review.
We can also provide keywords that will assist with searching, along with
support and marketing URLs. Because of the way the App Store works, this
is our users’ only way to contact us, so developing a page that greets users,
helps them out, and gives them a way to provide feedback is a good defense
against one-star reviews, which unfortunately are the easiest and most com-
mon way for users to communicate with developers.
For presentation in the App Store UI and web page, Apple also requires a
1024×1024 app icon (which hopefully our designer has provided us with!), a
publicly visible version number, and content information for assigning an age
rating based on the possible inclusion of elements like profanity, violence,
and simulated gambling.
The last section is called Version Release, and it determines how the app will
be released to the public if and when it is approved. We have three options:
automatically release it to the App Store immediately once it’s approved; hold
on for us to manually release it via iTunes Connect; or schedule it for release
on a given date and time.
When all our metadata and screenshots are uploaded and entered, we can
send it to Apple by clicking the Submit For Review button. At this point, the
app goes into a queue for review by Apple. The review process typically takes
about a week, during which time the app will appear in iTunes Connect as
Waiting for Review. Once a reviewer gets to the app, it will show as Under
Review. If it’s rejected, the status will be Rejected, and we’ll get an email
explaining the reasons for the rejection. At this point, we’ll have to address
the problem in our code, upload a new build, and submit for review again.
Once the app is approved, it will briefly appear as Processing for App Store,
and then Pending Developer Release if we chose a manual release, Pending
Apple Release if we chose a scheduled release, or Ready for Sale if we chose
for it to be made available immediately upon approval.
Success!
Our app is on the store and we are done!
Once version 1.0 is out the door, inevitably our attention will turn to version
1.1. We’ll include features that didn’t make the cut for 1.0, and incorporate
feedback from users and reviewers. By visiting the Activity tab in iTunes
Connect, we can see ratings and reviews from the App Store. There’s also an
App Analytics section that shows us sales data.
It’s also possible that our app has bugs bad enough to crash the app. Hope-
fully not, but maybe we force-unwrapped an optional somewhere that we
shouldn’t have. When users allow their diagnostic data to be sent back to
Apple, that actually shows up on our end in the Organizer. For any of the
builds we’ve archived, the Crashes tab shows us where the app crashed.
However, this only works if we keep the archive for that build on this machine.
The internally stored archive allows the crash report to be symbolicated,
meaning that the crash report’s data about memory addresses where crashes
occurred can be compared against the archive to figure out what line of code
caused the crash. Without the archive, crashlogs are almost impossible to
make heads or tails of.
Next Steps
You’ve learned so much to get to this point, but there’s a lot more you can
master in the iOS SDK. Where to go from here depends in part where you
want to focus your interests. The platform is so large, it’s possible to be a
generalist with an interest in many different iOS technologies, but also very
rewarding to focus on a few specific areas. We’ll finish up with a look ahead
to some directions you might want to go from here.
Networking
Our Twitter example was made much easier with the Social framework, though
we did get to make pretty much raw web-service calls to Twitter REST end-
points with the SLRequest class. Of course, many iOS apps call web APIs as
their basic functionality and aren’t using the Social framework. For general-
purpose networking, the first place to look is in Foundation, specifically at
the NSURLSession class and its various helper classes. NSURLSession offers perfor-
mant, asynchronous networking that lets you work pretty much the way we
did with Twitter: compose a request, send it off, parse the response in a clo-
sure, and pull out the returned NSData.
Productivity
Many iOS apps help users get the most value out of their data: finances;
appointments; and various kinds of personal records, from contacts to comic
book collections. For these productivity applications, you may want to learn
Foundation’s UIDocument class, which is the cornerstone of saving documents
6. https://github.com/AFNetworking/AFNetworking
7. https://github.com/Alamofire/Alamofire
to the local filesystem or iCloud. Another option is Core Data, a data persis-
tence framework that provides object-relational mapping: the power and speed
of a database with the simplicity and elegance of an object model.
Some of the user’s data is also exposed to apps via iOS frameworks. You
already saw how the Photos framework lets us work with the user’s photo
library. The Contacts framework lets you work with the Address Book entries,
while the MessageUI framework allows apps to compose email or SMS/iMes-
sage posts. This way, your app has the ability to provide access to the user’s
personal data and organizational features, without leaving your app in favor
of built-in Apple apps like Contacts, Mail, and Messages.
Games
Games are among the most popular apps on the iOS platform, and there are
lots of ways to get started. For 2D games, Sprite Kit offers a great place to get
started, by letting you focus on the design, physics, and gameplay, while
handling the drawing, animation, and collision detection for you.
2D graphics can also be created with Core Graphics, the system framework
for vector-based drawing.
3D games are a stiff challenge, but frameworks exist here to help as well. For
cross-platform code, the OpenGL ES library is a good place to start, with lots
of resources and sample code for drawing 3D graphics. The newer Metal
framework offers higher performance, at the price of being limited to Apple
platforms, because it gets its performance gains by cutting out cross-platform
abstractions and working directly and explicitly with the graphics chipsets
found on iOS devices.
Media
iOS devices are popular for watching video and listening to audio: music,
podcasts, movies, TV shows, livestreams…it’s hard to find an iOS user who
doesn’t enjoy at least one of these. Most media developers should start with
AV Foundation, which offers capture, editing, export, and playback of both
audio and video. Playback support includes local files, remote URLs, and
streams using the HTTP Live Streaming protocol. Media developers can also
use the Media Player framework to access the music library on the device.
For more advanced media processing needs, you can drop down to the lower-
level media processing frameworks, Core Video and Core Audio, which offer
more power but also much more complexity.
For a bigger challenge and a bigger screen, tvOS is an iOS-based platform for
writing apps for Apple TV. While the interaction model with a remote is differ-
ent than the touch-based gestures of iPhone and iPad screens, many of the
APIs will be familiar.
Device-Specific Features
Finally, there are a number of frameworks that exist specifically to provide
access to the unique hardware on iOS devices. HealthKit is the first stop for
developers interested in physical data collected by sensors like the step sensor
in the iPhone or the heart-rate sensor in the Apple Watch. Core Motion provides
access to the accelerometer and gyroscope data, allowing developers to react
to the motion and orientation of the device as the user handles it.
And it’s not just the iOS devices themselves anymore. HomeKit allows your
app to interact with Internet-of-things devices that support Apple’s HomeKit
standards. And Core Location, which helps your app figure out where in the
world it is, can work with iBeacons, which are used in indoor positioning
systems, like helping customers find their way around a store.
We finished up with a look at what’s next. The iOS SDK is much too big to
fit in one book, so it’s up to you to figure out what kind of app you want to
write next, and where you can find the features in the iOS frameworks to
create it. With the foundations learned in the preceding chapters, you’re ready
to build on your knowledge and create great new things.
managing properties of, new table model for con- portrait orientation and split
85–94 currency, 153, 155 views, 209–211
overhead of, 46 objects, 127 PragmaticTweepsKeyboard, 260–
OCUnit framework, 102 structure, 127, 271 265
online resources for this ParsedTweet.swift file, 126, 270 PragmaticTweets app
book, xiv ParsedTweetCell class, 133 camera button, 238
opaque property, UIView, 84 pass-by-reference, 44 debugging, 275–288
detail view for, 186–191
openURL:() method, 253 pass-by-value, 44
downloading code for, xiv
OpenGL ES library, 308 pass/fail results, for testing, growth, managing, 167–
optional type ?, appending to 106 175
properties, 39 pausing, breakpoints, 278 images for, in detail view,
optionals, 31–34 peek gesture, 235 190–191
chained, 108, 124 performRequestWithHandler:() keyboard extension for,
enumerations, 53 method, 145, 147 260–269
unwrapped, 33–34, 39, modal navigation, 191–
performSegueWithIdentifier()
43, 89, 133, 149 196
method, 197
OptionSetType, 149 multiple view controllers
PHAsset class, 238, see also as- in, 167–168
orange line or border, in IB, sets multitasking, 212
71–72
PHAuthorizationStatus class, 241 navigating between views,
Organization Identifier, for
PHFetchOptions class, 240 182–184, 191–196
project, 61 navigation controller in,
PHFetchResult class, 238–243
Organization Name, for 177–182
project, 61 PHImageRequestOptions class, 242
network security, 92–94
organizational tools, 3 Phone app, opening, 253 new tweet feature, 180–
Organizer window, 297, 304 Photo Editing extension point, 182
259 queues used by, determin-
origin property, UIView, 84
PhotoKit, 237 ing, 156
OS X, Xcode and, 3 queues used by, specify-
photos, 237–247
OS X Server, offering Xcode ing, 157–158, 160–163
accessing photo library,
bots, 115 race conditions, 161–163
241
outlets, 75, 88, 187 refactoring code in, 168–
as assets, 238
175
override, 188 aspect and size, 242
sharing data between
overriding fetching, 238–243
views, 185–191
trait collections, 215–217 filters, 243–247
split view for, 200–209
viewWillAppear() method, finding and filtering, 239–
table view for, refreshing,
188 243
153, 155–156
saving to photo library,
testing, 99–102, 104–110
P 240
tweets, data structure
pan gesture, 224, 227–229 tweet convenience
for, 153–155
method, 241
pane buttons, 281 Twitter account for, 145–
Photos framework, 237, 239 146
panic button, 230
Photos UI framework, 237 Twitter requests for, 142–
parameters
labeling, 158 pinch gesture, 224, 227, 231 143, 146–148
Twitter request parame- pinning UI elements, 70–71 Twitter responses from,
parsing, 148–150
ter, 147 play() method, 12
URL schemes, handling,
parameters variable, 147 playAudio() method, 41 253–259
parent view controllers, over- playAudioWithURL() method, 42– user detail view for, 191–
riding traits, 216 43 196, 253–259
ParsedTweet class playground, 5–8 user image detail view
appending to parsedTweets plus (+) button, 181 for, 220–224
array, 139 web view for, 105
plus sign (+) concatenation
cell reuse, 132
operator, 21 PragmaticTweets module, import-
filename, 126 ing, 105
initializers, 137 pop gesture, 235
PragmaticTweets.app file, 65
instances, 127
searching App Store, 305 showUserImageDetailSegue, 222 Source iOS group, selecting,
sections, rows in, 124 ShowUserFromURLSegue, 257–258 125
security, network, 92–94 Simulator split views, 200–209
camera access, 240 collapsing, delegate for,
segues
doing a pull-to-refresh on 207–208
adding, 182–184
the table, 281 connecting scenes, 204–
creating new scenes, 254
error alerts in, 82 205
embed, 213
launching, 65 container controllers,
exit segues, 66, 196–197
launching apps, 82, 112 213–215
gestures, 220–223
rotating, 72 iPhone 6s Plus, 213–217
modal, 192
running app, 65, 69 making room for, 88
naming, 184, 213
screenshots, 304 on iPad, 200–205, 209–
navigating between view
simulating pinch gesture 211
controllers, 182–184
in, 232 on iPhone, 206–209, 215–
open URL callbacks, 256
simulating slow network 217
programmatic, 197
in, 163 on iPhone 6, 215–217
sending data via, 186
using as a real iPhone, 82 restoring discarded view
unwind, 222, 258
in Xcode, 3 controllers, 208–209
selector variable, gesture recog- size classes, 212
nizers, 221 single inheritance classes, 43
splitViewController property, 204
self keyword, 40, 157 Single View Application, se-
lecting, 60 Spotlight Index extension
self.twitterWebView, 90 point, 259
size
Send Tweet button, 86, 111– Sprite Kit, 308
classes for iOS devices,
112
211–213 SSL, 30, 146
sendTwitterRequest() function doubling with transforma- sslRawVerify() method, 30
coding second view con- tions, 227
troller, 188 stack views, 70, 192
keyboard storyboard, 263
creating, 171–174 labels, 69 Standard Editor button, 77
friends table view, 267 photos, 242 Standard Editor mode, 64,
making public, 271 sizing bar in IB, 67, 72 91, 136
troubleshooting after tables, 134–135 static launch images, 293
testing, 302 user images, 223 static types, 17
sensors, data collection apps, Size Inspector, 69, 134–135
309 status bar, 125
SizeClassOverrideViewController Status display, Xcode win-
serviceType parameter, 146 class, 257 dow, 62
setOverrideTraitCollection(), 216 SKU, 296 step buttons, 280–281
setUp() method, 100, 111 SLComposeViewController class, Stop button
sets, 26 79, 81, 112–113, 242 stopping the Simulator,
Settings app sleepForTimeInterval() method, 112
built around a table, 119 114 in Xcode toolbar, 65
grouped table in, 131 SLRequest class, 142, 145–148, in Xcode window, 62
launching, 82 172 stopAudio() method, 42
split views, 200
SLRequestHandler class, 145, 172 stored properties, 40–41
shaders, 245
SLRequestMethodGET constant, storyboards
Share extension point, 259 146 about, 66–67
Share Links extension point, SLServiceTypeTwitter constant, adding table view to, 121
259 146 with Assets.xcassets file, 292
shared code, bundling in SMS messages and Mes- creating table cells in,
frameworks, 269–273 sageUI framework, 308 266
sharedApplication() method, 83 Social framework
keyboard extension, 262–
264
Show/Hide areas, Xcode win- compiler and, 81
launch images, 294
dow, 62 generalizing code, 171
navigating with segues,
showDetailViewController() method, letting apps connect to
184
209 social networks, 79
navigation bar, customiz-
showUserDetailSegue, 192 SLRequest class, 142, 145
ing, 180–182
Marcus S. Zarra
(238 pages) ISBN: 9781680501230. $38
https://pragprog.com/book/mzcd3
Marcus Zarra
(212 pages) ISBN: 9781680501704. $38
https://pragprog.com/book/mzswift
Jeff Kelley
(218 pages) ISBN: 9781680501339. $24
https://pragprog.com/book/jkwatch2
Adam Tornhill
(218 pages) ISBN: 9781680500387. $36
https://pragprog.com/book/atcrime
Ron Jeffries
(178 pages) ISBN: 9781941222379. $24
https://pragprog.com/book/rjnsd
Visit Us Online
This Book’s Home Page
https://pragprog.com/book/adios3
Source code from this book, errata, and other resources. Come give us feedback, too!
Contact Us
Online Orders: https://pragprog.com/catalog
Or Call: +1 800-699-7764