昨日永不逝去
一种使用UIKit的便捷方式
@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 仅支持通过 SPM (Swift Package Manager) 进行部署。
dependencies: [
.package(url: "https://github.com/ioskrew/SwiftLayout", from: "4.0.0"),
],
addSubview
和 removeFromSuperview
的 DSL 特性NSLayoutConstraint
、NSLayoutAnchor
和激活的 DSL 特性if else
、swift case
、for
。警告
随着 Swift6 的更新,SwiftLayout 中的大多数接口现在都显式标记为在 @MainActor 下运行。
LayoutBuilder
是用于设置 UIView 层级的 DSL 构建器;它允许以简单且可见的方式将子视图添加到父视图。
@LayoutBuilder var layout: some Layout {
view.sl.sublayout {
subview.sl.sublayout {
subsubview
subsub2view
}
}
}
就像下面这样
view.addSubview(subview)
subview.addSubview(subsubview)
subview.addSubview(subsub2view)
AnchorsBuilder
是用于 Anchors
类型的 DSL 构建器,它有助于在视图之间创建自动布局约束。 它主要用于 anchors
,Layout 的一个方法。
Anchors
具有 NSLayoutConstraint 的属性,并且可以创建约束。
NSLayoutConstraint 的摘要
- first: Item1 和 attribute1
- second: item2 和 attribute2
- relation: 关系(=, >=, <=), constant, multiplier
约束的方程式具有以下格式:Item1.attribute1 [= | >= | <= ] multiplier x item2.attribute2 + constant
有关 NSLayoutConstraint 的详细信息,请点击此处。
它首先使用 Anchors 中定义的静态值获取所需的属性。
Anchors.top.bottom
您可以通过关系方法(例如 equalTo)来设置第二个项目(NSLayoutConstraint.secondItem, secondAttribute)。
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.top.equalTo(superview, attribute: .top, constant: 10)
}
}
这与以下约束格式相同
selfview.top = superview.top + 10
Anchors 中没有子项关系函数的属性可以配置为与父项匹配
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.sl.top.bottom
}
}
这可以用以下表达式表示
selfview.top = superview.top
selfview.bottom = superview.bottom
...
此外,可以如下设置multiplier。
Anchors.top.multiplier(10)
如果不设置第二个项目,则宽度和高度将变成项目本身。
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.width.height.equalTo(constant: 10) // only for selfview
}
}
这表示以下表达式。
selfview.width = 10
selfview.height = 10
现在可以一起使用 LayoutBuilder
和 AnchorsBuilder
来添加子视图,创建自动布局并将它们应用于视图。
在调用 anchors
方法后,需要一个 sublayout
方法来添加子视图。
@LayoutBuilder func layout() -> some Layout {
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.allSides
}.sublayout {
subview.sl.anchors {
Anchors.allSides
}
}
}
}
你的层级结构太复杂了吗? 把它分开。
@LayoutBuilder func layout() -> some Layout {
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.allSides
}
}
selfview.sl.sublayout {
subview.sl.anchors {
Anchors.allSides
}
}
}
使用 LayoutBuilder
和 AnchorsBuilder
创建的 Layout
类型仅包含实际工作所需的信息。
对于 addSubview 和约束的应用,必须调用以下方法
如果不需要更新,您可以调用 Layout
的 finalActive
以立即完成所有操作。
finalActive
在立即 addSubview 和激活约束后不返回任何内容。
@LayoutBuilder func layout() -> some Layout {
superview.sl.sublayout {
selfview.sl.anchors {
Anchors.top
}
}
}
init() {
layout().finalActive()
}
如果需要使用某些功能进行更新,您可以调用 Layout
的 active
。
返回 Activation
,一个包含更新所需信息的对象。
@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)
}
在 SwiftLayout 中,Layoutable
的作用类似于 SwiftUI 中的 View
。
为了实现 Layoutable
,你需要编写以下代码
var activation: Activation?
@LayoutBuilder var layout: some Layout { ... }
: @LayoutBuilder 可能不是必需的。
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
}
}
SwiftLayout 的构建器是 DSL 语言,因此您可以执行 if、switch case、for 等操作。
但是,为了反映视图布局中的状态更改,您必须在必要时直接调用 Layoutable
提供的 sl
属性的 updateLayout
方法。
var showMiddleName: Bool = false {
didSet {
self.sl.updateLayout()
}
}
var layout: some Layout {
self.sl.sublayout {
firstNameLabel
if showMiddleName {
middleNameLabel
}
lastNameLabel
}
}
如果 showMiddleName
为 false,则不会将 middleNameLabel
添加到超级视图,如果已添加,则会从超级视图中删除它。
在这种情况下,您可以使用 LayoutProperty
自动更新
@LayoutProperty var showMiddleName: Bool = false // change value call updateLayout of Layoutable
var layout: some Layout {
self.sl.sublayout {
firstNameLabel
if showMiddleName {
middleNameLabel
}
lastNameLabel
}
}
您可以通过在 Layoutable
中更新约束来启动动画,并且该方法与以下方法一样简单
UIView
的动画块中,调用 updateLayout
并将 forceLayout
参数设置为 true。final class PreviewView: UIView, Layoutable {
var capTop = true {
didSet {
// start animation for change constraints
UIView.animate(withDuration: 1.0) {
self.sl.updateLayout(forceLayout: true)
}
}
}
// or just use the convenient propertyWrapper like below
// @AnimatableLayoutProperty(duration: 1.0) var capTop = true
let capButton = UIButton()
let shoeButton = UIButton()
let titleLabel = UILabel()
var topView: UIButton { capTop ? capButton : shoeButton }
var bottomView: UIButton { capTop ? shoeButton : capButton }
var activation: Activation?
var layout: some Layout {
self.sl.sublayout {
topView.sl.anchors {
Anchors.cap
}
bottomView.sl.anchors {
Anchors.top.equalTo(topView.bottomAnchor)
Anchors.height.equalTo(topView)
Anchors.shoe
}
titleLabel.sl.onActivate { label in
label.text = "Top Title"
UIView.transition(with: label, duration: 1.0, options: [.beginFromCurrentState, .transitionCrossDissolve]) {
label.textColor = self.capTop ? .black : .yellow
}
}.anchors {
Anchors.center.equalTo(topView)
}
UILabel().sl.onActivate { label in
label.text = "Bottom Title"
label.textColor = self.capTop ? .yellow : .black
}.identifying("title.bottom").anchors {
Anchors.center.equalTo(bottomView)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
initViews()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
initViews()
}
func initViews() {
capButton.backgroundColor = .yellow
shoeButton.backgroundColor = .black
capButton.addAction(.init(handler: { [weak self] _ in
self?.capTop.toggle()
}), for: .touchUpInside)
shoeButton.addAction(.init(handler: { [weak self] _ in
self?.capTop.toggle()
}), for: .touchUpInside)
self.sl.updateLayout()
}
}
您可以使用布局中的 onActivate 函数来装饰和修改视图。传递给 onActivate 函数的闭包在激活过程中被调用。
contentView.sl.sublayout {
nameLabel.sl.onActivate { label in
label.text = "Hello"
label.textColor = .black
}.anchors {
Anchors.allSides
}
}
您可以设置 accessibilityIdentifier
并使用它代替视图引用。
contentView.sl.sublayout {
nameLabel.sl.identifying("name").sl.anchors {
Anchors.cap
}
ageLabel.sl.anchors {
Anchors.top.equalTo("name", attribute: .bottom)
Anchors.shoe
}
}
在 UIView
或 UIViewController
上实现 Layoutable
,您可以轻松地在 SwiftUI
中使用它。
class ViewUIView: UIView, Layoutable {
var layout: some Layout {
...
}
}
...
struct SomeView: View {
var body: some View {
VStack {
...
ViewUIView().sl.swiftUI
...
}
}
}
struct ViewUIView_Previews: PreviewProvider {
static var previews: some Previews {
ViewUIView().sl.swiftUI
}
}