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 axisarrangedSubviews
The 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 not
addSubview
, the callarrangedSubviews
automaticallyaddSubview
- When an element is
removeFromSuperview
,arrangedSubviews
It also removes them synchronously - When an element is
removeArrangedSubview
, will not triggerremoveFromSuperview
It’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
horizontal
The horizontal direction(the default)vertical
The 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?