FlexBox layout on iOS

Why learn about FlexBox?

Recently, I have heard about FlexBox from time to time. In addition to using FlexBox in two well-known cross-platform projects, Weex and React Native , AsyncDisplayKit also introduced FlexBox.

Let's talk about the two layout methods that iOS itself provides us:

  • Frame, directly set the horizontal and vertical coordinates, and specify the width and height.
  • Auto Layout, which is laid out by setting constraints on relative positions.

Frame doesn't have much to say, directly specify the coordinates and size, and set the absolute value.

Auto LayoutWell-intentioned in and of itself, trying to free us from frames and stereotypes about coordinates and sizes. Instead, use the relative positional relationship between UIs to set corresponding constraints for layout.

But Auto Layoutgood intentions don't do good things, and its syntax is stinky and long! I've been learning iOS for two years, and I can count on my fingers to use the native Auto Layoutsyntax. It can only be used by a third-party library like Masonry .

How Auto Layout works

After talking about Auto Layoutthe use of , let's take a look at how it works.

In fact, Auto Layoutthe constitute a series of conditions, which become an equation. Then solve for the coordinates and size of the Frame.

For example, let's set up a UI named A:

A.center = super.center
A.width  = 40
A.height = 40

则:A.frame = (super.center.x-40/2,super.center.y-40/2,40,40)

Set up another B:

B.width  =  A.width
B.height =  A.height
B.top    =  A.bottom + 50
B.left   =  A.left

则:B.frame = ( A.x , A.y + A.height + 50 , A.width , A.height )

As shown in the figure:

ZenonHuang_FlexBox_1

Cassowary

Auto LayoutThere is an internal algorithm specially used to deal with constraints. I always thought that it was developed by Apple itself. After consulting the information, I found that it came from Cassowaryan .

Cassowary is a parsing toolkit that can effectively parse linear equation systems and linear inequality systems. Inequality and equality relationships will always appear in the user interface. Cassowary has developed a rule system that can describe the relationship between views through constraints. Constraints are rules that express the position of one view relative to another.

Those who are interested can learn more about the implementation of this algorithm.

Performance comparison of Frame / Auto Layout / FlexBox

Auto LayoutAfter some understanding of , it is easy to conclude Auto Layoutthat the performance is worse than Frame due to redundant computation.

But how much difference? What about the performance of FlexBox?

Here, according to the test code in Auto Layout's Layout Algorithm Talking about Performance, the Frame / Auto Layout / FlexBox is laid out, and the layout time of 10 to 350 UIViews is measured in sections. Take the average of 100 layout times as the result, and the time-consuming unit is seconds.

The result is as follows:

ZenonHuang_FlexBox_2 Although the test results are inevitably biased, according to the line chart, it can be clearly found that the layout performance of FlexBox is relatively close to Frame.

60 FPSAs a gold standard for iOS fluency, the layout is required to be completed within 0.0166667 s, and Auto Layoutwhen there are more than 50 views, it may start to have problems maintaining smoothness.

The machine configuration used for this test is as follows:

ZenonHuang_FlexBox_3

Using Xcode9.2, iPad Pro (12.9-inch) (2nd generation) simulator.

The project code for testing the layout is uploaded on GitHub

What is FlexBox?

FlexBoxIt is a UI layout method and is supported by all browsers. FlexBoxThe first is based on the box -like model , Flexible means flexibility, making it adaptable to different screens, complementing the flexibility of the box-like model.

FlexBoxThink of each view as a rectangular box, with inner and outer margins, arranged along the main axis, and there is no dependency between views at the same level.

Auto LayoutSimilar to , FlexBoxa descriptive language is used for layout, instead of using absolute coordinates for layout directly like Frame.

The main idea of ​​flex layout is to give the Flex Container the ability to change the width and height of the Flex Item to fill the available space (mainly to accommodate all types of display devices and screen sizes).

Most importantly, FlexBoxlayout is orientation-independent, and conventional layout designs lack flexibility to support large and complex applications (especially when it comes to orientation shifts, scaling, stretching, and shrinking, etc.).

FlexBox composition

Elements that take a FlexBoxlayout , called Flex Container.

Flex ContainerAll child elements of , called Flex Item.

ZenonHuang_FlexBox_4

The following will talk about some concepts in FlexBox to facilitate the use of FlexBox later.

Flex Container

As mentioned earlier, FlexBoxone of the features is that there is no dependency between views.

Flex ItemThe arrangement of , depends on Flex Containerthe property settings of , rather than setting each other.

So let's talk about Flex Containnerthe .

Flex Direction

FlexBox has 主轴(main axis)a 侧轴(cross axis)concept of and . The side axis is perpendicular to the main axis.

They can be horizontal or vertical.

The main axis defaults to Row, and the side axis defaults to Column:

0DF515D1-1EEF-4C38-9782-F875C1433AE0

Flex DirectionDetermines Flex Containnerthe spindle arrangement direction in .

The main axis defaults to Row (left to right):

87691D2C-34C3-4805-B960-4D8217717D98

At the same time, RowRevers can also be set (from right to left):

1F430BC5-A0BE-474B-9791-23F2B308AEE9

Column (top to bottom):

AA2DF5F6-1164-4440-ACF2-9897D0D82730

ColumnRevers (bottom to top):

C33D6321-66C3-4A78-B429-E82B3F83CB6E

Flex Wrap

Flex Wrap determines how the view wraps when it doesn't fit on the grid.

Flex Wrap is set to NoWrap by default, which will not wrap, and will always be arranged along the main axis to the outside of the screen:

9C0FD351-E504-4A6B-A442-E3DE1E084FAC

Set to Wrap, then when the space is insufficient, the line will be automatically wrapped:

2C14AAC1-6DDB-4450-B393-5497E0743AFC

Set WrapReverse, the wrapping direction is opposite to Wrap:

35B10621-C2BD-4699-B5B5-4383B35F510E

This is a very useful property. For example, typically 九宫格布局, if iOS is not used UICollectionViewto do it , then it needs to save 9an instance, and then make judgments and calculate the frame, and the maintainability is really not high. Using UICollectionViewcan solve the layout very well, but many scenarios cannot be reused, and it is not particularly simple to do.

For FlexBox layout Flex Wrap, Wrapit can be done directly with property settings.

Similar solutions on mobile platforms, such as Linear Layout for Android and UIStackView for iOS, are far less powerful than FlexBox.

Display

Display chooses whether to calculate it, the default is Flex. If set to None, the calculation of this view is automatically ignored.

Useful when displaying UI based on logic.

For example, our existing business needs to display the Tencent identity logo. According to the general practice, multiple icons are connected to each other in a row, different distances are set according to their identities, and other icons are hidden at the same time, which is troublesome for comparison. The best way for iOS is to use UIStackView, which has issues such as version compatibility. With the FlexBox layout, when it is not a certain identity, as long as the Display is set to None, it will not be included in the UI calculation.

Justify Content

Justify ContentUsed to Flex Itemdefine the alignment on the main axis: FlexStart (spindle start point alignment), FlexEnd (spindle end point alignment), Center (center alignment).

And SpaceBetween (justified):

7F4F84F0-6B50-462A-BDA6-D11D087FFCE0

Set justify so that the spacing Flex Itembetween is equal.

SpaceAround (aligned with equal margins):

3B7E08DD-6F78-4A8D-9D56-07565E5F9E24

Make the margins equal Flex Itemaround

Align Items

Align ItemsFlex ItemDefines the alignment on the side axis.

Align ItemsYou can set FlexStart, FlexEnd, Center, SpaceBetween, SpaceAround in the same way Justify Contentas .

Align ItemsYou can also set Baseline (baseline alignment):

25B54897-E6D4-4742-837E-13E5E4D827DA

As shown, it is based on the baseline alignment Flex Itemof the first line of text.

BaselineThis value is equivalent to if theFlex Item inline and side axes of and are the same . FlexStartOtherwise, this value will participate in baseline alignment.

Align ItemsCan also be set to Stretch:

F40B6D31-225F-4A99-9209-15886475CC1F

StretchLet the Flex Itemstretch fill the whole Flex Container. StretchWill make Flex Itemthe outer margin as close as possible to the size of the row or column in accordance with the corresponding property constraints.

If the value is Flex Itemnot set, or is set auto, it will occupy the entire Flex Containerheight

Align Content

Align ContentIt is also the alignment of the side axis Flex Itemin , but it is based on an entire row as the smallest unit.

Note that this property has no effect if there is Flex Itemonly one axis (one line only ).Flex Itme

Adjust FlexWrapto Wrap, the effect will be displayed:

6C8FB222-16DC-4B6E-A2D9-251C4CA69F8E

Flex Item

After I finished talking about Flex Containerthe properties of , I finally talked about the properties of Flex Item. Flex ContainerThe properties in it all act on what it contains, Flex Itemand Flex Itemthe properties in it all act on itself.

AlignSelf

AlignSelfYou can make a single Flex Itemand other Flex Itemhave a different alignment, overriding the Align Itemsproperty .

The default is , which automeans inherited properties. If it doesn't itself , it's equivalent to .Flex ContainerAlign ItemsFlex ContainerStretch

FlexGrow

FlexGrowAssigned can be 剩余空间set 比例. i.e. how to expand.

FlexGrowThe default value is 0, if not defined FlexGrow, the layout will not have the right to allocate the remaining space.

E.g:

The overall width is 100, the width of sub1 is 10, and the width of sub2 is 20, so 剩余空间it is 70.

The setting FlexGrowis to assign the ratio of this 70 width.

Now for the ratio question:

If all Flex Itemof the FlexGrowproperties are 1, if there is remaining space, divide the remaining space equally.

If Flex Itemthe FlexGrowproperty of one is 2, and the rest Flex Itemare 1, then the former will occupy the remaining space Flex Itemmany 1times more than the other .

FlexShrink

Contrary to FlexGrowdealing with space remaining, it FlexShrinkis used to deal with insufficient space. That is how to shrink.

FlexShrinkDefaults to 1, i.e. the item will shrink if there is not enough space

If all Flex Itemof the FlexShrinkproperties are 1, when the space is insufficient, it will be scaled down proportionally.

If Flex Itemthe FlexShrinkattribute of one is 0, and the rest Flex Itemare 1, then when the space is insufficient, theFlexShrink former of is will not shrink.0

FlexBasis

FlexBasisFlex ItemDefines what is occupied before the extra space is allocated main size(主轴空间). Based on this property, the browser calculates whether there is excess space on the main axis.

FlexBasisThe default value is auto, which is Flex Itemthe original size of .

To learn more about FlexBox properties, you can refer to A Complete Guide to Flexbox

Implementation of FlexBox -- Yoga

As mentioned at the beginning, the FlexBox layout has been applied to several well-known open source projects, which use Yoga from Facebook.

Yoga  is a Flexbox layout engine implemented by C. Its performance and stability have been well verified in major projects, but the disadvantage is that Yoga only implements a subset of the W3C standard.

The following will YogaKitdo .

Based on the basic understanding of the FlexBoxlayout above, make some simple layouts.

YGLayout

The key to the whole YogaKit is in the YGLayoutobject . Use YGLayoutto set layout properties.

In UIView+Yoga.hthe file of :

/**
 The YGLayout that is attached to this view. It is lazily created.
 */
@property (nonatomic, readonly, strong) YGLayout *yoga;

/**
 In ObjC land, every time you access `view.yoga.*` you are adding another `objc_msgSend`
 to your code. If you plan on making multiple changes to YGLayout, it's more performant
 to use this method, which uses a single objc_msgSend call.
 */
- (void)configureLayoutWithBlock:(YGLayoutConfigurationBlock)block
    NS_SWIFT_NAME(configureLayout(block:));

You can see yogaa YGLayoutread-only object named , and a configureLayoutWithBlock:(YGLayoutConfigurationBlock)blockmethod , and also use NS_SWIFT_NAME()to define the method name in Swift.

In this way, we can directly use the instance object of UIView to directly set its corresponding layout.

isEnabled

YGLayout.his defined isEnabledin .

/**
 The property that decides during layout/sizing whether or not styling properties should be applied.
 Defaults to NO.
 */
@property (nonatomic, readwrite, assign, setter=setEnabled:) BOOL isEnabled;

isEnabledThe default is NO, we need to set it to during layout YESto enable the Yoga style.

applyLayoutPreservingOrigin:

For this method, the header file explains it like this:

/**
 Perform a layout calculation and update the frames of the views in the hierarchy with the results.
 If the origin is not preserved, the root view's layout results will applied from {0,0}.
 */
- (void)applyLayoutPreservingOrigin:(BOOL)preserveOrigin
    NS_SWIFT_NAME(applyLayout(preservingOrigin:));

Simply put, it is used to perform layout calculations. So, once the layout code is complete, 根视图call this method on the properties of the yoga object to apply the layout to 根视图and 子视图.

Layout demo

The following example shows how to use Yogafor FlexBoxlayout.

Centered

[self configureLayoutWithBlock:^(YGLayout * layout) {
                layout.isEnabled = YES;
                layout.justifyContent =  YGJustifyCenter;
                layout.alignItems     =  YGAlignCenter;
            }];
        
[self.redView configureLayoutWithBlock:^(YGLayout * layout) {
                layout.isEnabled = YES;
                layout.width=layout.height= 100;
            }];
            
[self addSubview:self.redView];

[self.yoga applyLayoutPreservingOrigin:YES];
    

The effect is as follows:

75F9B6E0-1C63-4A91-97EE-3F3A6087BE3B
Our real layout code, just set Flex Container the and justifyContent of . alignItems

Nested layout

Let a be viewslightly smaller than it superView, with a margin of 10:

    [self.yellowView configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.margin = 10;
                layout.flexGrow = 1;
            }];
    [self.redView addSubview:self.yellowView];

The effect is as follows:

E2BE382B-85DA-47E9-93D3-FD94DD7A1ABA

The layout code only uses settings, View's marginand flexGrow.

equally spaced

Arrange a group of views vertically and equally spaced:

            [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                
                layout.justifyContent =  YGJustifySpaceBetween;
                layout.alignItems     =  YGAlignCenter;
            }];
            
            for ( int i = 1 ; i <= 10 ; ++i )
            {
                UIView *item = [UIView new];
                item.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
                                                  saturation:( arc4random() % 128 / 256.0 ) + 0.5
                                                  brightness:( arc4random() % 128 / 256.0 ) + 0.5
                                                       alpha:1];
                [item  configureLayoutWithBlock:^(YGLayout *layout) {
                    layout.isEnabled = YES;
                    
                    layout.height     = 10*i;
                    layout.width      = 10*i;
                }];
                
                [self addSubview:item];
            }

The effect is as follows:

8E963A9D-F9AF-47A6-966C-5AEAA836E598

As long as it is Flex Containerset layout.justifyContent = YGJustifySpaceBetween, it can be done easily.

Equal spacing, automatic width setting

Make two 100heights viewcentered vertically, equally wide, and equally spaced, with an interval of 10. Calculate their widths automatically:

            [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.flexDirection  =  YGFlexDirectionRow;
                layout.alignItems     =  YGAlignCenter;
                
                layout.paddingHorizontal = 5;
            }];
            
            
            
            YGLayoutConfigurationBlock layoutBlock =^(YGLayout *layout) {
                layout.isEnabled = YES;
                
                layout.height= 100;
                layout.marginHorizontal = 5;
                layout.flexGrow = 1;
            };
            
            
            [self.redView configureLayoutWithBlock:layoutBlock];
            [self.yellowView configureLayoutWithBlock:layoutBlock];
            
            [self addSubview:self.redView];
            [self addSubview:self.yellowView];

The effect is as follows:

7221D26D-E78E-4AF3-B129-6D983C842

We only need to set Flex Containerthe paddingHorizontal, and Flex Itemthe marginHorizontal, flexGrow on it. And can reuse Flex Itemthe layout layout style.

UIScrollView arrangement automatically calculates contentSize

Arrange some in UIScrollVieworder view, and automatically calculate contentSize:

            [self configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;
                layout.justifyContent =  YGJustifyCenter;
                layout.alignItems     =  YGAlignStretch;
            }];
            
            UIScrollView *scrollView = [[UIScrollView alloc] init] ;
            scrollView.backgroundColor = [UIColor grayColor];
            [scrollView configureLayoutWithBlock:^(YGLayout *layout) {
                layout.isEnabled = YES;

                layout.flexDirection = YGFlexDirectionColumn;
                layout.height =500;
            }];
            [self addSubview:scrollView];

            UIView *contentView = [UIView new];
            [contentView configureLayoutWithBlock:^(YGLayout * _Nonnull layout) {
                layout.isEnabled = YES;
            }];
            
            
            for ( int i = 1 ; i <= 20 ; ++i )
            {
                UIView *item = [UIView new];
                item.backgroundColor = [UIColor colorWithHue:( arc4random() % 256 / 256.0 )
                                                  saturation:( arc4random() % 128 / 256.0 ) + 0.5
                                                  brightness:( arc4random() % 128 / 256.0 ) + 0.5
                                                       alpha:1];
                [item  configureLayoutWithBlock:^(YGLayout *layout) {
                    layout.isEnabled = YES;

                    layout.height     = 20*i;
                    layout.width      = 100;
                    layout.marginLeft = 10;
                }];

                [contentView addSubview:item];
            }
            
            [scrollView addSubview:contentView];
            [scrollView.yoga applyLayoutPreservingOrigin:YES];
            scrollView.contentSize = contentView.bounds.size;

The effect is as follows:

679CD74E-2C82-4B4F-871A-46D42832C8CB

The layout UIScrollViewmainly uses a middle contentView, which plays scrollviewthe contentSize. It should be noted here that the setting should be made after callingscrollview , otherwise the result will not be obtained.applyLayoutPreservingOrigin:

For the usage of UIScrollView, I haven't found a more official example on the Internet at present.

The sample code used above has been uploaded to GitHub

Summarize

FlexBox is indeed a layout method that is very suitable for mobile terminals, with clear semantics and stable performance. Now mobile UI views are becoming more and more complex, especially after all browsers have supported FlexBox, it is necessary for mobile developers to understand the new solution.

After you are proficient in using YogaKit, you can also try to encapsulate a set of layout code to speed up development efficiency.


Author: ZenonHuang
Link: https://juejin.im/post/5a33a6926fb9a045104a8d3c
Source: Nuggets The
copyright belongs to the author. For commercial reprints, please contact the author for authorization, and for non-commercial reprints, please indicate the source.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325416787&siteId=291194637