SetNeedLayOut VS LayOutIfNeed

原文链接地址:http://www.iosinsight.com/set…

Let’s take a look at the difference between setNeedsLayout and layoutIfNeeded.让我们来看下setNeedsLayout与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.这篇文章中利用基于约束的动画来解释上述两者之间的区别,在我们讨论之前,至关重要的一步是要了解 app应用中的runloop的概念、 Auto Layout 的工作流程, 所以你们对我要有些耐心哈.

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.(所有的事件全部处理完并且runloop从新拥有控制权) Let’s label this point where control is returned to the run loop as the update cycle(当run loop 重新获得控制权的时候就认为是一个更新周期), 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(系统会记录view的改变并做一些需要重新绘制的标记). 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(绘制view以及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 immediately without 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 needed part 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.

Auto Layout工作的三个阶段:1 约束更新(从内到外) 2布局过程(从外到内) 3 绘制

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.

图片链接:
https://i0.wp.com/www.iosinsi…
https://i1.wp.com/www.iosinsi…

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.

      
   import UIKit
   class ViewController: UIViewController {
     
       @IBOutlet weak var blueHeight: NSLayoutConstraint!
        
       @IBAction func heightPressed(sender: AnyObject) {
        
            view.layoutIfNeeded()
            
            if(self.blueHeight.constant == 25.0)
            {
                self.blueHeight.constant = self.view.bounds.height - 100.0
            }
            else
            {
                self.blueHeight.constant = 25.0
            }
            UIView.animateWithDuration(2.0) {
                self.view.layoutIfNeeded()
            }
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
     
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
        
    }
      

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.

当我们改变约束值的时候,就会在后台进行一个和setNeedsLayout效果类似操作:先标记这个view需要更新,然后在下一个循环周期内更新. 而在块中 调用layoutIfNeeded方法的作用是:让view立即刷新, 与此同时 “view.frame的改变能够被块捕捉到” 就产生了动画.

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


    UIView.animateWithDuration(2.0) {
        self.view.setNeedsLayout()
    }

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 cycle because our code had marked that the view needed a layout update via the setNeedsLayout.

上面两段大概含义:动画效果的完成在单独一个线程,runloop、约束更新、发生在主线程.这样就可以实现 一种异步行为 :当view 约束改变的时候 能够被动画块(异步)捕捉到,然而setNeedLayout 只是标记view需要在下一个循环周期更新. 而在下一个循环周期更新时,在主线程中发送更新的事件, 但是此时动画上下文也许已经结束了(上一个循环周期执行)就捕捉不到本次的约束改变事件,所以就看不到动画效果.

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

示例代码地:https://github.com/lmacfadyen…

如有发现错误,请指正谢谢

    原文作者:Stephanie
    原文地址: https://segmentfault.com/a/1190000009603350
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞