Clean up your knowledge of iOS drawing and layout.

IOS main RunLoop

IOS’s main RunLoop handles all user input events and triggers responses. All user interactions are added to an event queue. The UIApplication object pulls events from the queue and distributes them to other objects in the application. When the view responds (the view responder), the control flow returns to the main RunLoop and begins the Update cycle, which is responsible for laying out and re-rendering views.

2. The Update Cycle

Update Cycle is the point in time when the control flow returns to the main RunLoop after the application has completed all of your event handling code. It is at this point in time that the system begins to update the layout, display, and set constraints.

If you request changes to a view in the event handler code, the view will be marked as needing to be redrawn. During the subsequent Update cycle, the system performs these changes on the View.

Delays between user interactions and layout updates are barely noticeable to users. IOS apps typically display animations at 60 FPS, which means that each update cycle takes 1/60 of a second. The update process is fast, so the user doesn’t feel the update delay in the UI when interacting with the application. However, since there is a gap between the processing of the event and the redrawing of the corresponding view, the view update in the RunLoop at some point may not be what you want it to be. If some calculations in your code depend on the current view content or layout, you run the risk of operating on outdated (or incorrect) view information. To do this, you need to understand a few important layout methods in UIView to avoid this kind of problem.

The figure below shows the Update cycle occurring at the end of the RunLoop.

Three Layout.

The layout of a view refers to its size and position on the screen. Each view has a frame property that represents its position and size in the parent view’s coordinate system. UIView gives you methods to notify the system that a view layout has changed, and it also gives you callback methods to call when the view layout is recalculated.

layoutSubviews()

This UIView method handles repositioning and resizing of a view and all its subviews. It is responsible for giving the position and size of the current view and each of its children. This method is expensive because it works on each subview and calls its corresponding layoutSubviews method. The system automatically calls this method whenever it needs to recalculate the frame of the view, so you should override it whenever you need to update the frame to reposition or resize it.

But you should not call this method explicitly. Instead, there are many mechanisms that trigger layoutSubviews calls at different points in the runloop, and these trigger mechanisms are much less costly than calling layoutSubviews directly.

When layoutSubviews are complete, the viewDidLayoutSubviews call is triggered on the view’s owner, the viewController. Since viewDidLayoutSubviews is the only method that can reliably be called when the view layout is updated, you should put all your layout or size dependent code in viewDidLayoutSubviews, Instead of putting it in viewDidLoad or viewDidAppear. This is the only way to avoid using outdated layout or location variables.

Automatic refresh

There are many actions that automatically mark the view with “Update Layout”, so layoutSubviews will be called in the next cycle without requiring the developer to do it manually. These automatic ways of notifying the system that the layout of a view has changed include:

  • Modify the size of the view
  • New subview
  • The user inUIScrollView Scroll up (layoutSubviewsWill be inUIScrollViewAnd its parent view.)
  • User rotating equipment
  • Update the constraints of the view

setNeedsLayout()

The cheapest way to trigger a layoutSubviews call is to call the setNeedsLaylout method on your view. Calling this method indicates to the system that the layout of the view needs to be recalculated. The setNeedsLayout method executes immediately and returns, but does not actually update the view until it returns. The view is updated in the next Update cycle.

layoutIfNeeded()

LayoutIfNeeded is another method that causes UIView to trigger layoutSubviews. Unlike setNeedsLayout(), which causes the view to call layoutSubviews the next cycle to update the view, layoutIfNeeded immediately calls the layoutSubviews method. However, if you call layoutIfNeeded and there is no action to indicate to the system that the view needs to be refreshed, layoutSubview will not be called. If you call layoutIfNeeded twice in the same runLoop and the view is not updated between calls, the second call also does not trigger layoutSubviews.

With layoutIfNeeded, layout and redrawing occur immediately and are completed before the function returns (unless there is an animation in progress). This method is more useful than setNeedsLayout when you need to rely on the new layout and cannot wait for the next update cycle. Unless this is the case, you should use setNeedsLayout so that the layout is updated only once per runLoop.

This method is especially useful when you want to animate a constraint by modifying it. You need to call layoutIfNeeded on self.view before the animation block to ensure that all layout updates are propagated before the animation starts. After setting the new constraint in the animation block, you need to call layoutIfNeeded again to animate to the new state.

4. The Display

The display of a view contains view properties such as colors, text, images, and Core Graphics drawings, but not the size and position of its own and its subviews. Like layout, display has methods that trigger updates, which are called automatically by the system when an update is detected, or we can call a direct refresh manually.

draw:

The draw method of UIView is the operation to display the contents of the view, similar to the layoutSubviews of the view layout. But unlike layoutSubviews, the draw method does not trigger subsequent calls to the view’s subview methods. The main thing to note is that you should not call the draw method directly, but instead call the trigger method and have the system call automatically at different points in the runLoop.

setNeedsDisplay()

This method is similar to setNeedsLayout in the layout. It sets a dirty flag for views with updated content, but returns it before the view is redrawn. Then in the next Update cycle, the system iterates through all marked views and invokes their DRAW method.

Most of the time, updating any UI component in a view will automatically mark the corresponding view as “dirty”, and by setting the view’s “internal update mark” it will be redrawn in the next Update cycle without the need for an explicit setNeedsDisplay call.

In the following code example, do a custom draw by setting drawType and calling setNeedsLayout in didSet.

class MyView: UIView {
    var drawType = 0 {
        didSet {
            setNeedsDisplay()
        }
    }

    override func draw(_ rect: CGRect) {
        switch self.drawType {
            case 0: return
            case 1: drawPoint(rect)
            case 2: drawLine(rect)
            case 3: drawRectangle(rect)
            default: drawEllipse(rect)
        }
    }
}
Copy the code

There is no layoutIfNeeded method in the view’s display method that triggers an immediate update like the layoutIfNeeded method in layout.

5. Bound Constraints

Automatic layout consists of three steps to lay out and redraw the view. The first step is to update the constraints. The system calculates and sets all the required constraints for the view. The second step is the layout phase, where the layout engine calculates the frames of the view and subviews and lays them out. The third step is the display stage, which redraws the contents of the view and calls draw if the draw method is implemented.

updateConstraints()

This method is used to dynamically change view constraints in automatic layout. Like the layoutSubviews() method in a layout or the draw method in a display, updateConstraints() should only be overloaded and should never be called explicitly in code.

In general you should only implement constraints that must be updated in the updateConstraints method. Static constraints should be specified in the Interface Builder, view initializer, or viewDidLoad() method.

Typically, setting or unbinding a constraint, changing the priority or constant value of a constraint, or removing a view from the view hierarchy sets an internal flag “Update constarints” that triggers a call to updateconContinent () on the next update cycle. Of course, there is a way to manually mark a view with the “Update constarints” label, as follows.

setNeedsUpdateConstraints()

Call setNeedsUpdateConstraints () will ensure that in the next update cycle update constraints. It triggers updateConstraints() by marking “Update constraints”. This method works similarly to the setNeedsDisplay() and setNeedsLayout() methods.

updateConstraintsIfNeeded()

This approach is equivalent to layoutIfNeeded for views that use automatic layout. It will check the update constraints “tag (automatically set can be setNeedsUpdateConstraints or invalidateInstrinsicContentSize method). If it thinks these constraints need to be updated, it triggers updateConstraints() immediately, rather than waiting until the end of the runLoop.

invalidateIntrinsicContentSize()

Some views in automatic layouts have the attribute intrinsicContentSize, which is the view’s natural size based on its content. The intrinsicContentSize of a view is usually determined by the constraints of the elements it contains, but can also be overloaded to provide custom behavior. Call invalidateIntrinsicContentSize () will be set a tag says this view intrinsicContentSize has expired, the need to recalculate the next layout stage.

For example, UILable, UIImageView, etc., have intrinsicContentSize, which can be computed automatically without setting its size.

6. Summary

Layouts, displays, and constraints all follow similar patterns, such as how they are updated and how updates are enforced at different points in the run loop.

Each component has an actual update method (layoutSubviews, Draw, and updateConstraints) that you can override to manipulate the view manually, but do not explicitly call it in any case. This method is called at the end of the runLoop if the view is flagged for update. (There are some actions that set this flag automatically, but there are also methods that allow you to explicitly set it.)

The following flowchart summarizes the interaction between the Update Cycle and event loop and indicates where the methods mentioned above are located during the run loop.

You can run any point in the loop to explicitly call layoutIfNeeded or updateConstraintsIfNeeded, need to remember that it will cost a lot.

At the end of the cycle is the Update cycle. If the view is set to a specific “Update constraints”, “Update Layout”, or “needs Display” flag, this node updates the constraints, layout, and presentation. Once these updates are complete, runloop restarts.

Generalization of seven.

1. The setNeedsDisplay or setNeedsDisplay (the rect: CGRect)

  • Mark the corresponding view area to be redrawn
  • The call is not immediately redrawn, but drawn in the next drawing cycle
  • It’s going to call the Viewdraw(_ rect: CGRect)methods
  • Don’t calllayoutSubviews()methods

2. SetNeedsLayout method

  • The interface will not be updated immediately, it will be updated in the next refresh cycle
  • You need to call this method on the main thread
  • This is called regardless of whether the size has changedlayoutSubviews()methods

3. LayoutIfNeeded method

  • The view is updated immediately
  • Views that use automatic layout will update the changed size by default
  • This property can be used in animation
  • If there are tags that need to be refreshed, they are called immediately; if there are no tags, they are not called

4. Call timing of layoutSubviews

  • Setting frame not to Zero during initialization is triggered
  • Direct call[self setNeedsLayout]
  • When addSubview
  • When the size of a view changes, the frame value changes back and forth
  • slidingUIScrollViewwhen
  • Rotating the screen may trigger
  • Update the constraint of the view

5. If you want to refresh immediately

  • First call[view setNeedsLayout], marked as needed for layout, and then called[view layoutIfNeeded], realize the layout

1.Demystifying iOS Layout 2 Handsome king of the army

The END. I’m a little prince. Working hard in the imperial capital as an office worker by day and a knowledge service worker by night. Be sure to follow and like the book if you find it rewarding. I wouldn’t say no if I had to.