ioskrew/swiftlayout
*Yesterday never dies*
LayoutBuilder
LayoutBuilder is a DSL builder for setting up the UIView hierarchy; this allows subviews to be added to the parent view in a simple and visible way.
@LayoutBuilder var layout: some Layout {
view.sl.sublayout {
subview.sl.sublayout {
subsubview
subsub2view
}
}
}this is like below:
view.addSubview(subview)
subview.addSubview(subsubview)
subview.addSubview(subsub2view)AnchorsBuilder
AnchorsBuilder is a DSL builder for Anchors types that aids in the creation of autolayout constraints between views. It is mainly used within anchors, a method of Layout.
Anchors
Anchors have attributes for NSLayoutConstraint and can creates.
summary of NSLayoutConstraint
- first: Item1 and attribute1
- second: item2 and attribute2
- relation: relation(=, >=, <=), constant, multiplier
equation of constraint has following format: Item1.attribute1 [= | >= | <= ] multiplier x item2.attribute2 + constant
Detailed information about NSLayoutConstraint can be found here.
- It starts by getting the required properties using static values defined in Anchors.
``swift Anchors.top.bottom ``
- You can set up a second item (NSLayoutConstraint.secondItem, secondAttribute) through a relationship method such as equalTo.
``swift superview.sl.sublayout { selfview.sl.anchors { Anchors.top.equalTo(superview, attribute: .top, constant: 10) } } ``
this is same as following constraint format:
`` selfview.top = superview.top + 10 ``
- Attributes in Anchors that do not have a relation function in the child can be configured to match the parent item
``swift superview.sl.sublayout { selfview.sl.anchors { Anchors.top.bottom } } ``
this can be expressed by the following expression:
`` selfview.top = superview.top selfview.bottom = superview.bottom ... ``
also, the multiplier can be set as follows.
``swift Anchors.top.multiplier(10) ``
- Width and height become the item itself if you do not set the second item.
``swift superview.sl.sublayout { selfview.sl.anchors { Anchors.width.height.equalTo(constant: 10) // only for selfview } } ``
this represents the following expression.
`` selfview.width = 10 selfview.height = 10 ``
LayoutBuilder + AnchorsBuilder
ah, finally
LayoutBuilder and AnchorsBuilder can now be used together to add subviews, create autolayouts, and apply them to views.
- A
sublayoutmethod is required to add subviews after invoking ananchorsmethod.
``swift @LayoutBuilder func layout() -> some Layout { superview.sl.sublayout { selfview.sl.anchors { Anchors.allSides }.sublayout { subview.sl.anchors { Anchors.allSides } } } } ``
- Is your hierarchy too complex? Just separates it.
``swift @LayoutBuilder func layout() -> some Layout { superview.sl.sublayout { selfview.sl.anchors { Anchors.allSides } } selfview.sl.sublayout { subview.sl.anchors { Anchors.allSides } } } ``
active and finalActive
The Layout types created with LayoutBuilder and AnchorsBuilder only contain information to actually work. For the application of addSubview and constraint, the method below must be called:
- you can call
finalActiveofLayoutfor instantly do all stuff in case of no needs to updates.
finalActivereturn nothing after addSubview and active constraints instantly.
```swift @LayoutBuilder func layout() -> some Layout { superview.sl.sublayout { selfview.sl.anchors { Anchors.top } } }
init() { layout().finalActive() } ```
- you can call
activeofLayoutif needs using some features for updates.
Returns Activation, an object containing information needed for update.
```swift @LayoutBuilder func layout() -> some Layout { superview.sl.sublayout { selfview.sl.anchors { if someCondition { Anchors.bottom } else { Anchors.top } } } }
var activation: Activation
init() { activation = layout().active() }
func someUpdate() { activation = layout().update(fromActivation: activation) } ```
Layoutable
In SwiftLayout, Layoutable plays a role similar to that of View in SwiftUI.
For implementing Layoutable, you needs be write following codes
var activation: Activation?
@LayoutBuilder var layout: some Layout { ... }: @LayoutBuilder may not required.
```swift class SomeView: UIView, Layoutable { var activation: Activation? @LayoutBuilder var layout: some Layout { self.sl.sublayout { ... } }
init(frame: CGRect) { super.init(frame: frame) self.sl.updateLayout() // call active or update of Layout } } ```
LayoutProperty
Builders of SwiftLayout is DSL languages, so you can perform if, switch case, for etc.
However, in order to reflect the state change in the layout of the view, you must directly call the updateLayout method of the sl property provided by Layoutable when necessary.
var showMiddleName: Bool = false {
didSet {
self.sl.updateLayout()
}
}
var layout: some Layout {
self.sl.sublayout {
firstNameLabel
if showMiddleName {
middleNameLabel
}
lastNameLabel
}
}If showMiddleName is false, middleNameLabel is not added to the super view, and if it is already added, it is removed from the super view.
In this case, you can update automatically by using LayoutProperty:
@LayoutProperty var showMiddleName: Bool = false // change value call updateLayout of Layoutable
var layout: some Layout {
self.sl.sublayout {
firstNameLabel
if showMiddleName {
middleNameLabel
}
lastNameLabel
}
}Animations
You can animate constraint changes in Layoutable by calling updateLayout(.forced) inside a UIView.animate block:
final class AnimatedView: UIView, Layoutable {
var activation: Activation?
var isExpanded = false {
didSet {
UIView.animate(withDuration: 0.3) {
self.sl.updateLayout(.forced)
}
}
}
// or use the convenient property wrapper:
// @AnimatableLayoutProperty(duration: 0.3) var isExpanded = false
let contentView = UIView()
var layout: some Layout {
self.sl.sublayout {
contentView.sl.anchors {
Anchors.top.horizontal.equalToSuper()
Anchors.height.equalTo(constant: isExpanded ? 200 : 50)
}
}
}
}Other useful features
onActivate(_:) of UIView
You can use the onActivate function in a layout to decorate and modify the view. The closure passed to the onActivate function is called during the activation process.
contentView.sl.sublayout {
nameLabel.sl.onActivate { label in
label.text = "Hello"
label.textColor = .black
}.anchors {
Anchors.allSides
}
}identifying of UIView and Layout
You can set accessibilityIdentifier and use that instead of the view reference.
contentView.sl.sublayout {
nameLabel.sl.identifying("name").anchors {
Anchors.cap
}
ageLabel.sl.anchors {
Anchors.top.equalTo("name", attribute: .bottom)
Anchors.shoe
}
}- from a debugging point, if you set identifier, the corresponding string is output together in the description of NSLayoutConstraint.
Updating Constraints Dynamically
You can tag constraints with an identifier and update their constant or priority at runtime using ConstraintUpdater.
class MyView: UIView, Layoutable {
var activation: Activation?
var layout: some Layout {
self.sl.sublayout {
headerView.sl.anchors {
Anchors.top.equalToSuper(constant: 20).identifier("headerTop")
Anchors.horizontal.equalToSuper()
Anchors.height.equalTo(constant: 100).identifier("headerHeight")
}
}
}
func expandHeader() {
// Update a single constraint
activation?.anchors("headerHeight").update(constant: 200)
// Update with priority
activation?.anchors("headerTop").update(constant: 0, priority: .required)
}
}You can also filter constraints by attribute when multiple constraints share the same identifier:
// Update only the width constraint among "size" identified constraints
activation?.anchors("size", attribute: .width).update(constant: 300)
// Or use a custom predicate
activation?.anchors("insets", predicate: { $0.constant > 0 }).update(constant: 20)Working with UIVisualEffectView
SwiftLayout automatically handles UIVisualEffectView by adding subviews to its contentView:
@LayoutBuilder var layout: some Layout {
self.sl.sublayout {
blurView.sl.sublayout { // UIVisualEffectView
// These views are automatically added to blurView.contentView
titleLabel.sl.anchors {
Anchors.center.equalToSuper()
}
iconView.sl.anchors {
Anchors.bottom.equalTo(titleLabel, attribute: .top, constant: -10)
Anchors.centerX.equalToSuper()
}
}
}
}Layout guides are also supported inside UIVisualEffectView:
blurView.sl.sublayout {
UILayoutGuide().sl.identifying("contentGuide").sl.anchors {
Anchors.allSides.equalToSuper(constant: 20)
}
label.sl.anchors {
Anchors.center.equalTo("contentGuide")
}
}Working with UILayoutGuide
SwiftLayout provides full support for UILayoutGuide with the same syntax as UIView, making it easy to create flexible layouts without adding extra views to the hierarchy.
@LayoutBuilder var layout: some Layout {
containerView.sl.sublayout {
// Create and configure a layout guide
UILayoutGuide().sl.identifying("centerGuide").sl.anchors {
Anchors.centerX.centerY.equalToSuper()
Anchors.width.height.equalTo(constant: 200)
}
// Position views relative to the layout guide
titleLabel.sl.anchors {
Anchors.centerX.equalTo("centerGuide")
Anchors.bottom.equalTo("centerGuide", attribute: .top, constant: -10)
}
imageView.sl.anchors {
Anchors.center.equalTo("centerGuide")
Anchors.size.equalTo(width: 100, height: 100)
}
descriptionLabel.sl.anchors {
Anchors.centerX.equalTo("centerGuide")
Anchors.top.equalTo("centerGuide", attribute: .bottom, constant: 10)
}
}
}Using in SwiftUI
Implement Layoutable on your view or view controller to easily use it in SwiftUI.
iOS/tvOS/visionOS (UIKit):
class MyUIView: UIView, Layoutable {
var activation: Activation?
var layout: some Layout {
self.sl.sublayout { ... }
}
}
struct ContentView: View {
var body: some View {
MyUIView().sl.swiftUI
}
}macOS (AppKit):
class MyNSView: NSView, Layoutable {
var activation: Activation?
var layout: some Layout {
self.sl.sublayout { ... }
}
}
struct ContentView: View {
var body: some View {
MyNSView().sl.swiftUI
}
}Credits
- oozoofrog(@oozoofrog)
- gmlwhdtjd(@gmlwhdtjd)
- della-padula(@della-padula)
Package Metadata
Repository: ioskrew/swiftlayout
Default branch: main
README: README.md