Transcript
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Silence ]
>> Hi there.
My name's Jacob Xiao and
I'm really excited to talk
to you today about
Building Adaptive Apps.
Before we get started though,
I want to tell you what
we mean by "adaptive".
So first, let's go back.
In the beginning,
there was the iPhone.
One device, one screen,
it was pretty simple
to build applications for.
But then when you
consider rotation,
now you have this portrait
and landscape orientations,
and then after that, we
introduced the iPad as well
as the iPhone 5 with
its four-inch display,
and now you have all
these different devices
and all these different
screen sizes,
and it can seem intimidating
to design apps for.
Well, in iOS 8 we want
to make it simple for you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Well, in iOS 8 we want
to make it simple for you
to build one application that's
both universal and is able
to adapt to all of
these different devices,
screen sizes, and orientations.
So today, I'd like to tell
you about some new concepts
that we've added for
adaptivity inside of UIKit,
and then we'll take a look at
some of the changes we've made
to both view controllers
and interface builder
to support this new
adaptive world.
All right, to start off, let's
take a look at size classes,
a new concept that we've
introduced in the iOS 8.
In the past you've used
UIInterfaceOrientation
and UIUser InterfaceIdiom to
differentiate between portrait
and landscape and iPhone
and iPad, but in iOS 8,
we're recommending against
using these two concepts,
and instead we're
advocating this new concept
that we call size classes.
So, let me show you a little
bit about what me mean
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So, let me show you a little
bit about what me mean
by these differences that are
represented by size class.
If you think about a typical
iPad application and the screens
that it shows, you'll
see features
like Split View Controllers,
Form Sheets, and Popovers.
But really, none of these
features are specific
to the iPad itself, we're just
using these in this iPad version
of the application because
we have this large horizontal
canvas to display within.
Now on the other hand,
if you look at a typical
iPhone application,
you have a much more
constrained layout
where things are usually
presented fullscreen
and shown in a single column.
However this is also not
intrinsically tied to the fact
that it's an iPhone, but
it's just tied to the fact
that we have this smaller
horizontal canvas size.
And you'll see the same kind
of thing in the master side
of a Split View Controller,
and even in the content
of a Popover.
And both of these are on iPad.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And both of these are on iPad.
So, we call this difference
"regular" and "compact",
and we call this axis the
horizontal size class.
Now, if we consider the
same kinds of distinctions
in the vertical direction,
you can see that on things
like an iPad or an iPhone,
we have these taller
full-size bars,
whereas in a more
constrained vertical situation
like an iPhone in landscape,
we have both condensed bars,
and now in iOS 8, we even hide
the status bar completely.
And we call these also regular
and compact, but this time
in the vertical size class.
Now, you can think of these two
size classes in a similar way
to the way size works.
Just like a size
has a horizontal
and vertical dimension,
so does a size class.
So if you put these two
concepts together, you get a two
by two grid that defines any
of the possibilities of regular
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
by two grid that defines any
of the possibilities of regular
and compact in both
the horizontal
and vertical directions.
And you can have a View
Controller with any
of these four possibilities,
which results
in different layouts
like you'd see here.
In addition to View
Controllers having any
of these size class
combinations,
our devices also have default
size classes that they'll use.
An iPad, in both
landscape and portrait,
will have a regular size
class in both the horizontal
and vertical direction.
An iPhone, when in portrait,
will have a compact
horizontal size class
and a regular vertical
size class, and when it's
in landscape it will be
compact in both the vertical
and horizontal size classes.
So this is all a
little theoretical.
Let's take a look at
a sample application
to see how this works
in practice.
All right, so the sample code
that I'll be showing
you today is available
on the WWDC website.
Just go to the Sample
Code section and search
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Just go to the Sample
Code section and search
for Adaptive Photos, the
name of our sample app.
It's a basic photo
sharing application,
and this is what it
looks like on iPhone.
You can see our list of our
contacts, and I can tap on any
of these to see the photos
that they've sent to me.
If someone who's only sent me a
single photo, I'll go directly
to that image, and if they've
sent me multiple photos,
I can see a list of the
photos that they've sent me,
and then view individual ones.
Now, on the Photo page,
you'll notice that in addition
to the picture, we're also
showing a comment overlay
and a rating control that
lets me rate the photos
that they've sent me.
All right, now when I rotate
this application to landscape,
notice that our bars
become condensed
and the status bar
disappears entirely.
This all happens automatically,
but in our application,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
This all happens automatically,
but in our application,
we've done a few
more customizations.
Inside of a photo
view, when I rotate,
the Comments will
have smaller margins,
and the Rating Control will
shrink down to a smaller size.
Now, our application
is of course universal,
so let me run it inside
of the iPad simulator.
Here, you'll notice that I have
all of the same functionality,
and all of the same controls
inside of my Photo View,
but watch when I
rotate to landscape.
All of the bars still
have their full height,
and none of my controls
here have shrunk down.
And this is because we
still have this large,
vertical real estate, and in
our application we're using the
vertical size class instead
of the interface orientation,
so we get the right behavior
on both iPhone and iPad.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
so we get the right behavior
on both iPhone and iPad.
Now, one of the new
features we've added
in iOS 8 is a resizable
simulator.
So if you notice
here at the bottom,
I can type in new
dimensions and you'll see
that my application has resized,
and everything still looks great
because we're using Auto Layout
and standard system components.
But in addition to
just changing the size,
I can also change the
size class and now,
you'll see that we're
showing the iPhone version
of our application
even on the iPad just
by changing the size class.
So, I highly recommend that
you test out your application
with the new resizable
simulator in iOS 8.
All right, so now that you
know how size classes work
in general, how do
you actually get one
of these size classes
in your application?
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of these size classes
in your application?
Well, to do that, you use a
new system we call Traits.
Traits are essentially
properties that you can use
to determine how the layout of
your application should change
as its environment changes.
They consist of a
set of properties,
including the horizontal
and vertical size classes
that we just talked about, as
well as the userInterfaceIdiom
and also the displayScale.
Now, all of these traits are
wrapped up inside a container
that we call a Trait Collection.
This includes the
Trait Properties
and also their values, and
this new object is called a UI
Trait Collection.
To get one of these Trait
Collections you just need
to use a Trait Environment.
Trait Environments are a
new protocol that are able
to return their current
Trait Collection,
and these include
Screens, Windows,
View Controllers,
and also Views.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
View Controllers,
and also Views.
All of these are able to return
their current Trait Collection
to you to use to determine
how your interface should be
laid out.
One other object that's
also a Trait Environment is
a UIPresentationController.
This is a new helper
object that assists
with View Controller
presentation, and it's also able
to participate in adaptivity.
We don't have time to talk today
about presentation controllers,
but you can come back
tomorrow at 11:30 to the
"A Look Inside Presentation
Controllers" talk to learn more
about how presentation
controllers work,
including how they work
with traits and adaptivity.
Now all of these Trait
Environments make
up a hierarchy, and
the trait collections
that they have will flow
from parent to child.
So by default, the
trait collections
that any given child Trait
Environment has will be the ones
that it's inherited
from its parent,
all the way up to the
screen which makes
up the root Trait Environment.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, in addition to getting
the current trait collection
from Trait Environments, they
also have another method,
and that's called
traitCollectionDidChange.
This gets called
whenever the traits
for a given Trait
Environment have just changed,
and you can override this
in your View Controller,
or View Subclass, to know when
you should be changing all
of your UI elements
that depend on traits,
and we'll see an example
of that a little bit later
in our sample application.
Now let's take a look at a
typical trait collection.
This trait collection
is one you might see
on an iPhone in portrait.
We have a compact
horizontal size class,
a regular vertical size
class, the idiom of phone,
and a display scale of two.
We call this a fully
specified trade collection
because it has values for
all of its trait properties.
Now it's also possible to have a
trait collection that's missing
some of its values, and
we call these missing
values Unspecified.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
values Unspecified.
Generally though, when you
ask a Trait Environment
for its trait collection,
you'll get back a fully
specified trait collection
like the one on the left.
However, if a Trait
Environment like a View
or a View Controller is not
inside of the view hierarchy,
you might get back
unspecified values
like the trait collection
on the right.
You'll also get back these
partially specified trait
collections if you create your
own trait collection using one
of our creation methods,
like traitCollectionWith
HorizontalSizeClass,
which would allow you to
create a trait collection just
like the one on the right.
Now, one operation
that we can perform
on multiple trait collections
is comparing them to each other.
And comparing a trait
collection involves asking
if one trait collection
contains another one.
Now, what this means
about containment is
that for any trait
that's specified
in a second trait collection,
the value of that trait
in the first trait
collection has
to have the same-has
to match exactly.
So here, the second trait
collection only has specified a
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So here, the second trait
collection only has specified a
horizontal size class and those
horizontal size classes are
equal, so we'd say that the
first trait collection contains
the second one, and the way
you can ask this question
of trait collections is by using
the containsTraitsInCollection
method on UITraitCollection.
If we were to change the
horizontal size class
of the second trait
collection to regular,
you can see that now, these two
horizontal size classes don't
match, so the second trait
collection is now longer
contained by the first one.
Now, you can perform
these comparisons yourself
in your own code to
determine how you should lay
out your views or
View Controllers,
but UIkit also uses
this internally for some
of its functionality,
and one example
of that is the Appearance Proxy.
The Appearance Proxy is a system
that we introduced a while ago
for customizing the
properties of your views,
and we've extended it in iOS 8
to support trait collections.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and we've extended it in iOS 8
to support trait collections.
We now have a new method,
appearanceForTraitCollection,
that returns you a
new appearance proxy
with a given trait collection
that you've passed in,
and any customizations
that you perform
on that appearance proxy will
only take effect on views
that are-that conform
to that trait collection
that you've passed in.
So, [applause] I'm
glad you like it.
Generally you pass
in a partially specified
trait collection
like a horizontal size class of
Compact, and then you'd be able
to customize all of your
views when they are inside
of a compact horizontal size
class, and this is really great
for customizing all of
your views together.
Another class that we've
added trait collection support
to is UIImage.
In the past, you'd have a 1X
and 2X version of your UIImages,
and you'd generally put
these in your image catalog.
However, in iOS 8, we've
extended this to allow you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
However, in iOS 8, we've
extended this to allow you
to add multiple versions
of your image
for different trait collections.
So for example, we could have
this smaller image that's used
when we have a vertically
compact size class,
and then the other image
that we use in any other time
for any other trait collections.
Now when you use one of these
images inside of a UIImageView,
the image view will
automatically pull
out the right version
of the image
for its current trait
collection.
So for example, if our image
view has a regular vertical size
class, we'd be using this larger
image, and what's really cool is
when our image view changes
to have a compact
vertical size class,
it will automatically update
the image that it's using
to be the smaller image,
and even change its own
intrinsic content size to shrink
down to exactly match
that image's size.
And this makes it really
easy for you to use images
that have different versions
for different trait collections,
and automatically get
the right behavior
in wherever your
application adapts to.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in wherever your
application adapts to.
Now like I said, this
all happens automatically
with UIImageView, but we
also have a new class called
UIImageAsset that gives
you even more control.
An image asset wraps up all
of these different versions
of the image, and
it allows you to ask
for a specific image matching
a given trait collection
that you can pass in,
and you can even add
and remove your own
representations
of an image using other
methods on image assets,
so check out the UIImageAsset
header file to see all
of the details of
how this works.
Now, one last thing
that we can do
with trait collections
is add them together.
When we add one trait
collection to a second,
we get a combined trait
collection, and we can do
that with the traitCollection
WithTraitsFromCollections
method.
Any time that one of the
traits is unspecified,
except for in one of the trait
collections that we're adding,
we'll get only the
specified trait
in the final trait collection.
However, if there are multiple
versions, multiple values
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
However, if there are multiple
versions, multiple values
for the trait that are
being added together,
then the last trait collection
will be the one that wins.
So here, we're adding
Compact to Regular
for the horizontal size class,
so our resulting trait
collection will also have a
regular horizontal size
class, and we'll see
where this can be used a
little bit later in our talk.
Now that you know
how traits work,
let's see how we use
them in our application.
So, let's run our application
on the iPhone again -
- and we'll take a look
again at these two views
that change their appearance
when their vertical
size classes change.
The first one is our rating
control, and we can look
at the code for that right here.
All that we're doing in this
view is creating image views
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
All that we're doing in this
view is creating image views
and setting images on
them that we're pulling
out of our asset catalog.
There's actually no code
in here at all that deals
with traits or size classes.
Instead, we just have assets
that define a regular version
of the image, and a vertically
compact version of the image,
and our Image View
automatically updates
to give us this resizing
behavior
for our ratings control, and
since we're using Auto Layout,
our entire control
will shrink and grow
to match those images changing.
Notice that we also have black
versions of these images here,
and they're turning to be
blue in our application,
and this is because we've added
Image Rendering Mode support
to the asset catalog.
[ Applause ]
All right, next let's take
a look at our Overlay view.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
All right, next let's take
a look at our Overlay view.
Here, in our intrinsic content
size method, we're looking
at the horizontal and
vertical size classes
that we're currently in, and
using that to determine margins
to add on to our
intrinsic content size,
and this is how we automatically
change between this larger
and smaller margins as we rotate
between a vertically compact
and regular size class.
However, we also need to
tell the system when it needs
to update this intrinsic
content size, and we do this
by overriding
traitCollectionDidChange.
Here, we check if
either the vertical
or horizontal size
class has changed along
with this trait collection
change, and if it has,
then we just invalidate
our intrinsic content size,
and that's all we have to do.
Now, one last view
in our application
is this Profile View,
and this shows some information
about the current user,
as well as the last
image that I've sent.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
as well as the last
image that I've sent.
However, when I rotate
this to landscape,
you'll see that our
layout changes
to show a side-by-side
view instead of this up
and down view, and
we've implemented
that by using Trait Collections
and Size Classes as well.
If I go to my Profile
View Controller,
you can see that we have
a method that updates all
of our auto layout constraints,
and it takes a trait collection
that we pass in, checks
its vertical size class,
and uses one set of
constraints when it's compact
to show the side-by-side
view, and uses a different set
of constraints, otherwise,
to show the up and down view.
All right, now let's look
back at slides, again.
The next thing I'd like to tell
you about is some of the details
of how our View Controllers have
adopted these concepts of traits
and size classes to
automatically perform,
be more adaptive in
your applications.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
One of the View Controller
classes
that has changed the most in
iOS 8 is UISplitViewController.
In the past, you'd often
use a Split View Controller
in the iPad version of your
application, and then you'd have
to write a completely different
View Controller hierarchy
for your iPhone application.
Well in iOS 8, we've made the
Split View Controller available
on both platforms so you can
just write one View Controller
hierarchy that works
great on iPhone and iPad,
and that's what we've done
in our sample application.
However, we've even gone
a little bit further there
and forced the Split
View Controller
to have its side-by-side
two-column view in iPhone
in landscape just like
it would have in iPad.
So, let me show you
exactly how we did that.
Let's first take a look at the
Trait Environment hierarchy
that our application has
when it's on the iPad.
Here, you can see that the Split
View Controller is inheriting
its trait collections
from its parents
and this gives it a regular
horizontal size class.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
When the split View
Controller is
in a regular horizontal
size class,
it automatically will
show the two column view
that you expect on an iPad.
However, if we now change
to what our iPhone's
Trait Environment looks
like in landscape here,
you'll see that once again
the Split View Controller is
inheriting its trait
collections,
but here it has a compact
horizontal size class,
and this triggers it to
show the one column view.
Well, to change this
in our application,
we'll insert our own container
View Controller as the parent
of the Split View Controller.
Then, we'll use the new method
in iOS 8 called
setOverrideTraitCollection:
for ChildViewController.
This allows us to add
our own trait collection
to the one that's inherited
by the Child View Controller.
In this case, we'll be adding
a regular horizontal size class
in our trait collection,
and notice that it's
partially specified,
so when we add it together with
the inherited trait collection,
our Split View Controller will
now have a regular horizontal
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
our Split View Controller will
now have a regular horizontal
size class in addition
to what else it inherits
from its Trait Environments,
and this regular size
class will cause it
to show the two-column
split view appearance.
So what this ends up with
when we rotate from portrait
to landscape, is it will
change from one to two columns.
All right, now that we've seen
how we can change the trait
collections of our Child View
Controllers, let's take a look
in detail at how the trait
collection transition occurs,
for example, when we rotate.
In this case, we'd be rotating
from portrait to landscape,
and let's take a look at the
timeline of changes that occurs.
The first phase we have is
the setup where we get ready
to perform this transition.
Then, we show animations to
indicate the visual changes,
and when all of that's
done, we do some cleanup
to finalize the transition,
and in iOS 8,
when Trait Collections change
as part of this collection,
we've given you some
callbacks that you can tie
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
we've given you some
callbacks that you can tie
into to participate
in this change.
The first one is
willTransitionToTraitCollection:
withTransitionCoordinator,
and this gets called
at the very beginning
of the setup before the
trait collection has changed
to its new value.
You can use this to get ready
for the change that's
about to occur.
After that, the trait collection
change itself happens,
and immediately following
that we call
traitCollectionDidChange,
as I mentioned earlier,
and you can use this
in View Controllers
or View Subclasses.
However, that's not
the end of the story.
We still have these
Animation and Cleanup Stages,
and you can actually use the
willTransitionToTraitCollection
method to tie into
those, as well.
The transition coordinator
that you get passed
in this method has an animate
alongside method that allows you
to add your own animation
blocks to this transition
that will run along with the
transition's own animations,
for example, the
rotation in this case.
There's also a way to add
your own completion blocks
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
There's also a way to add
your own completion blocks
that will get run inside
of the cleanup stage.
So let's take a look at
where we might use these two
specific callbacks.
WillTransitionToTraitCollection
is great
for animating View
Controller changes along
with these trait collection
changes, so we use this
in our application in the
profile view that we just saw,
since we want this change
to occur right alongside
that rotation transition.
However,
WillTransitionToTraitCollection
is only available
on View Controllers,
not on other Trait
Environments like UIViews.
And so, traitCollectionDidChange
is great for use
in UIView subclasses where
you want to update your UI
as the traits are changing.
So we've used this one
in our Comment Overlay
View that you saw earlier.
So now that we've seen
these trait changes,
let's drill in a little bit
deeper into the behavior
that happens when a Split
View Controller collapses
from a two-column to
a one-column view.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
As part of this change,
there are two changes
that need to occur.
The first thing that
has to happen is we need
to find a new primary
View Controller to show
in this collapsed
one-column state.
By default, Split View
Controller will use the primary
View Controller from your
expanded two-column state
as the new primary
View Controller
in your collapsed
one-column state as well.
However, you can
also change this
by overriding the
splitViewControllerDelegate
method, primaryViewControllerFor
CollapsingSplitViewController.
This allows you to return
any View Controller you want
to be the new primary
View Controller
in the collapsed version of
your Split View Controller.
Now, once that new primary View
Controller has been chosen,
the next step that has to happen
is the secondary View Controller
has to get merged into that
primary View Controller,
and in general, Split
View Controller will try
to automatically do the
right thing here as well.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to automatically do the
right thing here as well.
In fact, in our sample
application we didn't need
to write or add any code to get
the behavior that you see here
where the secondary View
Controller, our Photo View,
automatically gets pushed onto
the Navigation Controller Stack
that we had from our
primary View Controller.
So generally, you won't
have to do anything here.
However, there may be some
special cases where you want
to interact a little
bit with this change.
One example of that from our
sample application is the No
Conversation Selected View.
We show this whenever we're
in the two-column wiew
and nothing has been
selected on the left.
However, if we just use
the default behavior,
the Split View Controller
would take
that Secondary View
Controller and push that on top
of our navigation stack,
but this No Conversation View
doesn't really add anything
to the single column view
because there's no way
that the user can
interact with it.
So, really we'd rather have
a view that looks like this
where we just show the top
level List View Controller
when we get collapsed,
and it's possible to do
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
when we get collapsed,
and it's possible to do
that with another
splitViewController method,
collapseSecondaryViewController:
ontoPrimaryViewController,
and I'll show you in detail
the code that we use to do
that in our sample app
a little bit later.
Next, let's look at the
transition that happens
in the opposite direction
when we expand
from a single column view
to the two-column view.
Once again, there are two
stages that need to occur.
We need to find the new primary
View Controller, and by default,
split View Controller will also
use the primary View Controller
from the collapsed view and the
expanded view, and once again,
you can use a Split View
Controller Delegate method
to change that behavior.
This one is
primaryViewControllerFor
ExpandingSplitViewController.
Now once that new primary View
Controller has been chosen,
we need to take the secondary
View Controller and recreate it
from the primary View
Controller that was collapsed,
and a Split View Controller will
automatically do the right thing
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and a Split View Controller will
automatically do the right thing
here as well by popping off
our photo view and showing
that as the new secondary View
Controller in our Expanded view.
However, once again, we want
to do something a
little bit special
for the No Conversation view.
Here, if we were
showing the List view
when we were collapsed,
we want to recreate
that no conversation selected
view and make that appear
as the new Detail View
Controller on the right.
So we can do that using another
Split View Controller delegate
method,
separateSecondaryViewController
FromPrimaryViewController.
Now, I've just been telling you
that Split View Controller
is doing all of these merging
and unmerging of the secondary
View Controller automatically,
and that's not entirely true.
The primary View Controller
itself is actually helping
to do this merging
and unmerging,
and it's doing this using these
two methods on UIViewController,
collapseSecondaryViewController
forSplitViewController
and
separateSecondaryViewController
ForSplitViewController.
These get called by the
Split View Controller as part
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
These get called by the
Split View Controller as part
of its default implementation of
the collapsing and uncollapsing
that occurs,
and UINavigationController
implements these to push
and pop the secondary
View Controller
for you automatically.
However, you can
also implement these
in your own container View
Controllers to get the same kind
of behavior the navigation
controller has,
or even something completely
custom for your application.
Now, one other change
that we've made
to how View Controllers
work in iOS 8 is in the way
that you showViewControllers.
In the past, if you had
a leaf View Controller
like a table View Controller
and you showed a different one,
perhaps by tapping on the cell,
that View Controller would reach
up through the View
Controller hierarchy
and grab the navigation
controller
that it was embedded inside of
and call push View Controller
on that, but this is a
pretty tight coupling
between the leaf View Controller
and the exact environment
that it's inside of, and
we want to try to move away
from this pattern in iOS 8.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Applause ]
So, instead we're
introducing two new methods
that help you de-couple
this, showViewController
and showDetailViewController.
These methods work by starting
at the leaf View Controller
and walking up its parent
View Controller hierarchy
until they find the right
Container View Controller
for that specific action.
So, let me show you how
these specific methods work.
We'll start with
ShowViewController
and how it behaves when it's
inside-when it's called inside
of a UINavigationController.
Here it will just push onto
the Navigation Controller,
so this is a great replacement
for that Self.Navigation
Controller/Push View Controller
approach that you saw earlier.
And what's great
about this method is
that it will actually adapt
to different View
Controller containers
and do something different.
For example, when you're inside
of a Split View Controller
and you call this method it
will instead show the new View
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and you call this method it
will instead show the new View
Controller on the left side
of the Split View Controller
as the new Primary
View Controller.
In fact, even when you're
not inside of any container,
View Controller that implements
this method will still give you
some automatic behavior.
Here, we'll show
the View Controller
as a modal View Controller
presentation.
So, you can always be guaranteed
that the View Controller
that you pass to
ShowViewController will be shown
in exactly the right way
for its current environment.
Next, let's look at
showDetailViewController,
this works similarly
and it's implemented
by Split View Controller.
Here, the Split View Controller
will show the View Controller
you pass on the right
of its split.
However, if that Split View
Controller is collapsed,
as you might see in
an iPhone application,
then it will actually redirect
that showDetailViewController
method to showViewController
and re-send it to its own
Primary View Controller.
Let's look at a more
concrete example,
where we have
UINavigationController
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
where we have
UINavigationController
as the Primary View Controller
of the Split View Controller,
just like in our application.
Here, the Navigation Controller
gets the showViewController
method and will push just
like you saw earlier.
So,
this showDetailViewController
gives you great behavior
in a Split View Controller
where it'll show it
on the right-hand side if it's
expanded, but it will push
onto a Navigation Controller
if that's your Primary View
Controller when it's collapsed.
And once again just like
with showViewController,
showDetailViewController will
show that View Controller
as a model presentation
if it's not inside
of any container View
Controller that implements it.
So, these are those two
View - those two methods
and what their method
signature looks like but,
in addition to being
able to call them,
you can also implement these
in your own custom
View Controller methods
and this lets you get exactly
the same kind of behavior
as Navigation Controller
and Split View Controller
will get inside of all
of your custom View Controllers.
[ Silence ]
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[ Silence ]
[ Applause ]
Next, I'd like to
tell you a little bit
about how these methods
are actually implemented.
And that's using a new method
called targetViewController
ForAction:sender.
This does all the work of going
up the View Controller hierarchy
until the right View
Controller gets found.
For example, if we call
showViewController we'd walk
up until we find the
Navigation Controller
since that's the first View
Controller that implements it
or if we called
showDetailViewController we
would keep walking up the
View Controller hierarchy
until we got to the
Split View Controller.
And
targetViewControllerForAction
works by looking at the
View Controller and seeing
if it's overwritten the action
method that you've passed in,
and also whether that
View Controller wants
to receive that specific action.
And the great thing
about this is
that since its public you can
use it to make your own methods
that work just like
ShowViewController
and ShowDetailViewController,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and ShowDetailViewController,
and we'll see some
specific examples of this
as we've used it in
our sample application.
The last topic that I'd
like to mention is View
Controller presentation.
In iOS 8 we've made
this adaptive as well.
So if you show a Popover,
a View Controller presentation
will now automatically adapt
that to a fullscreen
presentation when you're
in a horizontally
compact size class.
Once again we don't have time
to talk about that today,
but I highly encourage
you to go to the
"A Look Inside Presentation
Controllers" talk,
which is tomorrow at 11:30.
It will show you all about how
Presentation Controllers work
and also how you can use them
with traits and adaptivity.
All right, let's look
at a demo of how all
of those View Controller
features work
in our application.
First, let's look at
that profile view again
and let me show you what
its transition looks
like in slow motion.
Notice that our labels and image
view are moving right along
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Notice that our labels and image
view are moving right along
with the rotation transition.
And as I indicated,
the way we do that is
by calling this
updateConstraints
ForTraitCollection
method inside a
willTransitionToTraitCollection
with transition coordinator.
Here we just use the
animateAlongsideTransition
method of the transition
coordinator,
call that updateConstraints
method
and then make our view
update its layout.
This will automatically cause
this layout-these layout
constraints to change alongside
that rotation transition.
All right, now let's look at
how we can override the traits
for our Split View
Controller to automatically get
that landscape view
that shows two columns.
We'll just add this
viewWillTransitionToSize method
and here, if our width
is larger than 320,
we'll add this Forced Trait
Collection, which is -
has a horizontal size
class of regular.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
has a horizontal size
class of regular.
This is one of those partially
specified Trait Collections
that we talked about earlier.
When we set that trait
collection we'll use the
setOverrideTraitCollection
ForChildViewController method
to add it to our
Child View Controller,
which in this case is a
Split View Controller.
Now when I run the
application again you'll see
that we can rotate to landscape
and get this two-column view.
And everything just works
in iPhone as you'd expect
and in landscape it works
just as it would on the iPad.
Now let's look in detail at how
we implemented the collapsing
and expanding behavior.
This happens in our Split View
Controller delegate method.
First, we have the
collapseSecondaryViewController:
ontoPrimaryViewController method
and here we want to use this
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
ontoPrimaryViewController method
and here we want to use this
for when we're collapsing
and we're going from a -
showing the No Conversation
view to hiding it
and only showing
this top level list.
First, we'll ask the
Secondary View Controller
if it contains a
photo and we did this
by adding a category method
to your View Controller
to return whether or not any
given View Controller shows a
photo and, if we do
not have any photo
in our current Secondary View
Controller, we'll return yes.
This tells the Split
View Controller
that we've handled the collapse
ourselves and turns off any
of its default behaviors
which would have pushed
that Secondary View Controller
on top of the navigation stack.
We also have some
logic here to make sure
that we don't push a
View Controller on top
of any View Controllers that
don't match its current photo.
You can look at the
sample application yourself
to see exactly how this works.
Finally, we return "no"
here when there was a photo
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Finally, we return "no"
here when there was a photo
and this tells the
Split View Controller
that we didn't implement
the collapse
and so it will implement-it will
perform that behavior itself.
And that's how we get
the automatic behavior
of pushing this photo view
off the navigation stack.
Now if we look in
the other direction,
where we're expanding our Split
View Controller we'll take a
look at all of the View
Controllers that are on top
of the navigation stack and,
if any of them contain a
photo, we'll return "no".
This also indicates to the Split
View Controller that we want it
to perform its default behavior
here so if we had a photo
and we were expanding then
that photo would
automatically get popped off
of the navigation stack
and shown on the right.
If we didn't contain
any photo though,
we'll want to create our
Empty View Controller
and that's how we re-show
that No Conversation view
when we're expanding.
Now let's take a look at
how our application moves
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now let's take a look at
how our application moves
between View Controllers.
First we'll look at the
Conversation View Controller.
This is the one that's
shown here when one
of our conversations
have multiple photos.
If we look at the
method that gets called
when we select a
table view cell,
we create our new
View Controller
and we call
ShowDetailViewController
on that new View Controller.
This shows it on the right
of the Split View Controller
if we're expanded and, as I
mentioned, it will push it
onto the navigation
stack if we're collapsed
without us having to write
any device specific checks
in our code.
We can also look at our
List View Controller,
which has similar but slightly
more complicated behavior.
And that's so that we can
show a single View Controller
immediately if there
is only one photo,
or this Conversation
View Controller
if there is more than one.
Here we check if for any
given row we should show
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Here we check if for any
given row we should show
that Conversation View
and if we're showing the
Conversation View we create it
and use ShowViewController.
This method will
always push onto the -
onto its navigation controller
even when we're expanded
in the two-column view.
On the other hand, if we only
have single photo we'll create
that View Controller and
call ShowDetailViewController
which gives us the
same behavior as we had
in the Conversation
View Controller case.
Now one more thing that you'll
notice in our application is
that when we rotate to
transition between a single
and two column view these
disclosure indicators are hiding
and appearing and this is
to maintain the behavior
that whenever tapping on a row
would push we show a disclosure
indicator and whenever it
wouldn't we don't show the
disclosure indicator.
So in a single-column,
collapsed view we push on all
of these rows, but in the
expanded view we only push
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of these rows, but in the
expanded view we only push
on these multiple
conversation views.
And we did that by adding our
own category to UIViewController
that implements two new methods,
willShowing
ViewControllerPushWithSender
and willShowingDetail
ViewControllerPushWithSender.
These correspond to
the ShowViewController
and ShowDetailViewController
methods that we talked
about earlier, and they just
return whether calling one
of those methods would
cause a push to occur or not
which we use to determine
whether or not
to show a disclosure indicator.
The way we implement these is
by using the
targetViewControllerForAction
method that we just
talked about,
we pass in the same action will
showing View Controller push
with sender and then we get
back a targetViewController
that implements that method.
Once we have our
targetViewController we just
need to ask it whether
it will push or not
and if we don't have a
target we'll return no.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Then, you just need to
override those methods
and Navigation Controller
to return yes,
then showing a View
Controller here will always push
and will override it in Split
View Controller to return no,
since here showing a View
Controller will show it
in the left hand column
as I mentioned earlier.
Now, the behavior
for willShowingDetailView
ControllerPush is slightly more
complicated, because
here we need to check
if we're collapsed first
and if we're collapsed then
we'll take our current Primary
View Controller and
we'll redirect the
willShowingDetailViewController
method
to willShowingViewController,
and this is the same kind
of behavior that Split
View Controller implements
with showDetailViewController.
So, now that we've seen
how those are implemented,
let's look at how we used it
in our Table View
Controller classes.
Here, when we're laying out one
of our cells we'll call will
showing detail View Controller
push and that's because the
method that we would call
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
push and that's because the
method that we would call
when it was tapped is
showDetailViewController.
We'll get back the
result of whether or not
that action will push and we'll
use it to set the accessory type
to either disclosure
indicator or none.
This is how we get these showing
and hiding disclosure
indicators.
Now, we also use these methods
to determine whether or not
to deselect a row when
it gets popped too
and you can take a look at the
sample application yourself
to see exactly how that works
by using the same
willShowingView Controller
and willShowingDetail
ViewController methods.
The last thing I'd like to show
you is a new notification we
added in iOS 8,
UIViewControllerShow
DetailTargetDidChange
notification and
this is important
for telling our table
view when it needs
to update its disclosure views.
This notification gets triggered
by Split View Controller
whenever the target
that would be used for
showDetailViewController changes
and this is when a Split View
Controller expands or collapses
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and this is when a Split View
Controller expands or collapses
so we use this notification
to go through all of our cells
and update their
disclosure indicators.
That's all for our
sample application,
but I really encourage
you to download it
from the WWDC website.
Remember, it's called
adaptive photos and take a look
at how it implements
all of these features.
Next, I'd like to hand things
over to Tony to talk to you
about some of the changes we've
made to Interface Builder,
Interface Builder for
supporting adaptivity.
[ Applause ]
>> Thanks Jacob.
Hi, I'm Tony Ricciardi
and I'm an engineer
on the Interface Builder team.
Jacob just introduced a
few new concepts in iOS 8
for developing adaptive
UIs such as Size Classes
and Trait Collections.
Now I'm going to show you guys
a few new features we've added
to Xcode to help you
work with these concepts.
In Xcode 6, you can
customize your layout
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
In Xcode 6, you can
customize your layout
for multiple Size Classes
using a single Interface
Builder document.
That means you can now target
both the iPhone and the iPad
with one storyboard or XIB.
You can deploy these documents
backwards to older versions
of iOS, and you can preview your
layout for different devices,
orientations and OS
versions all within Xcode.
Now documents using this
feature will require Xcode 6
and auto layout, so
they won't be compatible
with older versions of Xcode.
Let's head over to
Xcode and take a look.
Okay, so here we
have the storyboard
for an adventure game I
have designed for the iPad.
Today I want to extend this
storyboard to target the iPhone.
Before I get started,
let me show you the app
running in the stimulator.
This is the main menu
for my adventure game.
On the left I have a few
buttons that control the content
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
On the left I have a few
buttons that control the content
that shows up on the right side.
The Play button takes
you to this menu
for choosing your character,
which can either be
a Warrior or a Mage.
The Store button takes
you to the store page
and this button takes
you to the Settings page.
As you can see, I've
implemented this UI using
a UISplitViewController.
Over here I have my
Primary View Controller
with those three buttons.
Up here, I have my
character menu and, down here,
I have my Store page
and my Settings page.
Each of these three
buttons is connected to one
of those Secondary View
Controllers using a
replace segue.
That means that when you
tap one of those buttons,
the Secondary View Controller
is going to be replaced
by the View Controller that
is connected to that button.
So as I mentioned today
I want to enable -
I want to extend the storyboard
to target the iPhone and to do
that I'm going to
enable Size Classes.
I'm going to head over
to the File Inspector
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I'm going to head over
to the File Inspector
and check the U Size
Classes box.
When I do that, I get
this dialog telling me
that my document is
going to be upgraded,
upgrading will enable
auto layout
and it will convert your segues
to the new adaptive segue types.
If you've viewed storyboards
before, you're familiar
with the usual segue
types like push, model,
and replace that allow you
to display View Controller
when an event is triggered.
In Xcode 6 we've added some
new segue types that correspond
to the new View Controller API
that Jacob discussed earlier,
like showViewController and
showDetailViewController.
When you enable Size Classes it
will automatically upgrade your
segue to those new types.
So, as you can see after
I enabled Size Classes,
my View Controller got
resized to this 480 by 480
to the second, this square
represents sizes of any width
and any height so, when you
see this square your edits will
apply to all Size Classes
unless you override your layout
for a specific size class.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
In my case, I've already
designed my layout to work well
for the iPad and now I want
to override my layout
for the iPhone.
To do that I'm going to use
this button here at the bottom
of the canvas, this
button allows me to choose
which Size Classes
I'm editing for.
Each of these squares in this 3
by 3 grid represents a
combination of a width class
and a height class, currently
the middle square is selected
and that corresponds to
any width and any height.
When this square is selected,
you're editing your default
layout, which is inherited
by all the other configurations.
In the top left, we have
a square for compact width
and compact height
and that corresponds
to an iPhone in landscape.
In the bottom right, we have
a square for regular width
and regular height, which
corresponds to an iPad,
and in my case since
I'm interested
in overriding my layout
for an iPhone in portrait,
I'm going to head over
to the bottom left
where I have a square
for compact width
and regular height.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
for compact width
and regular height.
When I choose that square, my
View Controller grows narrow
and tall to show me
that I'm now editing
for compact width
and regular height.
Also, you might have noticed
this bar at the bottom
of the canvas turns blue to
show me I'm no longer editing
for the default layout.
As you can see in this
configuration, my image view
and my buttons are
getting clipped.
So, this is going to
require a couple of fixes.
First I want to give my image
view a smaller image to use
when the width is
compact and second I want
to change those buttons
to be stacked on top
of each other vertically rather
than sitting side-by-side
like they are now.
Let's fix the image view first.
I'm going to head over to my
asset catalog using the jump bar
and then I'm going to
select my logo image set
and then I'm going to head over
to the inspector and up here
for the width attribute I'm
going to choose any and compact.
That gives me a couple
of new slots here.
The images that I put into
these slots are going to be used
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
The images that I put into
these slots are going to be used
at runtime instead of my default
images whenever the width
is compact.
I already have a couple
of images waiting here
in the finder, so I'm just going
to drag those over
to those slots.
Okay, now I'm going to head
back to the storyboard and,
as you can see, that image
view has been updated
with that smaller image and
it's no longer getting clipped.
Now let's fix these buttons.
I'm going to start by
just selecting the buttons
so you can take a look
at their constraints.
For those of you who are
unfamiliar with auto layout,
I'm not going to go
into too much detail
in layout constraints
for this demo.
I recommend checking
out our session
from last year's conference.
However, I do want to point out
that there is a hidden view here
between these two buttons
and that view only exists
to hold constraints.
It has a couple of constraints
centering it in its container
and it has a few more
constraints pinning the buttons
to it, but most importantly it
has this width constraint here
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to it, but most importantly it
has this width constraint here
and that allows me to control
how far apart the buttons are
from each other.
So, if I wanted to
move them farther apart
or closer together I
would just have to edit
that width constraint.
However, now that I want
these buttons to be laid
out vertically, I no
longer need this view or any
of its constraints to
hold the buttons together.
So, the next thing I'm going
to do is clear the restraints
by using this menu at
the bottom of the canvas.
I'm just going to choose clear
constraints and when I do
that the constraints
disappear from the canvas
and you might wonder if that's
going to cause my layout
to stop working for the iPad.
However, since I'm currently
editing for compact width
and regular height my
edits will only apply
to that configuration.
If we take a look
at the outline view,
you can see that those
constraints are still there
in my document they've
just been turned off
for the current editing mode.
Okay, the next thing I'm going
to do is remove this view
since I no longer need it
there to hold the buttons next
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
since I no longer need it
there to hold the buttons next
to each other horizontally.
So, I'm going to go over
to the Attributes Inspector
and down here at the bottom
we have this check box
that says Installed.
Installed means that the view
is present in the view hierarchy
at runtime as a subview
of its container.
I want this view to be installed
in all configurations except
for compact width and regular
height so, I'm going to add -
I'm going to use this "+" button
here to add a customization
for compact width
and regular height.
When I do that, I get a
new Installed checkbox and,
when I uncheck that box,
the view disappears,
and if we take another
look at the outline view
over here you can see that
it's once again still there
in my document but it's
just been turned off
for this editing mode.
[ Applause ]
Okay, now I'm just going to
drag around these buttons
so that they're going to be
laid out vertically rather
than sitting side-by-side.
Okay, then I'll select the
buttons and that label there
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Okay, then I'll select the
buttons and that label there
and I'm going to go back to this
menu and this time I'm going
to ask Interface Builder to
just give me some suggested
constraints for those views.
Okay, at this point I could
hit the Run button and try this
out in the iPhone simulator
but instead what I'm going
to do is show you how you can
preview your layout directly
within Xcode.
So, I'm going to go up
to the top and I'm going
to open the assistant
editor then I'm going
to use the jump bar to go
over to the Preview Assistant.
And since I'm interested in
previewing for the iPhone 4
in portrait, I'm going
to use this "+" button
and choose iPhone
4-inch, and there we go,
now I can see my layout
as it would appear
on an iPhone portrait.
[ Applause ]
If I wanted to try it
out in landscape I can use
this button here at the bottom
of the preview to
rotate it, or if I want
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of the preview to
rotate it, or if I want
to see both orientations at
the same time I can use the
"+" button again to add
another preview and now
that lets me see both
orientations at once.
As you can see in landscape the
buttons are once again sitting
side-by-side and that's because
we've only changed our layout
for compact width
and regular height.
An iPhone in landscape
corresponds to compact width,
compact height and so
those edits didn't apply.
Just in case you
didn't believe me,
now I'll actually run
this in the simulator.
All right, so here we have
my Primary View Controller
with those three buttons and, as
you can see, it's now fullscreen
since we're running on a phone.
When I tap the Play button it
takes me to my character menu
and those buttons are
stacked vertically just
like we expected, and
when I rotate they move
to sit side-by-side.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to sit side-by-side.
Let's recap what we
saw in that demo.
First, we used auto layout
to design a flexible UI
that works well for
multiple Size Classes.
For example, my image view
used a horizontal centering
constraint to position itself
for both compact width
and regular width.
Next, I showed you how
you can override subviews
and constraints for specific
Size Classes when you need to.
I did this to give my buttons
a vertical layout for iPhones
in portrait and a separate
layout for all other cases.
And finally I showed you how you
can preview different devices
and orientations, OS
versions, even localizations,
all within Xcode using the
Preview Assistant Editor.
Today you saw a lot
of new concepts
for developing adaptive UIs.
Jacob introduced you to Trait
Collections and Size Classes
which allow you to
modify your UI
in response to changes in size.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in response to changes in size.
He also introduced a
new API that allows you
to decouple Child
View Controllers
from their containers
and to collapse
and expand
UISplitViewControllers.
Finally I showed you some
new features we've added
to Interface Builder
and Xcode 6.
If you'd like more information
you can contact our Evangelists
or you can post on
the Developer Forum.
We recommend checking out the
other View Controller-related
sessions from this
year's conference
and we'll also be having an
Interface Builder session later
this afternoon in this room.
And with that, thank you
and I hope you enjoyed
Developing Adaptive UIs
[applause] in iOS 8.
-
[ Applause ]