Big talk iOS Layout

Big talk iOS Layout

Big talk iOS Layout

In the development of iOS, we spend most of our time dealing with UI, such as how to layout the UI, how to refresh the UI, and the optimization of complex UI to make our APP more smooth.

For UI layout, xcode provides visual layout methods: xib, storyboard, which are very convenient layout methods, what you see is what you get, the threshold is also very low, but it takes up more resources than the code, and in many people In the process of collaborative development, it is very difficult to deal with file conflicts in xml format, so many teams do not recommend using this type of layout, which is suitable for teams with relatively simple needs and projects that require rapid iteration.

The layout of pure code mode is our compulsory course, and Apple provides

Frame
with
Auto Layout
Two ways of layout.
Auto Layout
Is a set of layout engines provided by Apple (
Layout Engine
), this engine will convert the view, constraint, priority, and size into the corresponding frame through calculation, and when the constraint changes, it will trigger the system to recalculate again.
Auto Layout
The essence is a linear equation analysis Engine. based on
Auto Layout
, It is no longer necessary to focus on the size and position-related parameters of the views as in the frame era. Instead, focus on the relationship between the views, describe a set of constraints that represent the layout relationship between the views, and the Engine will parse the final value.

In the layout of mixed development, there will also be a

Virtual DOM
Mechanism, when the layout changes, the framework will submit the changes to
Virtual DOM
,
Virtual DOM
Calculate first, calculate the new relative position of each node, and finally submit it to
Real DOM
, In order to complete the rendering, when multiple modifications are submitted at the same time, the framework will also merge these modifications to avoid refreshing each modification. This mechanism is very similar to the rendering mechanism of the run loop in iOS.
Layout Engine
Calculate the frame of the view, and when the next run loop comes, submit the result to the rendering layer to complete the rendering. Similarly, some modifications will be "merged", and the result will not be rendered until the next run loop. .

This article mainly explains that in the layout process, the views are in

Auto Layout
as well as
Frame
How to complete the refresh in the same way.

Main run loop of an iOS app

The main run loop is used in iOS applications to process all user input and trigger appropriate response events. All user interactions of the iOS app will be added to one

Event queue
inside.
UIApplication object
All events will be taken out of this queue and then distributed to other objects in the application. It explains the user s input
application's core objects
A similar process is called in to execute the run loop. These processing events can be overwritten by the developer. Once these method calls are over, the program will return to the run loop, and then start
Update cycle
.
Update cycle
Responsible for the layout and redrawing of the view, which will be described in detail in the next section.

developer.apple.com/library/con...

Update cycle

Update cycle
Start time: After the application has executed all event processing, control returns
main run loop
. After this point in time, the system begins to layout, display, and constrain. If a view needs to be changed while the system is performing event processing, the system will mark this view as
Need to redraw
. In the next update cycle, the system will execute all modifications to this view. In order to prevent the delay between user interaction and layout update from being noticed by the user. The iOS app starts with
60fps
The frame rate refresh interface, that is, the time of each update cycle is
1/60 second
. Because there is a certain time interval in the update cycle, we will encounter some views that are not the effects we want to achieve during the process of laying out the interface. What we get is actually the effect of the last running loop. (Click here to YY, everyone may have encountered this kind of problem in the business code, a view layout is wrong, we add a 0.5 delay, and then it is correct, or asynchronously added to the main queue, the interface layout is also normal These are tricky operations. Refresh-related problems still exist. There may be no problems in your interface, but it may affect other people's or other interfaces.) So, "You will be mixed sooner or later. It must be paid back", the problem will be solved sooner or later. Next, we will introduce how to accurately know the timing of the layout, drawing, and constraint triggering of the view, and how to refresh the view correctly.

1/60 second of 60fps

1/60 second
Yes
CPU+GPU
For the entire calculation + drawing time interval, if the preparation for displaying data is not completed within this time period, the iOS application will display the previous frame, which is the so-called dropped frame. There are a large number of logic control units in the CPU, and a large number of data calculation units in the GPU, so the calculation efficiency of the GPU is much higher than that of the GPU. In order to improve efficiency, we can give the calculation logic to the GPU as much as possible. For articles related to the drawing process of a specific GPU, you can refer to the OpenGL topic

As you can see in the figure below, it starts after the main run loop ends.

Update cycle

Layout

The layout of a view refers to its position and size on the screen. Every view has a

frame
, Used to define its position and size in the coordinate system of the parent view. UIView will provide some methods to notify the view that the layout has changed. It also provides a series of methods for developers to rewrite to handle the operations after the view layout is completed.

layoutSubviews()

  • This method is used to handle the rearrangement (position, size) of a view and all its subviews. It provides the frame of the current view and all subviews.

  • The overhead of this method is expensive, because it acts on all sub-views and calls all sub-views level by level

    layoutSubviews() method
    .

  • The system will call this method when recalculating the frame, so when we need to set a specific frame, we can override this method.

  • Never call this method directly to refresh the frame. During the running loop, we can trigger this method by calling other methods, so that the overhead will be much smaller.

  • When UIView

    layoutSubviews()
    After the call is completed, its ViewController will be called
    viewDidLayoutSubviews()
    Method while
    layoutSubviews()
    The method is the only reliable method after the view layout is updated. So for the logic code related to the view frame should be placed
    viewDidLayoutSubviews()
    Method instead of
    viewDidLoad
    or
    viewDidAppear
    In this way, you can avoid using stale layout information.

layoutSubviews is called before the system recalculates the frame or after the frame is recalculated. (Preliminary estimate is after calculation)

Automatic refresh triggers

There are many events that will automatically mark that a view has changed its layout in order to facilitate

layoutSubviews()
The method is called the next time it is executed, instead of manually doing these things by the developer.

Some methods of automatically notifying the system that the layout has changed are:

  • Resize the view
  • Add a subview
  • The user slides the UIScrollView (UIScrollView and its parent view will be called
    layoutSubviews()
    )
  • User rotating device
  • Update the constraints of the view

These methods will tell the system to recalculate the position of the view, and eventually will be automatically called

layoutSubviews()
method. In addition, there are ways to directly trigger
layoutSubviews()
Call.

setNeedsLayout()

  • setNeedsLayout()
    Is a trigger
    layoutSubviews()
    The method that causes the least overhead. It will directly tell the system that the layout of the view needs to be recalculated.
    setNeedsLayout()
    It will execute and return immediately, and the view will not be updated before returning.
  • When the system is called step by step
    layoutSubviews()
    After that, the view will be in the next
    Update cycle
    Update. Even though
    setNeedsLayout()
    There is a certain time interval between redrawing and layout of the view, but this time interval will not be long enough to affect user interaction.

setNeedsLayout()
When did it return

layoutIfNeeded()

  • carried out
    layoutIfNeeded()
    Later, if the view needs to update the layout, the system will immediately call
    layoutSubviews()
    Method to update instead of
    layoutSubviews()
    Method joins the queue, waiting for the next time
    Update cycle
    Call again
  • When we are calling
    setNeedsLayout()
    Or after other events that automatically trigger the refresh, execute
    layoutIfNeeded()
    Method that can be triggered immediately
    layoutSubviews()
    method.
  • If a view does not need to update the layout, execute
    layoutIfNeeded()
    Method will not trigger
    layoutSubviews()
    method. For example, when we execute twice in a row
    layoutIfNeeded()
    Method, the second execution will not trigger
    layoutSubviews()
    method.

use

layoutIfNeeded()
Method, the layout and redrawing of the subview will happen immediately and can be completed before the method returns (unless the view is animating). If you need to rely on a new view layout and don t want to wait for the view to update to the next
Update cycle
Just finished, use
layoutIfNeeded()
Method is very useful. In addition to this scenario, generally use
setNeedsLayout()
Method wait till next
Update cycle
It is enough to update the view, which ensures that the view is updated only once in a run loop.

currently using

Constraint animation
This method is very useful when it comes to time. The general operation is to call it once before the animation starts
layoutIfNeeded()
Method to ensure that the layout update has been completed before the animation. After configuring our animation, in
animation block
Inside, adjust again
layoutIfNeeded()
Method, you can update to the new state.

Display

View display contains attributes that do not involve views and sub-views

size
with
position
, Such as: colors, text, pictures, and
Core Graphic drawing
. The display channel contains methods similar to the layout channel that triggers the update. They are all called by the system when the system detects a change, and we can also manually trigger the refresh.

draw(_:)

UIView

draw
(Objective-C is
drawRect
) Method, acting on
View content
Like
layoutSubviews()
Acting on the view
size
with
position
. However, this method will not trigger the subview
draw
(Objective-C is
drawRect
)method. This method cannot be called directly by the developer. We should be triggered by the call during the run loop.
draw
Method of other methods to trigger
draw
method.

setNeedsDisplay()

setNeedsDisplay()
Method is equivalent to
setNeedsLayout()
method. It will set a
Internal mark
To mark that the content of this view needs to be updated, but it returns before the view is redrawn. Then, in the next
Update cycle
The system will traverse all the
mark
View and then call their
draw
method. If you only need the next
Update cycle
Redraw part of the content of the view, you can call
setNeedsDisplay()
Method and pass
rect
Properties to set the part we need to redraw.

In most cases, I want to be in the next

Update cycle
Update the UI components on a view, by automatically setting the internal
Content update
Mark instead of manually calling
setNeedsDisplay()
method. However, if a view (
aView
) Is not directly bound to UI components, but we also hope that this view can be redrawn every time we update, we can observe the view (
aView
) Property setter method (KVO) to call
setNeedsDisplay()
Method to trigger the appropriate view update.

When you need to perform custom drawing, you can rewrite

draw
method. The following can be understood through an example.

  • in
    numberOfPoints
    of
    didSet
    Method call
    setNeedsDisplay()
    Method that can be triggered
    draw
    method.
  • By rewriting
    draw
    Methods to achieve the effect of drawing different styles in different situations.
class MyView : UIView { var numberOfPoints = 0 { didSet { setNeedsDisplay() } } override func draw ( _ rect : CGRect ) { switch numberOfPoints { case 0 : return case 1 : drawPoint(rect) case 2 : drawLine(rect) case 3 : drawTriangle(rect) case 4 : drawRectangle(rect) case 5 : drawPentagon(rect) default : drawEllipse(rect) } } } Copy code

Not like

layoutIfNeeded
Can be triggered immediately
layoutSubviews
That way, there is no way to directly trigger a view's content update. The update of the view content must wait until
Next update cycle
Go to redraw the view.

Constraints

In automatic layout, the layout and redrawing of the view requires three steps:

  1. Update constraints
    : The system will calculate and set all necessary constraints on the view.
  2. Layout stage
    :
    layout engine
    Calculate the frame of the view and lay out them.
  3. Show process
    : End the update cycle and redraw the view content, if necessary, it will call
    draw
    method.

updateConstraints()

  • updateConstraints
    The role in auto layout is like
    layoutSubviews
    In the frame layout, and
    draw
    It has the same effect in content redrawing.
  • updateConstraints
    Methods can only be overridden and cannot be called directly.
  • updateConstraints
    The method generally only implements those constraints that need to be changed. For constraints that do not need to be changed, we try not to write them in it as much as possible.
  • Static constraints
    Should also be in the interface constructor, view initialization method, or
    viewDidLoad()
    Method, not in
    updateConstraints
    Implemented in the method.

There are some ways to

Automatically trigger the update of constraints
. Set one inside the view
update constraints
Logo, which will be in the next
update cycle
In, trigger
updateConstraints
Method call.

  • Activate or deactivate constraints;
  • Change the priority and constant value of the constraint;
  • Remove constraints

In addition to automatically triggering the update of constraints, there are also the following methods to manually trigger the update of constraints.

setNeedsUpdateConstraints()

transfer

setNeedsUpdateConstraints
The method can ensure that the constraints are updated in the next update cycle. It triggers
updateConstraints
The method is by marking that a certain constraint of the view has been updated. The way this method works is similar to
setNeedsLayout
with
setNeedsDisplay
similar.

updateConstrainsIfNeeded()

This method is equivalent to

layoutIfNeeded
, But in auto layout, it will check
constraint update
Mark (this mark can be set automatically or through
setNeedsUpdateConstraints
,
invalidateInstinsicContentSize
Method to set manually). If it determines that the constraint needs to be updated, it will trigger immediately
updateConstraints
Method instead of waiting until the end of the run loop.

invalidateInstinsicContentSize()

Some views in Auto Layout have

intrinsicContentSize
Attribute, this is the view according to its
content
The resulting natural size. One view
intrinsicContentSize
usually
Determined by the constraints of the contained elements
, But you can also provide custom behavior through overloading. transfer
invalidateIntrinsicContentSize()
Will set a mark to indicate the view
intrinsicContentSize has expired
, Needs to be recalculated in the next layout stage.

How it all connects

The layout, display, and constraints all follow a similar pattern, such as how they update and how to force updates at different points in the run loop. Any component has a method to actually update (

layoutSubviews
,
draw
, with
updateConstraints
), these methods can be overridden to manually manipulate the view, but do not call them explicitly under any circumstances. This method will only be called at the end of the run loop. If the view is marked, it tells the system that the view needs to be updated.
mark
words. There are some actions that will automatically set this
mark
, There are also some methods that allow it to be set explicitly. For layout and constraint-related updates, if you can t wait in
run loop
If you update at the end (for example: other behaviors depend on the new layout), there are also methods that allow you to update immediately and ensure
update layout
Can be marked correctly. The following table lists how any component will be updated and its corresponding method.

LayoutDisplayConstraintsMethod intent
layoutSubviewsdrawupdateConstraintsThe method of performing the update can be re
Write, but cannot be called
setNeedsLayoutsetNeedDisplaysetNeedsUpdateConstaints
invalidateInstrinsicContentSize
The displayed marker view needs to be updated in the next update cycle
layoutIfNeeded-updateConstraintsIfNeededUpdate the marked view immediately
Add view,
reset size,
set frame (need to change bounds),
slide ScrollView
rotate device
Changes that occur inside the bounds of the viewActivate and deactivate constraints
modify the priority and constant values of constraints
constraints to remove constraints
Events that implicitly trigger a view update

The figure below summarizes

Update cycle
with
Event loop
The interaction between and indicates where the above methods point to the next step in the cycle. You can call it realistically
layoutIfNeeded
with
updateConstraintsIfNeeded
Anywhere in the run loop, but it should be noted that these two methods have potential overhead. in case
update constrints
,
update layout
,
needs display
The flag is set, at the end of the run loop
Update Cycle
The constraints, layout, and display content will be updated. Once these updates are completed, the run loop will restart.


Reference: Demystifying iOS Layout