preface

UIStackView is a set of apis that Apple has introduced in iOS9. It is a great way to reduce the repetitive work of manually writing or dragging constraints, and to automate changes in the number of permutations and elements.

Because of its iOS9+ threshold, which is generally compatible with iOS8 in Domestic apps, and the real power of UIStackView is Storyboard, even though there are dark technologies like FDStackView that can lower the threshold of introduction, The team would prefer to implement Autolayout using pure navigation /SnapKit.

UIStackView is, as its name suggests, a view stack, in other words: it is a container. These are container controls that we think of as UITableView, UICollectionView. Compared to these two traditional containers, UIStackView is positioned like this:

  • Easy to write
  • Easy to maintain
  • Convenient combination stack
  • lightweight

Another difference between UIStackView and a traditional container class is that although it inherits from UIView, it cannot render itself. For example, its backgroundColor is invalid, so it is destined to work in parallel with UIView. It helps UIView handle layout issues such as the position and size of its child views.

However, although it handles layout, it doesn’t entirely replace constraint, as much as a stack can. Besides, such as the intrinsic size of its sub-views, or the CHP (Content Hugging Priority), Content Resistance Priority (CRP), including the layout of UIStackView itself, are inseparable from handwritten constraints. So a good Autolayout wrapper library is still needed.

Want to say, its positioning should be between handwritten constraints and UITableView/UICollectionView tools. Just like the iPad is a device between a laptop and a phone. It no one can replace, but it has the self-confidence, that is handwritten Constraint is very tired, but use UITableView/UICollectionView feel very heavy.

For example, the following can be seen as a nesting of these uistackViews if implemented natively:

To the chase

1. Initialization

In the minimalist case, view Hierarchy introducing UIStackView is a situation like this:

To implement this simple model, first create a UIStackView:

let stackView = UIStackView(a)Copy the code

And then add it to the parent UIView

view.addSubview(stackView)
Copy the code

Next, we add the child View instance to the UIStackView, and instead of calling the traditional addSubview, we call the UIStackView instance

stackView.addArrangedSubview(subView1)
stackView.addArrangedSubview(subView2)
Copy the code

This is where the UIStackView arrangedSubviews have a value

open var arrangedSubviews: [UIView] { get }
Copy the code

ArrangedSubviews and subviews have different order meanings:

  • subviews: Its order is actually layersCovering the order, of the view elementThe z axis
  • arrangedSubviewsThe order represents the stackPlace the order, of the view elementX-axis and Y-axis

In practice, I used an extension like this to batch add:

extension UIStackView {
    func addArrangedSubviews(_views: [UIView?] ) {
            views.compactMap({ $0 }).forEach { addArrangedSubview($0)}}}Copy the code

Now that UIStackView is UIView, which means that addSubview or addArrangedSubview can be called, what is their relationship?

  • If an element is notaddSubview, the callarrangedSubviewsautomaticallyaddSubview
  • When an element isremoveFromSuperview,arrangedSubviewsIt also removes them synchronously
  • When an element isremoveArrangedSubview, will not triggerremoveFromSuperviewIt’s still in the view structure

2. Control the layout

UIStackView has a few important properties, and these are the only switches we need to control, so solving the layout of a page translates to how these limited switches describe the elements of the page.

2.1. The axis axis

  • horizontalThe horizontal direction(the default)
  • verticalThe vertical direction

2.2 distribution distribution

Definition:

The layout that defines the size and position of the arranged views along the stack view’s axis.

Describes layout relationships between elements aligned with axis

  • .Fill (the default) depends on two priorities: Compression resistance and hugging

  • .fillequally according to the same width/height layout

  • In accordance with the standard content size, the calculation is based on the proportion

  • EqualSpacing layout, if not fit, according to compression resistance compression

  • Equalpositions: Spacing between positions is greater than or equal to the spacing defined by spacing. If spacing is greater than or equal to spacing, press for spacing according to compression resistance

2.3. alignment

define

The alignment of the arranged subviews perpendicular to the stack view’s axis.

Describes layout relationships between elements that are perpendicular to Axis

  • .fill is as full as possible

  • . Leading When axis is vertical, align it in the leading direction equivalent to align it in the top direction when axis is horizontal

  • Top Aligned with top when axis is horizontal is equivalent to aligned with leading when axis is vertical

  • Trailing when axis is vertical, trailing is equivalent to trailing when axis is horizontal, trailing is bottom

  • Bottom Trailing when axis is horizontal is equivalent to trailing when axis is vertical

  • Center is centered and aligned

  • .firstbaseline is useful only on the horizontal axis, align by first line baseline

  • LastBaseline is useful only on the horizontal axis, align with the baseline at the bottom of the article

2.4 spacing

Sets the margin values between elements

2.5. IsBaselineRelativeArrangement (default false)

Determines whether the vertical axis participates in the layout according to the baseline if it is text.

2.6. IsLayoutMarginsRelativeArrangement (default false)

If open, the page layout is displayed, and if closed, the bounds are displayed

3. Ability to customize margins

1. Set the margin after an element

func setCustomSpacing(_ spacing: CGFloat, 
	      after arrangedSubview: UIView)
Copy the code

2. Get the margin after an element

func customSpacing(after arrangedSubview: UIView) -> CGFloat
Copy the code

3. Get the default margins for internal elements

class let spacingUseDefault: CGFloat
Copy the code

4. Get the default margin between adjacent views

class let spacingUseSystem: CGFloat
Copy the code

But it should be noted that custom margins are features of iOS11+, if you need iOS9 compatibility, need to introduce a hack scheme:

extension UIStackView {
    // How can I create UIStackView with variable spacing between views?
    func addCustomSpacing(_ spacing: CGFloat, after arrangedSubview: UIView) {
        if #available(iOS 11.0, *) {
            self.setCustomSpacing(spacing, after: arrangedSubview)
        } else {
            let separatorView = UIView(frame: .zero)
            separatorView.translatesAutoresizingMaskIntoConstraints = false
            switch axis {
            case .horizontal:
                separatorView.widthAnchor.constraint(equalToConstant: spacing).isActive = true
            case .vertical:
                separatorView.heightAnchor.constraint(equalToConstant: spacing).isActive = true
            }
            if let index = self.arrangedSubviews.firstIndex(of: arrangedSubview) {
                insertArrangedSubview(separatorView, at: index + 1)}}}Copy the code

4. Handle layout changes

The layout of UIStackView synchronizes array arrangedSubviews dynamically. The changes include:

  • additional
  • delete
  • insert
  • hidden

Note: For isHidden, UIStackView automatically uses the space, equivalent to temporarily deleting it, unlike Autolayout, which does not break the constraint.

5. Nested

How do you get layers of StackViews to work together? The answer is constraint completeness:

  • Make sure the layout on the parent View is flexible. For example, do not set the width or height of a View that needs to be stretched
  • If the size is fixed, CHP and CRP cannot solve the problem
  • Ensure that the intrinsic size of a subview can be correctly calculated

conclusion

Even if you are currently using some kind of Autolayout wrapper, introducing UIStackView is a great way to reduce the complexity of page constraints. It allows you to take a big picture view of typography, rather than getting bogged down in the restrictive details of each element. Best of all, it offers lower maintenance costs (like inserting a button in a blanket constraint) and higher fault tolerance (handwritten constraints create semantic conflicts).

—– Updated on January 7 at —-

Some students asked what it was like to use it in real combat. Here’s a quick example:

This is a chat bubble with translation function, just focus on the dark gray area

  • A transient is in translation
stackView.addArrangedSubviews([contentLabel,
                               translationLoadingSeparatorLine,
                               translationLoadingView])
Copy the code

  • The other is successful translation
stackView.addArrangedSubviews([contentLabel,
                               translationResultTopSeparatorLine,
                               translationResultTextLabel,
                               translationResultBottomSeparatorLine,
                               translationResultBottomLabel])
Copy the code

Switch the layout of a page by emptying and reinstalling the corresponding stackView. Isn’t it a little more elegant?