WWDC2015 Session 231

Transcript

X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
[Applause]
>> LUKE HIESTERMAN:
Hello, everyone.
Thank you so much for coming
to Cocoa Touch best practices.
I am Luke Hiesterman, an
engineer on the UIKit team,
and it will be my
pleasure to walk you
through today a collection of
pieces of wisdom, practical bits
of advice that you can apply
directly to the applications
that you are writing
today and into the future.
So I am going to do this by
walking across a collection
of topics that will
be of strong interest
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
of topics that will
be of strong interest
to any Cocoa Touch application,
and in each of these topics,
I will have sort of a series
of best practice tips to give
to you, and those topics are
app life cycle, number one;
views and view controllers;
Auto Layout; and finally,
table and collection views.
Now, as I go across
these topics,
I am going to have a series
of goals that I want to impart
to you with each of the bits
of wisdom that I am giving
because everything is sort
of corralled towards
accomplishing a few basic ideas
that you definitely want
to have in your app.
So you know, number one
is going to be you want
to have peak performance.
Your apps want to
be silky smooth,
so you look like
rock star developers
and everybody loves you.
You also want to have a
top-notch user experience
in your app.
That way everybody thinks
that your app looks
polished and it's awesome.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that your app looks
polished and it's awesome.
And finally, you want to
write your code in such a way
that it's as future-proof
as possible so that
as future versions of iOS
come out, you're writing
as little code as possible to
adapt to those revisions of iOS.
So those are the goals we
are going to have in mind
as we go through these topics.
And I will start off by
talking about app life cycle.
The very first best practice
I want to impart upon you has
to do with the very first
experience that the user has
with your app, and
that's launching it.
So the first best
practice is launch quickly.
That's how you appear responsive
when the user taps on your icon
and right away they get your
app ready to interact with.
And the way that you launch
quickly is extremely simple.
It's return quickly from the
Application Did Finish Launching
UI application delegate event.
That in itself is really simple.
I am sure you all
already know how to do it.
Take all the long-running work
you might have to do to set
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Take all the long-running work
you might have to do to set
up an application
and defer that out
of Application Did Finish
Launching because you want
to return as fast as possible
doing the minimum amount
of work, set up a
basic UI for your users
to interact with, and return.
So if you are loading data,
whatever you need to do,
from a database,
network, defer that out
of Application Did
Finish Launching.
If you take too much time, of
course, your app will be killed
because it just looks like
it's hung to the system,
so you really want to return
as fast as possible from that.
Now, being a superresponsive
app doesn't end
at application launch.
We want to think beyond that.
We want to be superresponsive
all the time.
So I want to delve deeper into
this technique of what it means
to be responsive in general
so we can build a technique
that works not just
at app launch
but throughout the
life cycle of your app.
So even though I just talked
about deferring all of this work
out of Application
Did Finish Launching,
what we are really getting
at for a best practice
isn't just about asynchrony.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
It's actually about taking
long-running work and putting
that on background queues.
If you need to load
data from a database,
if you are hitting the network,
that's work that can be
done in the background.
So if we revisit Application
Did Finish Launching
and we have this sort
of very simple approach
to a naive Application Did
Finish Launching, you know,
we see we load our data directly
in Application Did
Finish Launching,
and I just said defer that.
Okay. So we can do
that pretty easily.
We dispatch that, and it's gone.
It's out of Application
Did Finish Launching.
We are able to launch quickly,
and things are better at launch.
But that still introduces
the possibility
of blocking the main
queue later on and, thus,
blocking user interaction.
So really, the best
practice is move that work
onto a background queue so
that whenever it does run,
user interaction
continues to happen,
and your application seems
responsive all the time.
So this technique, putting
the work on background queues,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So this technique, putting
the work on background queues,
can be applied anytime in
your app, not just at launch.
Then, you take that work that
you do in the background working
with data, and when
you are done with it,
that's when you come back to
the main queue to interact
with UIKit elements like
views and view launchers.
So that's being really
responsive.
The next thing you
want to do besides
that first launch is
be superresponsive
on the second time the
user launches your app,
and the third time, and so on.
And this comes from the fact
that when the user exits your
app, the app doesn't just die,
it goes into a suspended
state on iOS.
And so to be superfast the
second time the user goes
to your app, you really
just want to resume
from that suspended state,
and that's contingent upon
you still being in memory.
So if we take a look
at a picture
of what system memory
looks like, you know,
we know that some of it
is taken up by the kernel
and the operating
system processes.
A good chunk is going to be
taken up by the foreground app.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
A good chunk is going to be
taken up by the foreground app.
And then a bunch of it
is going to be taken
up by background apps.
Now, you'll notice that
there's one sort of hog
of a background app in
this picture that's using
up more memory than
everyone else.
You don't want your
app to be that app,
and the reason you
don't want that is
because that app is the
first one that's going to die
when the foreground app
needs additional memory.
So you want your app to be the
one that's using UI application
delegate methods to know when
it's going into the background,
get rid of unneeded
memory resources,
take its memory footprint and
get it as small as possible
when it's going into
the background.
This is even more
important in the world
when we have split view
and there can be
multiple foreground apps.
You know when a second
foreground app comes,
that big hog of an app
isn't going to survive.
So you don't want to be that.
So that's being superresponsive
and thinking
about performance throughout
the life cycle of your app.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
about performance throughout
the life cycle of your app.
The next best practices I want
to talk about with respect
to general application
programming is
leveraging frameworks.
Now, this is maybe the most
basic best practice I can give
to you, but that's: do it!
Leverage frameworks
that Apple provides.
We spend our lives throughout
the year building great
frameworks for you to build
on top of, and doing so comes
with several basic advantages
that I am sure you
are familiar with.
It reduces your maintenance
burden.
You know? If you use UI
Navigation Controller,
for example, then you don't have
to maintain the Navigation
Controller across releases
as you would if you built your
own Navigation Controller.
And as we make improvements, you
get those improvements for free.
For example, you know,
Navigation Controller
gained a swipe gesture
for going back a
couple of releases ago.
Everyone who built on top of it
got that improvement for free.
If you had your own, you would
have had to go implement it
or not have a feel that fit
with the rest of the system.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
or not have a feel that fit
with the rest of the system.
So that's what you want because
you really want to be able
to spend your time focusing on
what makes your app special.
That's what we all want.
We want you to write fantastic
apps and spend your time
on that rather than things
that you could leave
to our framework.
So that's what we want
to encourage you to do.
And of course, while
you are doing that,
something that you're
going to have to keep
in mind is how you
deal with versioning.
So one of the biggest
questions we get is:
How many versions should
apps deploy against?
And our advice to
you is going to be,
target the two most
major releases of iOS.
So starting this fall when iOS
9 comes out and going forward,
that's going to mean
iOS 8 and 9.
This technique will give you the
best mix of getting a whole lot
of users while not taking
on the maintenance burden
of deploying back
several iOS releases
and having to deal with that.
Now, in this process,
you might find yourself
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Now, in this process,
you might find yourself
at times needing specific
logic to check the version
of what system you're on.
And another best practice for
that is going to be to make sure
that you include
fallbacks for your logic
that is based on system version.
So that means definitely don't
write code that looks like this,
where you check for
a specific version
like iOS 9 before
doing something.
If you make a check like this,
it is almost certainly going
to cause a bug in your program
when iOS 9.1, for example,
is released that causes
this check to fail.
Instead, you want to think
about anything that's
in iOS 9 is going to be in
future releases, so any logic
that you put in iOS 9 you'd
want for versions greater
than or equal to iOS 9.
And even better, if you are
writing your app in Swift,
you can take advantage of the
new pound availability syntax
to put all your version-specific
code into a block
that the compiler can
understand and reason about
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that the compiler can
understand and reason about
and let you know if you
are doing anything wrong
for a particular version.
Whichever technique
that you end up using,
think through whether you
need to have an Else clause
because you don't want
to make the mistake
of putting some specific logic
in that handles a core piece
of your application for some
system version but fails
to do other work that it needs
to do if it's not that version,
and you get a bug on versions
that aren't what you expected.
So that's some basic
best practices
for general application
life cycle.
Let's talk about view and view
controller best practices.
And the first idea I want
to hit there is how we think
about layout on modern devices.
You all know that last
fall we introduced iPhone 6
and iPhone 6 Plus, and I am
sure you are aware that along
with this we had
four new dimensions
that had never been
seen on iOS devices
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that had never been
seen on iOS devices
for your apps to lay out in.
When you add that to the
dimensions of various iPhones
that we already had plus
iPad, now the matrix
for possible dimensions that
your app needs to lay out in,
especially when you throw
in split view on iPad Air 2,
that matrix is fairly large.
So it no longer makes
sense to build layouts
that are built specific
for a particular dimension
that your view controller
expects to be in.
Instead, layout in general wants
to think of itself as being done
to proportions, and we do
that by specifically avoiding
hard-coded values in the layout
of our views and
view controllers.
If we imagine a view that simply
puts a label into a superview,
we might have a couple of
years ago thought of the layout
of this as being done as a label
described as 260 points wide
with a 30-point margin
from the left.
We don't want to do that
because we want to think
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
We don't want to do that
because we want to think
about the dimension scaling.
Either or both of
them might scale.
In this case, if the width
scales, this layout breaks,
it just doesn't work because the
offset no longer makes sense.
So if we had instead thought
of this as a centered label,
then that makes sense
as the dimensions scale.
And we will revisit this
example a bit when I talk
about Auto Layout
best practices.
I want to talk a little bit
about an API that we introduced
in iOS 8 to help you
with this idea of laying
out to proportions because
part of the goal was to get rid
of the idea of orientation.
You know, we no longer want you
to ever think about orientation.
In fact, I am going
to tell you if,
when you are designing your
app, you have the thought
in your head that thinks
about portrait or landscape
or you have that
conversation with your designer
where the word "portrait"
or "landscape" comes out,
you are already thinking
about it wrong.
We only think about
things in terms of size.
And so size classes are here
to help us think about things
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And so size classes are here
to help us think about things
in terms of size, do
proportional layout,
while also recognizing
and embracing
that there are certain
size thresholds
where the fundamental
UI we have changes.
As an example, Settings
on iPhone 4S is a simple
one-column table view.
When we go to the iPhone
5, it's still a table view.
It's just a little taller.
On iPhone 6, it's
taller and wider,
still basically a table view.
iPhone 6 Plus, bigger still.
However, when we
transition to iPad,
we cross a certain
width threshold
where now this view changes
fundamentally how it appears.
It's now two columns
of scrolling content.
So we've crossed
some threshold there.
And in fact, you find that that
same threshold has been crossed
when we view in iPhone 6,
and I will use the dirty
word, landscape mode.
And size classes are
the API that's there.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And size classes are
the API that's there.
For Apple to communicate
to your app
where those fundamental
thresholds are crossed
so that you can then react
to those thresholds and think
about having a fundamentally
changed UI according
to those thresholds.
And you get notified of
those thresholds changing
as size classes are packaged
in UITraitCollection objects,
which your view controller
will have access to.
So that's layout.
The next best practice I
want to impart upon you is
to use properties
in your classes
as an alternative
to tags on UIView.
So what I mean here is if
you are using View With Tag
or Set Tag UIView API
and shipping code,
I am going to encourage
you to move away from that.
Reasons are this is just --
[Applause]
Thank you.
I am really, really glad that
somebody is happy about that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
I am really, really glad that
somebody is happy about that.
Yes. So I mean, the reasons
for this should be obvious.
It's just an integer, and it
has collisions potentially
with other code.
Maybe it's other
code that you write.
Maybe it's the new guy on
your team who doesn't know
about your carefully
managed integers.
Maybe it's a framework
that you use
that you have no
visibility into.
And whenever these
collisions happen,
you get no compiler
warnings about them.
The compiler has
no way to reason
about your integer management.
And when you not only do
not get a compiler warning,
but any runtime errors you
get will not say anything
about your use of
View With Tags.
At best, you'll get a crash
for unrecognized selector.
You won't know what happened.
As a replacement to
this, declare properties
on your classes, and then you
will have real connections
to those views that
you need later.
As a simple code example,
imagine that I wrote some code
that creates an image view, and
I keep track of it with a tag
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that creates an image view, and
I keep track of it with a tag
of 1,000 because I'm sure
in all my cleverness nobody else
will ever use a tag of 1,000.
But then I watch my own
talk, and I say no, no,
let me create an actual property
that declares it
a UI image view.
Then I keep a real
reference to that view
that also has better type
information because View
With Tag only is a type UI view.
Now that I use a
property-typed UI image view,
the compiler can actually reason
about what I do and help me
out if I make mistakes.
So please heed that.
The last best practice for
view and view controllers is
about making timing
deterministic.
This is, for those of you who
may have been in the position
of doing something
alongside a system animation
or you have some work
that you want to fire off
when an animation is
complete, and so you are left
in the position of trying to
make a guess about how long
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in the position of trying to
make a guess about how long
that animation is going to take
and perhaps implementing
an NSTimer
to take care of that
time for you.
Well, you don't want
to do that because
that introduces indeterminism
into your app,
especially with the possibility
that animation timings can
change from release to release.
You are really the
opposite of future-proof
if that's what you are doing.
Instead, leverage
UIViewTransitionCoordinator,
an API on UIViewController,
to know what the timings are
for the animations
that you have.
This has the capability
to let you do any
animation you want alongside
of view controller transition.
You know for sure when that
transition is completed.
And it has built-in support
for cancelable animations
and interactive animations.
So if you imagine that
navigation swipe gesture again,
the user may move his or
her hand back and forth,
changing the speed,
direction, and even decide not
to pop the view controller
and cancel it altogether.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to pop the view controller
and cancel it altogether.
If you use the Transition
Coordinator, you are prepared
to handle all of that.
Let's talk about Auto
Layout best practices.
Auto Layout is a tool that I am
sure many of you know and love,
and it's kind of
built there designed
to help you be adaptable and
future-proof in your code.
And of course, we are
going to talk about that.
Future-proofing is one of
the goals with Auto Layout.
But first I want to hit
on some best practices
for high performance
in your Auto Layout.
And that's going to start
with managing your constraints
in the most efficient
way possible.
So the way that you do that is
imagining all the constraints
that will be in your view and
identify those constraints
that might change throughout
the lifetime of the view.
What that does when you identify
what changes is you'll be able
to make targeted changes
and not change the things
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to make targeted changes
and not change the things
that don't need to change
because when you keep
some things constant,
you allow the Auto Layout engine
to optimize for those things
that don't change and,
therefore, it doesn't have
to make certain calculations
over again, and your app lays
out faster, which is
especially important
if you are doing
re-layout during scrolling
or something else
user interactive.
So part of that, a
definite best practice --
while this is a worst practice
and the best practice is
to avoid the worst practice --
is removing all the
constraints from a view.
This is bad not only in
terms of the performance
of your app enforcing
the Auto Layout engine
to do the most work possible,
but it's also actually a
potential compatibility issue
because future versions of iOS
may have additional constraints
that the framework has added,
which you'll be removing
when you call Remove
All Constraints.
So you want to avoid calling
Remove All Constraints
on a view as much as possible.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
on a view as much as possible.
So the way that you sort of
tie this together and are able
to manage your constraints
efficiently is
by having explicit references
to them using the same strategy
that we just talked about
by replacing view tags,
having actual properties
that point out the views
or the constraints
that you might need
to change throughout the
lifetime of the view.
So we can look at a
very simple example
of how you might write your
update view constraints code,
and this does the most
naive thing possible,
and that is it says hey, I
need to update my constraints.
Let me just remove all of them,
and then I will recalculate
them and add them back.
We don't want to do this.
This is not the best practice.
The best practice is if we have
one constraint, for example,
that needs to be changed, we
can remove that constraint,
rebuild just that
constraint, and add it back.
The Auto Layout engine again
knows what didn't change
and is able to optimize
for us around that.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and is able to optimize
for us around that.
So the next set of best
practices I have for you
around constraints
are around this idea
of how specific you are when
you describe your constraints.
In general, you want
your constraints
to describe your layout exactly
as precisely as is necessary.
That is, you want to
say what is needed
to get the layout you
desire, and you don't want
to say any more, and you
don't want to say any less.
And there are potential problems
that can happen on both sides
of this specificity problem,
and I am going to talk
about each of these now.
The first one is a
performance problem.
So the first one is about
adding duplicate constraints
to your views.
Duplicate constraints are those
ones that if you removed them,
the layout would
be exactly the same
because they're just implied
by what's already there.
And when you have that, it
causes the layout engine
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And when you have that, it
causes the layout engine
to do more work than it
needs to because it's solving
for these constraints
because they are there,
but it didn't actually
need to solve for them.
An example of this can be seen
in this sort of simple layout.
I've got a couple of
views inside a superview,
and I might describe the layout
first by doing the vertical axis
and say, hey, there's
some margin between my top
and bottom view, and I give
it an alignment option to say
that the left edges of both of
those views are also aligned.
Then I say, okay, let me go
to the horizontal dimension.
I provide some spacing
for the top view,
so now I know what
its left margin is.
Then I think, well, I've got
to specify that bottom view
as well, so I specify a
margin for that bottom view.
But what I've just done
is provided a margin
that I really didn't need in
that bottom view left margin.
Since I already knew what
the top view's margin was
and I also knew that the
left edges of the bottom
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and I also knew that the
left edges of the bottom
and the top view were going
to align to each other,
this view hierarchy would
have laid out exactly the same
if I hadn't specified the left
margin of the bottom view.
So that extra constraint just
causes the engine to do work
that it doesn't need to do.
Get rid of that,
we'll be faster.
The next problem that
can happen as a result
of overspecifying your
constraints actually isn't a
performance problem, but
it's an adaptability problem.
It's a future problem
for your app.
And that's when your constraints
simply aren't flexible enough.
So if we think about hard-coded
values, we know we hate them,
and let's go back
to this example
that I promised we'd come back
to about a label in a view.
Again, if we think about this in
terms of it's a 30-point margin
from the left and
it's 260 points wide,
we might describe
its constraints
in terms of those hard values.
But those hard values cause
us to be rigid and unchanging,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
But those hard values cause
us to be rigid and unchanging,
which kind of defeats the
entire purpose of Auto Layout
as a future-proofing tool.
What we really want to do is
describe our constraints using
the bounds of the
views that they're in.
So this should have been
something that used the bounds
of the superview and
described minimum margins
around that view.
So let's talk about the other
side of constraint specificity,
which is underspecifying
your constraints.
You don't want to
do that either.
You want to make sure you've
specified everything you need.
If you think about this view
here and imagine what happens
if we underspecified, we'd
be introducing ambiguity.
So focus in on constraints
I might have specified here.
If I set left and right
margins around this label
and I set a top margin as well,
there's something missing,
and it should be
pretty obvious to you
that there's no bottom
margin, and there needs to be.
And there needs to be because if
it's ambiguous, it's undefined,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
And there needs to be because if
it's ambiguous, it's undefined,
and that means my
view might come
out different ways
different times I run my app.
Maybe if this is a table cell,
it changes when I
call Reload Data,
and I am mystified by that.
Maybe it will change on the
next version of iOS because,
you know, the cosmic rays
hit the phone differently.
Who knows.
We don't want undefined
behavior.
We don't want our view to
come out looking like this.
We want it to be the height
that we want it to be.
So make sure you fully
specify your constraints.
I want to give you a best
practice in terms of testing
and debugging your
Auto Layout code.
You can use a method on UIView
called Has Ambiguous Layout.
If you are in a debugger,
you are trying to figure
out why your view isn't
laying out the way
that you expect it to,
call Has Ambiguous Layout.
It will let you know if
there's ambiguity in your view.
Moreover, you call this method
on a UIWindow, it will tell you
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Moreover, you call this method
on a UIWindow, it will tell you
if any view in the window
tree has ambiguous layout.
So that's pretty handy.
You can call UIView Auto Layout
Trace, then, to get a picture
of all the constraints
throughout your entire view tree
and use those constraints
to go find ambiguity.
A really interesting
best practice is
to take these methods as they
are, put them into a unit test.
You can imagine for each view
tree, each basic UI in your app,
you could call UIWindow
Has Ambiguous Layout,
and if it does have
ambiguous layout,
then you could call UIView
Auto Layout Trace to find
where the ambiguous
constraints are.
Just package that up into a
report, and then you have a test
which both lets you know
when there's ambiguity
and provides debugging
information
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
and provides debugging
information
for whoever comes along and sees
that there is a failure
in the test.
So that's a great best
practice you can use
with your Auto Layout apps.
[Applause]
All right.
So I will transition now to our
last topic for best practices,
and that's table and
collection views.
I know that this is
something that is important
to almost every iOS
app out there,
and it's certainly
important to me, too.
So the first best practice
is use self-sizing cells
when you have content
that needs to change --
or you have cells that need
to change size based
on the content.
I am sure most all of
you have at some point
in your iOS development
life been in this situation
where you have a basic table
view with some content in there,
and you realize,
oh, each cell needs
to be a different height based
on the content that's in it.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
to be a different height based
on the content that's in it.
I can't just have one
height for every cell.
And self-sizing cells introduced
in iOS 8 make it easier
than ever to transition
to what you really want,
which is a table view where all
the cells are the height they
need for the content.
So I'll run through the
best practice mechanism
for how you get self-sizing
cells in your app.
And it starts just
like we talked
about in the Auto Layout section
by fully specifying
your constraints.
You want to use all those
tips that I just talked about,
thinking about this idea of
your Auto Layout system is this
machine that's taking
width in as an input
because the table view
has a fixed width,
and so your cell is
going to be that wide.
And then it's producing as an
output the height of the cell.
So any ambiguity in there,
if you haven't fully
specified your constraints,
comes out as the height
isn't what you want it to be.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
comes out as the height
isn't what you want it to be.
If we use the simple
example of a table view cell,
here it's really easy.
We can just put margins
around all of our content,
which in this case
is just a label,
and it has an intrinsic
content size.
So when we put margins
around it,
we fully specified
the constraints
of this particular cell, and
we'll get the size that we want.
You, however, might have some
more complex cells than this.
I understand that
this is an easy case.
And if you are in the position
where you find, you know, hey,
I've specified all
my constraints,
but I am not getting the height
that I thought I should get,
I want to give you a tip, which
is try adding a constraint
to your content view
that specifies the height
of the content view.
So you are using, in fact,
a height constraint
on the content view.
Then you can specify that
in terms of your content.
Here I can say hey, content
view height should be equal
to the height of the label
plus my top and bottom margins.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
In this case, that's
repeating work.
I don't really need
to do that here,
and I'll get the same thing.
But if in your app you are
not getting what you expect
and you add a height
constraint to your view,
and then that causes the
height of the cell to change,
then that's a great indication
that your constraints aren't
giving you quite the logic
that you expected them to.
So that's a great tool that
you can use to figure that out.
Now, once you have that,
you might want to think
about how you animate the height
changes of your cells, you know,
even using self-sizing
cells and Auto Layout.
Now, you can imagine if you had
some cell in here that you want
to change its content, you might
take the very naive approach
and update your model and
then call Reload Data.
And if you do that, it's
going to look like this,
where it snaps to the new
position of the table,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
where it snaps to the new
position of the table,
and it gets the job done, but it
just isn't the user experience
that you wanted out of your app.
It doesn't quite look as
polished as it should be.
What you wanted was to have
that cell animate its height
and the cells around it
animate their positions smoothly
so everything dropped into place
and the user understood
what was happening.
So let's walk through
how you do that.
Thankfully, it's pretty simple.
Whenever you want to
specify a geometry change is
to be animated in table
view, you use a begin update
and update block with
the Table View API.
So first step is to call
Table View Begin Updates.
This is true whether you are
using self-sizing cells or not.
This is the general way you
animate geometry changes
in table view.
Then you update your model.
That's easy.
Third step is if you're changing
the height of an onscreen cell,
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
Third step is if you're changing
the height of an onscreen cell,
you can just reach into
that cell, get a reference
by calling Table View Cell
For Row At Index Path,
and change the contents
of that cell,
even changing the
constraints as needed.
Sometimes people think they need
to call Reload Rows
Of Index Path.
You don't actually
need to do that,
and it won't get you quite
the optimal experience.
You actually can just
reach into the cell
and change its contents.
Then when you are
done with that,
you say Table View End
Updates, and table view
at that time recalculates
the geometry of all the rows,
including asking all
the onscreen rows
for their Auto Layout
information to get their height,
and everything animates
into place as you saw.
So begin updates, end
updates is the key to that.
So the last best practice
that I want to give you is how
to implement custom
collection view layouts
that invalidate themselves and
are very fast as they do it.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
that invalidate themselves and
are very fast as they do it.
So I know this comes up a lot,
people write custom
collection view layouts,
and they're doing something,
they are changing something
about themselves as
the user is scrolling,
and they have a hard time
keeping up with that layout.
Well, I am going to tell you
exactly how the Photos app
in iOS does this job so that
you can take that technique
and put it into your
custom layout that you have.
So the Photos layout has this
header, which is expressed
as a supplementary view
in collection view terms,
and when the user scrolls,
even though the cells move
with the scrolling, that header
view stays in place on screen.
This is the same basic idea
that I know many people want
to implement in their
collection views,
and the Photos layout
is able to do this
by using a UICollectionView
invalidation context instance.
This is API that you can
find in UICollectionView.
So the steps to this
are just a few.
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
So the steps to this
are just a few.
Number one is the most obvious.
The Photos layout is invalidated
on every bounds change.
So every frame as the
user is scrolling,
the Photos layout
gets invalidated.
Piece of cake.
That's the easy part.
The question is, how
do we make that fast?
And the answer is, the Photos
layout builds a targeted
invalidation context that is
specified to invalidate just
that header view so that
the collection view is able
to optimize, understanding
that the only view
that is being invalidated
is the header view
and none of the cells are.
That allows the collection
view to do the entire operation
as fast as possible,
and it's so fast
that this can just be
repeated as necessary
at frame rate scrolling as fast,
even though the layout is being
invalidated on every frame.
So you can use that same
technique in any layout if,
in general, if you have
performance concerns using a
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
in general, if you have
performance concerns using a
custom layout.
Generally speaking,
UICollectionView
invalidation context is the key
to overcoming those
performance concerns.
So I encourage you to
check out that API.
Okay. So I've talked
about a whole collection
of best practices here.
We talked about performance
and how
to make your apps
superresponsive at launch
and throughout its
life cycle and how
to make your Auto Layout
as fast as possible.
We've discussed user experience
as one of your great goals
and animating your table views
around and laying out properly
across the myriad
of iOS devices.
And of course, I've given you
tips for how to write your code
in the most future-proof way so
that it's running on versions
of iOS for generations to come.
Now, I encourage you to use
this entire talk as a reference,
something you can come
back, watch the video
as you are building
your future apps.
There's a lot of best practices
here that you can use this
X-TIMESTAMP-MAP=MPEGTS:181083,LOCAL:00:00:00.000
There's a lot of best practices
here that you can use this
as a launching board to then
go into the documentation,
look up the specific APIs
that I've referenced,
and you will be able to
put that to -- well --
best practice in all
of your future apps.
So you know, with that, I
thank you, and I am glad
that you have come here to WWDC,
and I hope you have a best rest
of your afternoon possible.
[Applause]