Yesterday never dies
A swifty way to use UIKit & AppKit
@LayoutBuilder var layout: some Layout { self.sl.sublayout { leftParenthesis.sl.anchors { Anchors.leading.equalToSuper(constant: 16) Anchors.centerY } viewLogo.sl.anchors { Anchors.leading.equalTo(leftParenthesis, attribute: .trailing, constant: 20) Anchors.centerY.equalToSuper(constant: 30) Anchors.size.equalTo(width: 200, height: 200) } UIImageView().sl.identifying("plus").sl.onActivate { imageView in imageView.image = UIImage(systemName: "plus") imageView.tintColor = .SLColor }.anchors { Anchors.center.equalToSuper(yOffset: 30) Anchors.size.equalTo(width: 150, height: 150) } constraintLogo.sl.anchors { Anchors.trailing.equalTo(rightParenthesis.leadingAnchor) Anchors.centerY.equalTo("plus") Anchors.size.equalTo(width: 200, height: 150) } rightParenthesis.sl.anchors { Anchors.trailing.equalToSuper(constant: -16) Anchors.centerY } } }SwiftLayout 5.x supports multiple Apple platforms:
| Platform | Minimum Version |
|---|---|
| iOS | 15.0+ |
| macOS | 12.0+ |
| tvOS | 15.0+ |
| visionOS | 1.0+ |
| Swift version | SwiftLayout version |
|---|---|
| Swift 6.0+ | 5.x (current) |
| Swift 5.7 | 2.8.0 |
| Swift 5.5 | 2.7.0 |
Note
- For projects targeting iOS 13–14 or Swift 5.x, use the corresponding 2.x releases linked above.
SwiftLayout only supports deployments via SPM(Swift Package Manager).
dependencies: [ .package(url: "https://github.com/ioskrew/SwiftLayout", from: "5.0.0"), ],- Multi-platform support: Works with UIKit (iOS, tvOS, visionOS) and AppKit (macOS)
- DSL features for
addSubviewandremoveFromSuperview - DSL features for
NSLayoutConstraint,NSLayoutAnchorand activation - Selective updates only when view states change
- Conditional and loop statements like
if else,switch case,forin view hierarchy and autolayout constraints - PropertyWrapper for automatically updating layout
- Various APIs for constraint relationships
Warning
With the update to Swift6, most of the interfaces in SwiftLayout are now explicitly marked to operate under the @MainActor.
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 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 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.
Anchors.top.bottom
-
You can set up a second item (NSLayoutConstraint.secondItem, secondAttribute) through a relationship method such as equalTo.
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
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.
Anchors.top.multiplier(10)
-
Width and height become the item itself if you do not set the second item.
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 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.@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.
@LayoutBuilder func layout() -> some Layout { superview.sl.sublayout { selfview.sl.anchors { Anchors.allSides } } selfview.sl.sublayout { subview.sl.anchors { Anchors.allSides } } }
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.@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.
ReturnsActivation, an object containing information needed for update.@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) }
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.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 } }
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 } }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) } } } }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 } }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.
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)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") } }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) } } }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 } }- oozoofrog(@oozoofrog)
- gmlwhdtjd(@gmlwhdtjd)
- della-padula(@della-padula)
