📐 仅用 10 行代码即可实现声明式 UIKit。
请参阅相关文章:仅用 10 行代码即可实现声明式 UIKit:一个简单的扩展,而不是库 以了解更多信息。
通过在 AnyObject
上的一个简单的扩展,您可以执行以下操作。
class ContentViewController: UIViewController {
...
lazy var titleLabel = UILabel()
.with {
$0.text = viewModel.title
$0.textColor = .label
$0.font = .preferredFont(forTextStyle: .largeTitle)
}
...
}
对任何类型的对象都适用。
lazy var submitButton = UIButton()
.with {
$0.setTitle("Submit", for: .normal)
$0.addTarget(self, action: #selector(didTapSubmitButton), for: .touchUpInside)
}
present(
DetailViewController()
.with {
$0.modalTransitionStyle = .crossDissolve
$0.modalPresentationStyle = .overCurrentContext
},
animated: true
)
present(
UIAlertController(title: title, message: message, preferredStyle: .alert)
.with {
$0.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
},
animated: true
)
let today = DateFormatter()
.with {
$0.dateStyle = .medium
$0.locale = Locale(identifier: "en_US")
}
.string(from: Date())
lazy var displayLink = CADisplayLink(target: self, selector: #selector(update))
.with {
$0.isPaused = true
$0.preferredFramesPerSecond = 120
$0.add(to: RunLoop.main, forMode: .common)
}
甚至包括值类型(在遵循 Withable
协议之后)。
extension PersonNameComponents: Withable { }
let name = PersonNameComponents()
.with {
$0.givenName = "Geri"
$0.familyName = "Borbás"
}
更不用说 3D 内容(ARKit
、RealityKit
、SceneKit
)。
view.scene.addAnchor(
AnchorEntity(plane: .horizontal)
.with {
$0.addChild(
ModelEntity(
mesh: MeshResource.generateBox(size: 0.3),
materials: [
SimpleMaterial(color: .green, isMetallic: true)
]
)
)
}
)
它通过这个 with
方法实现。💎
public extension Withable {
func with(_ closure: (Self) -> Void) -> Self {
closure(self)
return self
}
}
该方法实现了相当经典的模式。您可以将其视为介于非特定/参数化构建器,或具有可定制/可插拔装饰行为的装饰器之间。 有关所有详细信息(泛型、值类型),请参阅 Withable.swift
。
该包包含一些我使用的 UIKit
类的方便的扩展(可能会随着它们的增长而移动到它们自己的包中)。 我故意将它们留在这里,因为它们可以作为示例,说明如何创建针对您代码库的需求量身定制的扩展。
例如,您可以为 UILabel
创建一个方便的 text
装饰器。
extension UILabel {
func with(text: String?) -> Self {
with {
$0.text = text
}
}
}
此外,您可以将您的样式精简为简单的扩展,如下所示。
extension UILabel {
var withTitleStyle: Self {
with {
$0.textColor = .label
$0.font = .preferredFont(forTextStyle: .largeTitle)
}
}
var withPropertyStyle: Self {
with {
$0.textColor = .systemBackground
$0.font = .preferredFont(forTextStyle: .headline)
$0.setContentCompressionResistancePriority(.required, for: .vertical)
}
}
var withPropertyValueStyle: Self {
with {
$0.textColor = .systemGray
$0.font = .preferredFont(forTextStyle: .body)
}
}
var withParagraphStyle: Self {
with {
$0.textColor = .label
$0.numberOfLines = 0
$0.font = .preferredFont(forTextStyle: .footnote)
}
}
}
通过这样的扩展,您可以清理视图控制器。
class ContentViewController: UIViewController {
let viewModel = Planets().earth
private lazy var body = UIStackView().vertical(spacing: 10).views(
UILabel()
.with(text: viewModel.title)
.withTitleStyle,
UIStackView().vertical(spacing: 5).views(
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "size")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.size)
.withPropertyValueStyle,
UIView.spacer
),
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "distance")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.distance)
.withPropertyValueStyle,
UIView.spacer
),
UIStackView().horizontal(spacing: 5).views(
UILabel()
.with(text: "mass")
.withPropertyStyle
.withBox,
UILabel()
.with(text: viewModel.properties.mass)
.withPropertyValueStyle,
UIView.spacer
)
),
UIImageView()
.with(image: UIImage(named: viewModel.imageAssetName)),
UILabel()
.with(text: viewModel.paragraphs.first)
.withParagraphStyle,
UILabel()
.with(text: viewModel.paragraphs.last)
.withParagraphStyle,
UIView.spacer
)
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(body)
view.backgroundColor = .systemBackground
body.pin(
to: view.safeAreaLayoutGuide,
insets: UIEdgeInsets(top: 30, left: 30, bottom: 30, right: 30)
)
}
}
我建议阅读相关文章 仅用 10 行代码即可实现声明式 UIKit:一个简单的扩展,而不是库 以了解更多关于背景和更多示例。
后来,我发现 Apple 有时会使用完全相同的模式来启用对象的内联装饰。 这些装饰器函数甚至使用相同的 with
命名约定。
以下示例位于原生 UIKit
中。 🍦
let arrow = UIImage(named: "Arrow").withTintColor(.blue)
let mail = UIImage(systemName: "envelope").withRenderingMode(.alwaysTemplate)
let color = UIColor.label.withAlphaComponent(0.5)
UIImage.withTintColor(_:)
UIImage.withAlphaComponent(_:)
UIImage.Configuration.withTraitCollection(_:)
UIImage.Configuration
此外,该包包含一个 NSObject
扩展,可帮助在扩展中创建存储属性。 我最终将其包含在内,因为我发现使用存储属性扩展 UIKit
类是一个非常常见的用例。 有关更多信息,请参见 NSObject+Extensions.swift
和 UIButton+Extensions.swift
。
您可以执行以下操作。
extension UITextField {
var nextTextField: UITextField? {
get {
associatedObject(for: "nextTextField") as? UITextField
}
set {
set(associatedObject: newValue, for: "nextTextField")
}
}
}
另一个秘密武器是 UIView.onMoveToSuperview
扩展,它只是一个在 view
添加到 superview
时调用的闭包(一次)。 这样,您可以使用此闭包在初始化时提前声明约束,然后在运行时在视图具有 superview 时添加/激活它们。 有关用法示例,请参见 Keyboard Avoidance 存储库。
在 MIT 许可证 下获得许可。