# Maintainable code layout in UIKit
###### tags: `iOS@Taipei` `iOS` `concise-swift` `UIStackView`
This post is for those who use code layout and feel unhappy about so many constraints and offsets/insets should define and set.
It's really the time to reduce code layout process time and take more time on learning `SwiftUI`.
> **Warning**
> In this post we use `UIStackView` and `layoutMargins` for building concise custom view, that may not suitable for complex animation.
### 📐 Autolayout is not very easy to maintain
let's not talk about `frame-layout` maintainability.

When building custom view, we have to make sure the subviews of the custom view share the size and spaces correctly, also we might want some padding on custom view or on those subviews.
If we use pure `Autolayout`, it will be hard to deal with specific design requirement. Says we have to make sure 5 UI element horizontal spaces always equal to `5`, we have to anchor each of them horizontal space `4` times to make it happen.
Also when we want to set four direction padding for different value, you have to do four times anchor to make it.
```javascript=
cancelButton.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 8.0).active = true
cancelButton.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor, constant: -8.0).active = true
cancelButton.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 8.0).active = true
cancelButton.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor, constant: -8.0).active = true
```
Even if we using `SnapKit`, if the padding value is not the same, we still have to write code like:
```javascript=
cardView.snp.makeConstraints { (make) in
make.top.equalToSuperview().inset(16)
make.bottom.equalToSuperview()
make.leading.trailing.equalToSuperview().inset(20)
}
```
First, let's talk about how to make adpative custom view layout easier.
## 🥞 UIStackView
In WWDC15 session [Mysteries of Auto Layout, Part 1](https://developer.apple.com/videos/play/wwdc2015/218/) says `UIStackView` is **maintainable layout**. There's many handy properties to set in `UIStackView` and it can really help you to arrange subviews in autolayout.
The session also says that since `UIStackView` doesn't need to render background, so it is optimized to render fast than normal view.
> **The Session Conclusion**
> Start with Stack View, use constraints as needed.
Before using UIStackView, you may write code like:
```javascript=
let cardView = UIView()
addSubview(cardView)
cardView.snp.makeConstraints { (make) in
make.top.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalToSuperview()
}
cardView.addSubview(imageView)
imageView.snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview().inset(16)
make.trailing.equalToSuperview()
}
cardView.addSubview(titleLabel)
titleLabel.snp.makeConstraints { (make) in
make.top.leading.equalToSuperview().inset(16)
make.trailing.equalTo(imageView.snp.leading).offset(8)
}
cardView.addSubview(confirmButton)
confirmButton.snp.makeConstraints { (make) in
make.top.equalTo(titleLabel.snp.bottom).offset(12)
make.leading.bottom.equalToSuperview().inset(16)
}
cardView.addSubview(skipButton)
skipButton.snp.makeConstraints { (make) in
make.centerY.equalTo(confirmButton)
make.leading.equalTo(confirmButton.snp.trailing).offset(16)
}
```
With `UIStackView`, you can make concise and component-style code layout:
```javascript=
let cardView = UIView()
addSubview(cardView)
cardView.snp.makeConstraints { (make) in
make.top.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalToSuperview()
}
let buttonStackView = UIStackView(arrangedSubviews: [confirmButton, skipButton])
buttonStackView.axis = .horizontal
buttonStackView.spacing = 16
buttonStackView.alignment = .center
buttonStackView.distribution = .fill
let titleAndButtonStackView = UIStackView(arrangedSubviews: [titleLabel,
buttonStackView])
titleAndButtonStackView.axis = .vertical
titleAndButtonStackView.spacing = 12
titleAndButtonStackView.alignment = .leading
titleAndButtonStackView.distribution = .fill
let stackView = UIStackView(arrangedSubviews: [titleAndButtonStackView, imageView])
stackView.axis = .horizontal
stackView.spacing = 8
stackView.alignment = .fill
stackView.distribution = .fill
cardView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.top.leading.bottom.equalToSuperview().inset(16)
make.trailing.equalToSuperview()
}
```
Plus, you can use factory method like `makeButtonStackView()` to make your layout logic more readable.
## layoutMargins and layoutMarginsGuide
In WWDC18 session [UIKit: Apps for Every Size and Shape](https://developer.apple.com/videos/play/wwdc2018/235) introduce `layoutMargins` and `layoutMarginsGuide`.
**layoutMargins are padding** and the **layoutMarginsGuide is the square** that define space inside the padding.
We use layoutMarginsGuide to hook autolayout anchor inside the `layoutMargins` just like using safeAreaLayoutGuide to make sure the our view is in the safeAreaInsets.
Let me show you how to use `layoutMarginsGuide` to take your layout code readibility to the next level.
```javascript=
/// Before: Using pure autolayout concept
cardView.snp.makeConstraints { (make) in
make.top.leading.trailing.equalToSuperview().inset(16)
make.bottom.equalToSuperview()
}
imageView.snp.makeConstraints { (make) in
make.top.bottom.equalToSuperview().inset(16) // top and bottom padding
make.trailing.equalToSuperview() // trailing padding
}
titleLabel.snp.makeConstraints { (make) in
make.top.leading.equalToSuperview().inset(16) // leading padding
...
}
confirmButton.snp.makeConstraints { (make) in
...
make.leading.bottom.equalToSuperview().inset(16) // leading and bottom padding
}
/// After: Using layoutMarginsGuide
directionalLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 20, bottom: 8, trailing: 20)
cardView.snp.makeConstraints { (make) in
make.edges.equalTo(self.layoutMarginsGuide)
}
cardView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 16, leading: 16, bottom: 16, trailing: 0)
stackView.snp.makeConstraints { (make) in
make.edges.equalTo(cardView.layoutMarginsGuide)
}
```
## Combine all concepts
If we use `UIStackView`, `layoutMarginsGuide`, factory method and meaningful enum, we can make code really concise and easy to change any layout.
```javascript=
directionalLayoutMargins = Constant.Margins.FeatureTips
let cardView = makeCardView()
addSubview(cardView)
cardView.snp.makeConstraints { (make) in
make.edges.equalTo(self.layoutMarginsGuide)
}
let buttonStackView = makeActionButtonsStackView()
let titleAndButtonStackView = UIStackView(arrangedSubviews: [titleLabel, buttonStackView])
titleAndButtonStackView.axis = .vertical
titleAndButtonStackView.spacing = Constant.Spacing.TitleAndButtons
titleAndButtonStackView.alignment = .leading
titleAndButtonStackView.distribution = .fill
let stackView = UIStackView(arrangedSubviews: [titleAndButtonStackView, imageView])
stackView.axis = .horizontal
stackView.spacing = Constant.Spacing.TitleAndImageView
stackView.alignment = .fill
stackView.distribution = .fill
cardView.addSubview(stackView)
stackView.snp.makeConstraints { (make) in
make.edges.equalTo(cardView.layoutMarginsGuide)
}
```
### preservesSuperviewLayoutMargins
Set `.preservesSuperviewLayoutMargins` to true can make superview's `layoutMargins` affect subview, so the entire view can set `layoutMargins` just once. If you subview has animation any may go outside to superview's `layoutMargins`, you should set it to `false`.
## Conclusion