setNeedsLayout vs layoutIfNeeded Explained

setNeedsLayout vs layoutIfNeeded Explained


Let’s take a look at the difference between setNeedsLayout and layoutIfNeeded.

This post will explain the difference using a concrete example of animating constraint changes. Before discussing both methods, it is crucial to first have an understanding of the main run loop in an iOS app, and the Auto Layout process, so bear with me for a moment.

As part of the normal startup process, UIApplication in iOS starts the main run loop for an app, which runs on the main thread. The main run loop processes events (such as user touches) and handles updates to view-based interfaces. As events occur, such as touch, location updates, motion, and multimedia control, the run loop finds the appropriate handler for the events, calling appropriate methods, which call other methods, and so on. At some moment in time, all events will have been handled and control will return to the run loop. Let’s label this point where control is returned to the run loop as the update cycle, since this is how Apple refers to it in some of their documentation. You could use other terminology to conceptualize it, such as a break in the action, the redraw cycle, or a free moment.

While events are being processed and dispatched, and as changes to views are requested, they are not always acted upon right away. Instead, the system records the changes and marks views as needing to be redrawn. When are the changes drawn? It is in this update cycle, after all existing events have been handled, that attention is now turned to redrawing. Of course to the user it does not look like there is a wait to redraw (in general), because all of this is happening quickly. Knowing that there is an interval periodically, between sets of events being processed, where the system now takes on the task of updating the layout and display, is important for understanding setNeedsLayout and layoutIfNeeded.

The difference between these two methods can be now be described by referencing the update cycle described above.

The method setNeedsLayout for a UIView tells the system that you want it to layout and redraw that view and all of its subviews, when it is time for the update cycle. This is an asynchronous activity, because the method completes and returns immediately, but it isn’t until some later time that the layout and redraw actually happens, and you don’t know when that update cycle will be.

In contrast, the method layoutIfNeeded is a synchronous call that tells the system you want a layout and redraw of a view and its subviews, and you want it done immediatelywithout waiting for the update cycle. When the call to this method is complete, the layout has already been adjusted and drawn based on all changes that had been noted prior to the method call.

So, stated succinctly, layoutIfNeeded says update immediately please, whereas setNeedsLayout says please update but you can wait until the next update cycle.

When I first encountered these methods, I can remember thinking that the if neededpart of layoutIfNeeded made it sound less urgent or even optional, compared to the setNeedsLayout method that sounded more like a definitive statement to perform a layout. However, names can be a little deceiving.

Before we look at code, there is one more important concept regarding Auto Layout and the update cycle. There are actually three phases associated with the layout and drawing of the views. The first is update of constraints (constraint pass), which happens bottom up. The second is layout of views and subviews (layout pass), which happens top down and is dependent on constraint settings. The third phase is the display pass, where the views get redrawn based on the layout pass.

Let’s take a look at how the two methods, setNeedsLayout and layoutIfNeeded, play out for the case of animating constraint changes for a UIView. You should clearly see the impact in this scenario.

The app for this discussion is a simple Single View Application, and I’ve created one blue colored UIView within the main view, as well as a button to trigger update of the blue view height constraint constant. Here is the storyboard after I’ve completed these configurations.

Screen Shot 2016-04-12 at 11.18.34 AM

You can animate constraint changes within an animation block, and that is exactly what we’ll do here. With the blueHeight constraint IBOutlet in the ViewController, we’ll be able to update the constraint and cause the blue rectangle to grow or shrink when the button is clicked, and it will be animated. Here is the entire ViewController.

When the button is pressed, the first thing you see is a call to view.layoutIfNeeded(). Remember that this method forces an immediate layout and display update. You might wonder why this is done before we do the constraint change. Apple considers this a best practice in order to ensure that any previous changes awaiting the update cycle are completed, so I’ve added it for that reason.

The next section of code checks the current value of the constraint constant, so that the button click is alternating between the original constraint of 25, and the view height minus 100.

When the constraint is updated like this, that automatically does the equivalent of a setNeedsLayout, so nothing else would be required to have the view updated during the next update cycle. We could have no more code and you’d see the update, but it would not be animated. In our case, a 2 second animation block is added, and inside that block, we force an immediate layout via the layoutIfNeeded method. Because this layout happens synchronously, the frame movement from the constraint change is captured within the animation block, and therefore if you run the app now, you see how the blue view animates over the 2 seconds to the large or small size. Here is the view before and after animation from small to large.

Simulator Screen Shot Apr 12, 2016, 11.44.39 AM

Simulator Screen Shot Apr 12, 2016, 11.43.56 AM

Let’s replace layoutIfNeeded with setNeedsLayout, so that the animation block is now as follows.

Now what we are doing within the animation block is marking the view as needing a layout update, but it isn’t forced immediately. Instead, the setNeedsLayout method returns with the view simply being on the list to be updated in the next update cycle. The net effect is that nothing takes place in the animation block that can be animated, because there is no change to the view within that block.

Clicking the button in this case immediately updates the view size based on the updated constraint, instead of animating the update. Wait a minute, why is it immediate if we didn’t use layoutIfNeeded? That is a perfectly valid question which I’ll address.

It helps to know that animation actually takes place on its own thread, and not on the main thread where the update cycle occurs. Furthermore, animation is triggered at the next update cycle. During animation, progress updates are sent on the main thread, which is why we see incremental changes for animations. In this case though, animation isn’t making the changes to the view and sending updates for display. Instead, once the code completes in the IBAction, and there are no more events being processed, an update cycle occurs and does an immediate layout pass without any animation.

To our eyes, the change in size of the blue view happens instantaneously. At first it seems counter-intuitive because we didn’t use layoutIfNeeded to force an immediate update. However, we didn’t make the view update happen within the context of the animation block, so it appeared to be immediate because we marked it for update and the update cycle occurred, which is also where the animation would have started. So, instead of starting an animation of the constraint change and frame movement from the update cycle, the view update happened instantaneously in the update cyclebecause our code had marked that the view needed a layout update via the setNeedsLayout.

If you want to run this app for yourself and see the animation with layoutIfNeededversus no animation with setNeedsLayout, the code with animation is here in GitHub.

Are you also interested in understanding setNeedsDisplay, drawRect, and the display phase of the update cycle? My next post covers exactly those topics.

Thanks for reading!

發佈了60 篇原創文章 · 獲贊 13 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章