App Frameworks • iOS, macOS, tvOS • 40:19
Get details about new features of Auto Layout in Xcode, Cocoa and Cocoa Touch. Explore the new NSGridView, allowing your Cocoa app to easily build grid interfaces. Dive deeper into new tools that enable you to quickly diagnose layout issues in your interface. Learn about the new features in Xcode to quickly build adaptive interfaces.
Speakers: Jason Yao, Jesse Donaldson, Marian Goldeen
Unlisted on Apple Developer site
Downloads from Apple
Transcript
This transcript has potential transcription errors. We are working on an improved version.
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 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. 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 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.
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 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. 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 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 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 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 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.
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.
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, 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.
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 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, 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.
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.
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. 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.
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. 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 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 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. 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 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, 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, 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 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 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 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.
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.
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 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 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 ]