WWDC2016 Session 236

Transcript

[ Music ]
[ Applause ]
>> Wow. Good afternoon,
everybody.
Good afternoon and welcome.
You want to create great apps.
And having a great layout is
part of making a great app.
Auto Layout makes it easier
for you to build your layouts,
targeting different
devices, orientations,
adaptations as well as layout
for different languages.
Today we are going to talk
about what's new in Auto Layout,
and this session is broken
up into three subparts.
First is going to be with me.
My name is Jason Yao.
I'm an Interface
Builder Engineer.
I'm going to tell you
what's an Interface Builder.
Second, Jesse Donaldson from
AppKit will tell you what's new
Second, Jesse Donaldson from
AppKit will tell you what's new
in Cocoa, and then
third, Marian Goldeen
from UIKit will tell you
new debugging techniques.
With these tools and techniques,
you will save yourself
developer time and effort
when you apply them
to your workflow.
So, let's get started shall we?
The first topic I want to
talk about is something
that I'm calling incrementally
adopting Auto Layout,
incrementally adopting
Auto Layout.
What do I mean by this?
Well, when you are laying
out your views inside
of Interface Builder,
you do not need
to constrain everything at once.
You can do it incrementally
to save yourself time,
simplify your setups, as well as
give yourself more flexibility.
But before I show you how to do
this, let's first take a look
at an example to
set the backdrop.
So it all starts by taking
a view and dragging it
onto Interface Builder Canvas.
You give it a size and position.
You compile it and run.
And you're going to get that
view as sized on the device.
And you're going to get that
view as sized on the device.
Then you go head and rotate
the device, and you realize
that it's fitting the same
size but there is some work
that needs to be done.
So what's going on here?
What's going on is we're
simply pinning this to the top
and to the left with
the width and height.
In fact, what we're doing is
we are implicitly creating
constraints during compile time
so that this view holds the size
in -- with the Auto
Layout Engine.
If you needed more
dynamic resize behavior,
what you would need to do is
go to Interface Builder Canvas
and add your own constraints.
And the question is could there
be a better way of doing this,
perhaps a way without
constraints,
to get simple resize behavior?
There can be.
New to Xcode 8 is the ability in
Auto Layout documents to be able
to specify autoresizing
masks on your views
that do not have constraints.
What that means is that you
can get simple resize behavior
What that means is that you
can get simple resize behavior
by specifying autoresizing
masks.
For those of you who remember
a time before Auto Layout you
might recognize this UI.
[ Applause ]
This is the Springs & Struts UI.
You can pin things
to the edges as well
as decide whether it's going to
be width or height resizable.
And these views are
going to work great next
to your constraint views.
In fact, what we are
going to do is we're going
to take those autoresizing
masks and we are going
to translate them
into constraints.
But we are going
to translate them
into constraints at runtime.
And there is a key
distinction there.
We're generating them at
runtime and not at build time
because it's going to be more
flexible and more transparent
to you, giving you
greater control if you need
to do anything programmatic to
these views, because for those
who are pro-Auto
Layout users out there,
you might recognize this flag.
On the view, we are
simply setting
translatesAutoresizingMask
IntoConstraints equals true.
translatesAutoresizingMask
IntoConstraints equals true.
What about for the views
that you've added constraints
to inside of Interface Builder?
They're going to be
the same as before.
You click on the view.
You are going to see
all of the constraints
that are affecting that view.
Autoresizing masks are
going to be ignored
and translatesAutoresizingMask
for these views will be false.
So, putting all of this together
back to the original problem,
we can incrementally adopt
Auto Layout by first starting
with autoresizing masks to so
the simple resize behavior,
and then add constraints for
the more sophisticated behavior.
And that gives you
greater flexibility.
When you are ready to add
constraints, choose the subtree
that you want to
add constraints to,
start from the parent,
and work your way in.
This allows you to adopt
Auto Layout on your terms
and is great for new
layouts that you are going
and is great for new
layouts that you are going
to be building to get that --
the simple resize behavior,
but it's also great
for documents
where you've been
wanting to layout.
Well, now you can
while preserving your
existing behavior.
I'd like to show you a
demo of this in action.
So, let's take a
look at what this is.
So, I've got Xcode 8 open
with Interface Builder.
I am going to build
a weather app.
I've got my views
and I've dragged them
into the View Controller,
and they are fitting
to an iPad portrait.
And so, let's take a look
at what it's going to look
like in landscape by going to
the new device configuration bar
and switching it to landscape.
Clicking over here, you
can see there's work to do.
So, let's go ahead and
start with the top banner.
And we want this to be
stretched across the superview.
Instead of adding constraints,
I haven't added any constraints
to this yet, I can go
into the size inspector.
And of course we've got the
Autoresizing Mask Inspector
over here, and all I need to
do is make it width resizable
and pin to the other edge.
Now, I want to work
on the children.
I'm going to go to the moon,
and by default this is pinned
to the top and to the
left, the way I want it.
As for the cloud, I'm going to
pin it to the right and get rid
of the pin for -- to the left.
And then for the label, I want
this to proportionally resize
and proportionally precision.
So, I'll get rid of
the pin to the left,
make it width resizable, and
you can see there is a little
animation showing me the
behavior it's going to be.
And then we'll just pin
it to the top and bottom
and make it height
resizable as well.
Now when we take a look at
this thing, we can switch it
to landscape, and it's
looking pretty good.
Let's try two-thirds
split screen.
It's almost there, but
my label is truncated.
It's almost there, but
my label is truncated.
What's going on there?
What's going on is
as you take a look
at the label it's
doing the right thing.
It's using autoresizing mask,
and it's proportionally
resizing based on its superview.
Autoresizing masks do not take
into account the content
size of your view.
I can fix this up by
using constraints,
but for this particular case UI
label, I do have another trick.
I'm just going to go into
the Attributes Inspector
and then switch it
from a fixed font size
over to a minimum font size.
That way it's fitting into
the frame that it's given.
So, now when we go back
to landscape full screen,
it's looking pretty good,
as well as portrait.
Next, we are going to deal
with the temperature
control in the middle.
I want a more sophisticated
alignment for this.
So, I'm going to
use constraints.
What I could do is I'm
going to hold down --
I want to constrain the
75 to the superview.
So, I'm going to hold down
control on the keyboard,
drag out a connection
to the superview.
It brings up the constraint
menu where I can hold down shift
It brings up the constraint
menu where I can hold down shift
and add multiple
constraints at once.
So, I want to center it
horizontally and vertically
and hit add constraints.
Similarly, I want
to do the same.
Control drag to the
sunny, hold down shift,
and we want to horizontally
space this as well
as add a baseline constraint.
And then do the same to the sun.
Control drag, hold down
shift, horizontal spacing,
and this time center vertically.
Now, my views have the
constraints that they need.
And we just need to
update the frames
so that they are in position.
And so we are going to go to the
Resolve Auto Layout Issues menu,
for the selected views
and hit update frames.
Next, I want to make the
temperature control even larger.
So, I'm going to click on that
and just increase
this font size.
And you might notice that
when I increase the font size,
because everything is already
in place, Auto Layout is going
to automatically
update my frames for me.
We do more automatic update
frames for you in Xcode 8.
So, next, we want to
have a nice back drop,
and this is a sunny day.
So, we're going to go ahead
and add an image view.
From the object library, I'm
going to go ahead and drag
out that image view,
size it to my superview,
and then set as image, as
well as choose its fill mode.
In this case, we probably want
something like an aspect fill.
Now, we want to make sure
that this thing is
sizing to its superview.
And we could add
constraints, four of them,
and pin them to each side.
But for this simple resize
behavior, we don't even need
to add constraints because we
could go into the Size Inspector
and just use autoresizing
masks and pin it to all edges,
making it width and
height resizable.
Last but not least, let's go
ahead and put this to the top
of my document outline.
So, it's in the back.
And then we're going to clear
the background on my banner
And then we're going to clear
the background on my banner
because this is a
visual effect for you,
and we now can see
the nice translucency.
And then we'll test it out.
So, it's looking good --
looking good right now.
It's looking good in landscape,
two-thirds split view --
two-thirds split
view and portrait,
as well as iPhone 6s Plus.
And so that is how you can
incrementally adopt Auto Layout.
[ Applause ]
The next topic I want to talk
about is a little
more advanced topic.
And it is about mixing design
and runtime constraints.
There are situations where
you could run into this type
of thing where you are
laying out your views inside
of Interface Builder and
you're just adding constraints,
but you do not know all the
constraints you're going to add
to views until runtime,
maybe because it's based
on information like time of day
or like data that you're loading
that only the run
time app knows.
There are three tools and three
examples I want to walk you
There are three tools and three
examples I want to walk you
through for dealing with
these type of situations.
The first is the use of
placeholder constraints.
In this situation, I've got an
image, and I want to go ahead
and center it in my device both
horizontally and vertically,
also a little bit inside
form the leading margin.
I also want the image to
maintain its aspect ratio.
However, I don't know what
that final image is going
to be, not until runtime.
And so, in order to assimilate
the approximate size and layout
so I can see the Interface
Builder, I can go ahead
and add a 4 by 3 aspect ratio,
approximately what I think
it might be, and then mark it
as a place order constraint.
Therefore, it would be
removed at build time.
When I actually get the image
that I'm going to be setting
in runtime, I'll go ahead
and create the real aspect ratio
constraint and apply it in.
For the second example,
pretend you are creating a
custom control.
Your custom control is going
to drive off of something
like US -- UIView or NSview.
It's going to provide its own
drawing, and it also is going
to manage its own content.
It may want to even
provide its own size.
It could provide that size
to the Auto Layout system
by specifying its
intrinsic content size.
Interface Builder is
not executing that code,
and so it doesn't know what your
intrinsic content size is going
to be, but you can simulate
the approximate size of it
by giving it reasonable
values using the intrinsic
size placeholder.
Therefore, you could
also see how it is going
to look like in your layout.
Just remember for your custom
controls, if you're going
to do this, that you do need
to provide its real
intrinsic content size,
and you can do this
by overriding the intrinsic
content size property
on your custom control.
The final example is the
one that's new to Xcode,
and I'm leaving it for last
because it should also be the
option of your last resort,
when you have exhausted
your possibilities
for both placeholder
constraints and design
for both placeholder
constraints and design
and intrinsic content
size placeholders.
And so, we're giving
you the ability
to tune the ambiguity warning
levels on a per view basis.
What that means is that for
this view I want to go ahead
and center it on
screen, vertically,
but that's all I know.
I don't know what horizontal
position it's going to be
or what size it's going to be.
In fact Interface Builder
is drawing this thing is red
because it's warning me that it
doesn't have enough constraints
to be able to position it.
It's ambiguous.
I know that I'm going to be
adding this constraints later
on at runtime when I have all
the information that I need
so I can go ahead and remove
the clutter from the --
from my work space by going
to the ambiguity setting
and switching it from verifying
always to either verifying
for position or never verifying
at all, and I'm promising
to myself I will add those
constraints later on so
that the view holds its size
before the first layout pass.
that the view holds its size
before the first layout pass.
These are the tools
for being able to work
with designer runtime
constraints
when you've exhausted
the possibilities
of constraining everything you
can inside of Interface Builder.
Well, what we've seen is how to
incrementally adopt out a layout
and how to play well with
designer runtime constraints,
and we're ready for the
second part of this session.
I'd like to invite up my
colleague Jesse Donaldson
to talk about what's
new in Cocoa.
Over to you, Jesse.
[ Applause ]
>> Hi, everyone.
Today I'm going to tell
you about NSGridView,
a new layout container that
were providing on macOS.
So maintenance on constraints
can be complicated even
when you have something
as simple as this set
of checkboxes, and we've
built in a stack view
to make it easier to
build things like this.
And it's great anytime you need
to spread some items
across a space.
to spread some items
across a space.
But there's some kinds of layout
that can still be difficult
to achieve like this,
for example.
You can build this with stacks,
but NSStackView isn't going
to help you align the content
across both the rows
and the columns.
So that's why we've
built in NSGridView.
It makes it easy
to put the content
into an explicitly defined
grid, and it will take care
of alignment for you
across both axes.
So let's have a closer look.
I took this UI from our
voiceover preferences,
and I've added these
purple lines just
to help you see how it
can fit into a grid.
NSGridView uses a couple of
helper classes, NSGridRow
and NSGridColumn, to
represent the rows and columns,
and by default they're sized
automatically to their content.
You can also specify an explicit
size, if that's what you need.
These also let you add
some additional padding
if you need some extra space
here and there in your grid,
and if you have some UI
that doesn't apply the
current hardware configuration,
that doesn't apply the
current hardware configuration,
then they can be
dynamically shown and hidden.
NSGridView uses a
separate class, NSGridCell,
to represent the
individual cells,
and the job of a cell is
just to manage the layout
for a particular content view.
Cells also let you control
the placement of the content
if the cell has a bunch
of extra space in it.
And if you have some
content that needs
to span the boundaries between
cells, then they can be merged
as you might be familiar with
from a spreadsheet program.
So, I've built a smaller
version of this UI
that still has all the
interesting pieces in it,
and I want to go through
this a bit at a time
and show you how it's built.
So we intend to provide
support in Interface Builder
for NSGridView, but until
we have it, we're just going
to be working with the code.
What I've done here is
make outlets in my nib file
for all the controls that
I want to put into my grid,
and then whenever you specify
a ContentView for NSGridView,
it will take care of
moving that to be a subview
of the grid if needed.
This makes it really easy
to allocate your grid view
at run time and then
integrate it
into an existing view hierarchy.
There's a few different ways
to construct grid views.
I think this is the easiest one.
All you need to do
is specify a list
of the content views
for each of the rows.
And let's get to a
couple of things.
First, you don't have to
worry about sizing the grid.
It'll take care of however many
rows and columns are needed
for the content that
you've specified.
Second, the code that you end
up with is roughly
grid-shaped itself and is
at least highly correlated with
the UI that you're specifying.
This makes it much easier
for you or somebody else
to come back later and find
your way around the code.
So, if we just run this,
this is what we get.
That's not too bad just for
calling the constructor,
but it doesn't match the
design yet, obviously.
The most striking
problem is just
that the UI is very spread out.
There are all these
gaps, like this one.
There are all these
gaps, like this one.
And this occurs because the
grid view is constrained
to the edges of the window.
Now when I did that,
my intent was
to make the window the
same size as the grid,
but what actually happened was
that the grid was
stretched to fill the window.
If you've used Auto Layout
very much in the past,
you may have encountered
similar problems
with text fields
or other controls.
And we're going to resolve
this here the same way,
by adjusting the content hugging
priority of the grid view.
So, the other controls in this
window already have a higher
content hugging from
the nib file
but until we raise the content
hugging on the grid view,
then it's possible
for the window
to actually pull the content
view edges away from the edges
from the rows and columns.
Once we raise it, instead
we'll see that the edges
of the windows are pulled
in, and the gap closes up.
So, the next thing I'd like
to look at is these labels.
They're clearly laid
out within theirselves,
but they really need to be
right justified so that they're
but they really need to be
right justified so that they're
up against the controls
that they are labeling.
And this is easy to change
by just adjusting the
placement value for those cells.
The x and y placement properties
are available on rows, columns,
cells, and the grid view itself.
The idea is that if the
value is not specified
on the cell itself, then
the value from the row
and column will be used or from
the grid view if necessary.
This makes it very easy to
just set the value in one place
and then have it affect
a wide range of cells.
So, in this case, we can
get the column at index zero
from the grid view and just set
the x placement on that column,
and we'll see both of the labels
hop over to the right side.
The next thing I'd like to look
at here is the baseline
alignment.
The text of the labels
is not properly aligned
with the text in the controls.
And it's subtle in the
slide, but it's easy to fix
by adjusting the row alignment.
Row alignment is inherited
the same way as the placement,
Row alignment is inherited
the same way as the placement,
and for this particular design,
it's fine to just
have everything
in the grid align by baseline.
And so, we can set the value
in one place on the grid view,
and it will affect everything.
And if you look carefully,
you'll see the text
slide into place.
Now, one thing to keep in
mind about row alignment is
that you may have
a bunch of views
in your row that
are all aligned.
And then you may also specify
placement for those cells.
So, in that kind of a case,
the grid view may not be able
to satisfy all the requests.
And so, it's important
to understand
that the row alignment
will always take precedence
over the placement.
The way it works is that
we'll take the whole group
of aligned content views
and then we'll place
it using the placement
from the first cell.
So, the next piece I'd like to
look at is this pop-up button.
The design has a little
bit of extra space above
and below the pop-up button,
and we can get that space
in our layout by adding
some padding to that row.
in our layout by adding
some padding to that row.
So, the first thing we need
to do is fetch the row.
We could actually
do this the same way
that we got the column before
by specifying the index,
but this way is a
little bit better.
Instead, we ask the
grid view for the cell
that contains this
pop-up button,
and then from the
cell, we get the row.
This is better because if
someone comes along later
and changes the configuration of
the grid view to add a checkbox
or something like that, this
code will still be valid.
If you fill your code with a
lot of hard-coded index values,
then as soon as someone adds
a checkbox, you need to go
and review all of
that code to see
which of the indices
need to be updated.
And in any case, once we
have the row, we can go ahead
and set the padding
values, and we'll see
that we get a little
bit of extra space
above and below the pop-up.
We need a little space over
the status cells label as well,
and we can achieve
that the same way.
and we can achieve
that the same way.
So, I want to take a moment here
to talk about the difference
between padding and spacing.
We haven't really talked
about spacing much.
The padding values are
available on rows and columns,
and they are just for adding
an extra space here and there
where you need it in your grid.
The spacing values are available
on the grid view itself,
and they apply to the spaces
between all the rows
or all the columns.
So, if we take a copy of the
design here and we remove all
of the padding, then
this is what we get.
The UI is still properly
spaced out,
but we lose the visual
distinction
between the different
clusters of controls.
If instead we keep the
padding but we take
out all the spacing,
we get this.
We still have our
control clusters,
but the UI is very
cramped together.
And of course if you remove
both, you end up with this,
where the whole thing is
just very tightly compressed.
So, an important thing
to keep in mind here is
that the padding properties
all default to zero.
You won't have any padding in
your grid unless you specify it.
You won't have any padding in
your grid unless you specify it.
But you almost always want some
space between the content views,
and so the spacing on the grid
view defaults to non-zero.
If your use case requires
that your views are laid
out immediately next to each
other, then you'll need to go
to the grid view and set those
spacing properties to zero.
The last piece that's really out
of place here is this checkbox,
and it's an interesting
case because it needs
to span the boundary between
these bottom two cells.
But we can do that
by merging the cells,
like I mentioned in
the introduction.
There are a few methods
for this, but in this case,
we can just tell the row to
merge its first two cells.
And when you do this, it has the
effect of extending the boundary
of the top leading cell to
cover the entire merge range.
So with this code in effect
we see the checkbox slides,
and now this content view is
laid out across both cells
as if they were the same one.
And in fact, you can see it's
still inheriting the trailing
placement from that
first column.
placement from that
first column.
We don't really want it to
have trailing placement,
but we don't want to have
leading placement either.
This checkbox is actually
supposed to be centered
on the boundary between
the two columns.
So, because the columns
are not evenly sized,
this isn't something that
the grid view will do
for you out of the box.
But it does leave you room
to do it for yourself.
And that starts by setting a
placement for this cell to none.
When you set these
placement values to none,
it has the effect of causing the
grid view to stop maintaining
that aspect of the layout.
And what that buys us is
that the grid view won't
be applying any constraints
that might conflict
with our constraints.
So, once that's done, we can
go ahead and make a constraint
from the checkbox's center x
anchor to the leading anchor
of the checkbox above it.
And then once we
have the constraint,
we could actually
just activate it
like you normally would
with a constraint.
But in this case,
we're going to set it
in the custom placement
constraints array.
in the custom placement
constraints array.
If you do that, then
it allows the grid view
to maintain any custom
placement constraints for you,
and it will do things
like activate
and deactivate the
constraints depending on whether
or not the cell is visible.
So, with this code into place,
we see the checkbox shift
into position, and the
implementation then matches
the design.
We're finished.
Some pieces of the grid
configuration here were a little
bit complicated, but the whole
thing is still much simpler
that if you needed
to build this UI
from basic constraints
or even with stacks.
In fact, there's not a lot
that needs to change in order
to expand this to the
full UI that I swiped
from the voiceover preferences.
So, to summarize,
NSGridView is great
if your application has
a static grid-like UI
that you need to manage.
And a great way to start
working with it is to take all
of your content views
and put them
into a grid and see
what you get.
At that point, you can
iterate on the configuration
At that point, you can
iterate on the configuration
of your grid until you've
reached the layout design
that you're looking for.
I hope you find it useful.
Next, I'd like to bring out
my colleague Marian Goldeen,
who's going to talk to you
about some new debugging
tools that she's built.
Thank you.
[ Applause ]
>> It doesn't happen often,
but when it does it's a hang,
and then that turns
into a crash,
and it can be hard to debug.
And that's a layout
feedback loop.
When you encounter a
layout feedback loop,
you're usually beginning
or ending a transition,
and it might be something
like this.
You tap the button to
start a transition,
and the button responds,
but then nothing else does.
So, you're running on
Xcode, and you look
in the debugging navigator, and
you see that the CPU is pegged,
memory's increasing, and
maybe you break and you look
and you just see a bunch
of layout in the backtrace.
And what's happening is that
there is some collection
And what's happening is that
there is some collection
of views that are running
layout again, and again,
and again in a tight loop.
The run loop's never turning.
Any messages that are
happening are collecting
to autorelease objects,
so that's all collecting.
So that's why the
memory's increasing.
And what's driving this is
an upstream setNeedsLayout.
And what I mean by that is one
of the [inaudible] views during
its layout is doing something
that is causing a more
root directed view,
to get a setNeedsLayout so
when layout finishes its pass,
it goes right back to
the top and starts again,
and what you want to know
is what views are involved
because that's going to
help you figure out where
that setNeedsLayout
is coming from, why,
and what you're going
to do about it.
This information is actually
a little difficult to collect.
So, that's why we're introducing
a layout feedback loop debugger
to help with these
particular situations.
This is a launch argument
that you add in Xcode.
This is a launch argument
that you add in Xcode.
It's the UIView
or NSViewLayoutFeedbackLoop
DebuggingThreshold,
depending on whether
you're on macOS or iOS.
And you give it a value.
I've used 100 here.
You can use any value, but we'll
clamp it between 50 and 1,000.
Now when you've got this
launch argument set,
the layout feedback loop
debugger will be counting layout
subviews for every
view that runs layout,
and if any of them run
more than that threshold
within the same turn
of the run loop,
it will then allow
the cycle to continue
for a little bit while
it collects information.
Then it will throw --
raise an exception and dump
that information into the logs.
It dumps it to the
com.apple.AppKit subsystem
or the com.apple.UIKit
subsystem,
depending on whether
you're on macOS or iOS,
in both cases the
layout loop category.
If you want to know more about
the new logging subsystems
and categories, come to the
session at five o'clock today
and categories, come to the
session at five o'clock today
at Knob Hill on the unified
logging and activity tracing.
Alternatively, you can set a
break point in the debugger,
an exception break point.
And you can print the
feedback loop in the debugger,
or it's also nice to
hit the break point
and maybe introspect what's
going on a little more.
So, I said info dump,
and I do mean info dump.
So we're going to go
through two examples
of real live layout feedback
loops that I debugged
with the layout feedback
loop debugger.
Now we're going to look at
those logs, warts and all,
and hopefully a walkthrough
of two different logs
are going to help you.
We're going to look at a layout
feedback loop that was caused
by an upstream setNeedsLayout.
This is actually independent
of Auto Layout, and another one
that was caused by ambiguous
layout from constraints,
which is very special
to Auto Layout.
So for the first example with
the upstream geometry change,
here's a graph of the view tree
where the feedback
loop was happening.
And a lot of view in that
view tree were actually
running layout.
Often you're lucky and
there aren't so many views,
and ones like this your
heart kind of sinks.
But it turned out that ten
of them were just churn.
They were innocent victims
of the actual problem
that was happening higher
up in the hierarchy.
So, what was going on was
that that third level view,
during its layout, was changing
the bounds of its superview.
Now when a views bounds change,
it gets an implicit
setNeedsLayout
because it will need
to re position its views
for the new bounds.
But also if that view that's
receiving the bounds change,
if its superview is
not actually in layout,
the superview will also
receive a setNeedsLayout
so that layout subviews
will have the last word
on what the layout is.
So when the layout pass gets
down to the bottom and finishes,
it goes back to the top,
and it runs layout on it
because that top view
still needs layout,
because that top view
still needs layout,
which resets the bounds
of the middle view.
And the feedback loop is powered
by the two views fighting
over the bounds of the
view in the middle.
As you can imagine, there's
a lot of layout going on.
So, there's a lot of
information in the log.
So brace yourself.
This is what the log looks like,
or at least the top of the log,
which is where I
want you to start
when you look at
one of these logs.
The first thing that's called
out is the top-level view
that -- of the layout
feedback loops.
So, in this loop there
is no view more root word
than this view that's
getting layout.
Following that is a recursive
description of the subtree
under that top-level view.
And next to some of the views
in that description,
you'll see a number.
For instance, the -- you
will always see one next
to the top-level view.
In this case it's 23.
Those numbers, the ones with
the numbers are the views
that are actually
receiving layout,
and the numbers are in ordering.
Of course, it's a cycle,
so we can put one anywhere.
But we do it so that the last
one is the top-level view,
But we do it so that the last
one is the top-level view,
so it gives you an idea of
how many views are involved.
So, that's a 23, but there
is only ten down here,
and there is three up here,
and that doesn't add up to 23.
So, what happened?
Well, let's go look at the
next section of the log,
which is the views
receiving layout in order.
And in this section,
you can see that just
because there is one loop
doesn't mean that every view
that lays out in
lays out only once.
We have ten views
that are laying out,
followed by another two views,
followed by those
same ten views again.
And so, those where the
more [inaudible] views.
And when you see this situation,
where you have a bunch of views
that lay out more than once in
the cycle, they're often victims
of the other ones that
are more important.
So, as I said early on,
we're really interested
in where is the top-level view
getting its setNeedsLayout from.
So, following this
section is a lot of detail
So, following this
section is a lot of detail
about the actual --
what's actually happening
during the layout.
So, we're going to scroll
past that down to the bottom
of the log to a section
called call stacks sent
to the top-level view.
So, it's down there, and you
just have to look for it.
Now, there can be more
than one of these.
You know, usually
there's only one.
This was a pretty refractory
one, and there were several.
They're pretty similar.
So, I'm only going to
show you one of them.
At the top of the backtrace
are some funnel methods
for the feedback loop debugger,
but pretty near the top you see
that in frame five, you see
the DropShadowViews receiving a
set bounds.
And if you remember from
the recursive description,
the DropShadowView was the
subview of the TransitionView.
So, the only way that set bounds
of the DropShadowView could
be causing a setNeedsLayout
on the TransitionView would be
because the TransitionView
is not in layout.
So, that view in frame seven
that's running its layout is not
So, that view in frame seven
that's running its layout is not
the TransitionView.
It's something else.
But we don't have that
information in this backtrace.
I didn't have symbols
for this particular app.
So there, they are
not showing up.
Furthermore, it could be a view.
We're lucky here because it's
the DropShadowView that's
receiving set bounds,
which override set bounds,
but it could be a view
doesn't override set bounds.
But hopefully between the
information from the backtrace
and the information from
the top, you'll know what
in the details you're
interested in.
And so, what we are interested
in is the frame changes
for the DropShadowView.
So, we scroll back up,
and we find where that
one -- information is.
So, this is under some
geometry change information.
And we see that in fact
these geometry changes
to bounds changes and
our frame changes --
change are repeating
again and again.
And two of those changes
are coming during layout
on the TransitionView, which is
rational and what you'd expect.
But one of them is coming from
the viewLayoutSubviews method
But one of them is coming from
the viewLayoutSubviews method
of a particular view controller
for the view that's a subview
of the TransitionView.
So, we've located our problem,
and the way to fix this bug is
for the programmer to
figure out some other way
to achieve their ends that don't
involve changing the bounds
of a superview during layout.
All right, so now we're taking
a breath because we're going
to example two, which
is a different kind
of feedback loop entirely.
For those of you who have
been using Auto Layout
to do anything very
complicated, you've probably run
into the problem of
having ambiguous layout.
And ambiguous layout is
usually not so terrible.
Usually you get maybe a
bunch of zero size views.
And you're like where did my
views go, or maybe the view --
the layout is what you want
except occasionally you rotate
the device or something and
you get a different layout.
the device or something and
you get a different layout.
But sometimes you're
really unfortunate,
and during the update
constraints passes
that precede layout, you
can exercise that ambiguity.
And if that ambiguity
gets exercised,
then variable changes
will occur with each one,
and each time it'll
dirty layout somewhere.
And so you get this cycle.
Now an ambiguous layout as
a layout feedback loop leads
to something that's really
that gets you scratching your
head unless you think of it.
So, that's why when
this is likely,
we call it out at
the top of the log.
So they're already
thinking about it.
And so, it says ambiguous
layout is suspected.
And then when you look at
the recursive description,
you see ambiguous layout all
over the place, and, you know,
it's pretty suspicious.
I'm going to digress
here a moment
because I have abbreviated
things in these logs
to make them fit
okay on the slides.
to make them fit
okay on the slides.
And -- but there's an
abbreviation in there
that you might not recognize
that is actually
in the logs itself.
And that's the tAMIC.
You see that right there were
it says ambiguous layout.
So tAMIC stands for
Translates Auto Resizing Mask
into Constraints, and now
you can all call it that too.
[ Laughter ]
At any rate, so were pretty sure
that ambiguous layout
is our issue.
And we could go look
at the backtrace
for the setNeedsLayout is
set to the top-level view.
But I'm not going
to show you that.
All that's in there is internal
foundation and UIKit methods.
And it's not helpful to us.
So, in this case, we need
to look at the details,
and the details here are
going to tell us views
with variable changes that
are triggering layout.
And one thing you
need to remember
about ambiguous layout
is it's contagious.
So, you might just be missing
a couple of constraints.
But a bunch of views
are ambiguous
because they're all kind
of dependent on each other.
So, you may have a lot of these,
but you just start with one
So, you may have a lot of these,
but you just start with one
and once that's fixed up,
a whole lot of the rest will
evaporate if not all of them.
So you look at this.
You see that the min x variable
is oscillating between minus 120
and minus 160, which is
kind of odd values anyway.
And because it's ambiguous
layout, we list the constraints
that are affecting the layout,
which you can then
hopefully examine
and get some idea
of what's missing.
I'm going to go over now
examining constraints
because it can be intimidating
to see a list of constraints.
And drawing pictures is the
only way to deal with it.
I like to start with a graph of
the view hierarchy of the views
that are in -- that are
listed in the constraints.
And fortunately for
this example,
the views were all
different subclasses
so I could label them for you.
And the next thing I drew
is draw myself a picture
of what the constraints were.
So the constraints
were a minimum leading
and trailing padding for the
label inside the container.
and trailing padding for the
label inside the container.
And there was a centering
constraint
between the container
and the action view.
And then the action view had
autoresizing mask constraints
positioning it within
the representation view,
kind of positioning
it in a weird place.
Something's definitely funky
with these constraints.
And finally there's an alignment
between the representation
view and its sibling.
But there's nothing that
actually insists that these --
this whole view hierarchy needs
to be in any particular place.
So, that's how we
got this ambiguity
and the layout feedback loop.
So that's layout
feedback loop debugger.
It's a launch argument.
You won't need to use often.
But when you do, it should
save you a lot of time.
And to recap, we saw the --
how you can incrementally
adopt Auto Layout
and Interface Builder.
In AppKit, there's NSGridView
for grid-like layout.
If you'd like to see that
soon on iOS, cast your vote
in Apple Bug Reporter.
Finally, we have the
layout feedback loop
debugging threshold.
More information on the WWDC
website, and have a great rest
of the afternoon, and
thanks for coming.
[ Applause ]