SwiftLayout Logo

昨日永不逝去

一种使用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"),
],

特性

用法

警告

随着 Swift6 的更新,SwiftLayout 中的大多数接口现在都显式标记为在 @MainActor 下运行。

LayoutBuilder

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

AnchorsBuilder 是用于 Anchors 类型的 DSL 构建器,它有助于在视图之间创建自动布局约束。 它主要用于 anchors,Layout 的一个方法。

Anchors

Anchors 具有 NSLayoutConstraint 的属性,并且可以创建约束。

NSLayoutConstraint 的摘要

约束的方程式具有以下格式:Item1.attribute1 [= | >= | <= ] multiplier x item2.attribute2 + constant

有关 NSLayoutConstraint 的详细信息,请点击此处

LayoutBuilder + AnchorsBuilder

啊,终于

现在可以一起使用 LayoutBuilderAnchorsBuilder 来添加子视图,创建自动布局并将它们应用于视图。

active 和 finalActive

使用 LayoutBuilderAnchorsBuilder 创建的 Layout 类型仅包含实际工作所需的信息。
对于 addSubview 和约束的应用,必须调用以下方法

Layoutable

SwiftLayout 中,Layoutable 的作用类似于 SwiftUI 中的 View

为了实现 Layoutable,你需要编写以下代码

LayoutProperty

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 中更新约束来启动动画,并且该方法与以下方法一样简单

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()
  }
}
animation.mp4

其他有用的特性

UIView 的 onActivate(_:)

您可以使用布局中的 onActivate 函数来装饰和修改视图。传递给 onActivate 函数的闭包在激活过程中被调用。

contentView.sl.sublayout {
  nameLabel.sl.onActivate { label in 
    label.text = "Hello"
    label.textColor = .black
  }.anchors {
    Anchors.allSides
  }
}

UIViewLayoutidentifying

您可以设置 accessibilityIdentifier 并使用它代替视图引用。

contentView.sl.sublayout {
  nameLabel.sl.identifying("name").sl.anchors {
    Anchors.cap
  }
  ageLabel.sl.anchors {
    Anchors.top.equalTo("name", attribute: .bottom)
    Anchors.shoe
  }
}

SwiftUI 中使用

UIViewUIViewController 上实现 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
  }
}

鸣谢