1. Introduction
过去的两集有点抽象。 我们一直在日常代码中以一种看似无关的方式探索合成。让我们看看如何将合成应用到更具体的东西上:UIKit。
import UIKit
final class SignInViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let gradientView = GradientView()
gradientView.fromColor = UIColor(red: 0.5, green: 0.85, blue: 1, alpha: 0.85)
gradientView.toColor = .white
gradientView.translatesAutoresizingMaskIntoConstraints = false
let logoImageView = UIImageView(image: UIImage(named: "logo"))
logoImageView.widthAnchor.constraint(equalTo: logoImageView.heightAnchor, multiplier: logoImageView.frame.width / logoImageView.frame.height).isActive = true
let gitHubButton = UIButton(type: .system)
gitHubButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
gitHubButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
gitHubButton.clipsToBounds = true
gitHubButton.layer.cornerRadius = 6
gitHubButton.backgroundColor = .black
gitHubButton.tintColor = .white
gitHubButton.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16)
gitHubButton.setImage(UIImage(named: "github"), for: .normal)
gitHubButton.setTitle("Sign in with GitHub", for: .normal)
let orLabel = UILabel()
orLabel.font = .systemFont(ofSize: 14, weight: .medium)
orLabel.textAlignment = .center
orLabel.textColor = UIColor(white: 0.625, alpha: 1)
orLabel.text = "or"
let emailField = UITextField()
emailField.clipsToBounds = true
emailField.layer.cornerRadius = 6
emailField.layer.borderColor = UIColor(white: 0.75, alpha: 1).cgColor
emailField.layer.borderWidth = 1
emailField.borderStyle = .roundedRect
emailField.heightAnchor.constraint(equalToConstant: 44).isActive = true
emailField.keyboardType = .emailAddress
emailField.placeholder = "blob@pointfree.co"
let passwordField = UITextField()
passwordField.clipsToBounds = true
passwordField.layer.cornerRadius = 6
passwordField.layer.borderColor = UIColor(white: 0.75, alpha: 1).cgColor
passwordField.layer.borderWidth = 1
passwordField.borderStyle = .roundedRect
passwordField.heightAnchor.constraint(equalToConstant: 44).isActive = true
passwordField.isSecureTextEntry = true
passwordField.placeholder = "••••••••••••••••"
let signInButton = UIButton(type: .system)
signInButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
signInButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
signInButton.clipsToBounds = true
signInButton.layer.cornerRadius = 6
signInButton.layer.borderColor = UIColor.black.cgColor
signInButton.layer.borderWidth = 2
signInButton.setTitleColor(.black, for: .normal)
signInButton.setTitle("Sign in", for: .normal)
let forgotPasswordButton = UIButton(type: .system)
forgotPasswordButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
forgotPasswordButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
forgotPasswordButton.setTitleColor(.black, for: .normal)
forgotPasswordButton.setTitle("I forgot my password", for: .normal)
let legalLabel = UILabel()
legalLabel.font = .systemFont(ofSize: 11, weight: .light)
legalLabel.numberOfLines = 0
legalLabel.textAlignment = .center
legalLabel.textColor = UIColor(white: 0.5, alpha: 1)
legalLabel.text = "By signing into Point-Free you agree to our latest terms of use and privacy policy."
let rootStackView = UIStackView(arrangedSubviews: [
logoImageView,
gitHubButton,
orLabel,
emailField,
passwordField,
signInButton,
forgotPasswordButton,
legalLabel,
])
rootStackView.axis = .vertical
rootStackView.isLayoutMarginsRelativeArrangement = true
rootStackView.layoutMargins = UIEdgeInsets(top: 32, left: 16, bottom: 32, right: 16)
rootStackView.spacing = 16
rootStackView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(gradientView)
self.view.addSubview(rootStackView)
NSLayoutConstraint.activate([
gradientView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
gradientView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
gradientView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
gradientView.bottomAnchor.constraint(equalTo: self.view.centerYAnchor),
rootStackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
rootStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
rootStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
}
}
我们在构建UI时遇到的一个常见问题是可重用样式。这段代码还没有尝试让样式可重用。它只是在实例化后内联地配置每个视图。 很多组件都有重叠的样式,所以让我们尝试找到一种更好的方式来样式化组件,而不会有太多重复。
2. UIAppearance
首先,让我们探讨一下UIAppearance和Apple提供的帮助解决可重用样式的API。UIAppearance是一个带有一些静态方法的协议,主要是一个叫做appearance的方法。这些方法返回一个可以像配置常规视图一样配置的代理视图。一旦配置了这个代理,添加到视图层次结构中的任何同类视图都将以同样的方式进行配置。
我们所有的按钮都有相同的内容边框insets和字体,所以让我们尝试移动这个配置来使用UIAppearance。
UIButton.appearance().contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
UIButton.appearance().titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
如果我们从按钮配置中删除相应的行,事情看起来基本上是一样的。
let gitHubButton = UIButton(type: .system)
// gitHubButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
// gitHubButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
// …
let loginButton = UIButton(type: .system)
// loginButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
// loginButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
// …
但是我们注意到一个区别:字体小了一点。 这是怎么了? 我们使用.system按钮类型,它为按钮标题提供了一些很好的样式,这可能会碍事。 不过,仔细检查一下,我们通过代理对象深入了两层,试图通过titlelabel标签更新字体。UIAppearance的改变只对直接视图的属性起作用。 一个解决方案是扩展我们的视图并将此配置作为一个新属性公开,但这需要一点工作。
UIAppearance的另一个问题是它在类级别上工作。 我们已经达到了UIButton可重用配置的极限,所以为了构建更多种类的可重用按钮,我们的主要路径是子类化。
3. Subclassing
子类化是在整个应用程序中管理可重用样式的流行方法。让我们首先定义一个基按钮类,并覆盖它的初始化式来样式化它。
class BaseButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
self.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
编译器还强迫我们定义那个额外的初始化式。
现在我们可以将按钮实例化为BaseButton
let gitHubButton = BaseButton(type: .system)
// …
let loginButton = BaseButton(type: .system)
// …
但是字体看起来又不对了。按钮子类和.system类型不兼容,所以我们现在要放弃从那个按钮类型得到的东西。
let gitHubButton = BaseButton()
// …
let loginButton = BaseButton()
// …
现在字体已经正常呈现了。
我们现在直接使用这个BaseButton类,但每当我们在类名前加上Base前缀时,我们通常是在处理一些抽象的功能,这些功能会进一步子类化以供实际使用。
我们在屏幕上看到的更直接的按钮样式是什么? 我们有一个“填充”按钮样式,一个“边框”按钮样式,和一个“文本”按钮样式。让我们从定义一个fillledbutton子类开始。
class FilledButton: BaseButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.backgroundColor = .black
self.tintColor = .white
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
同样,我们需要提供额外的*init(coder:)*初始化式来满足编译器的要求,但是现在我们有了一个可以使用的新类。
let gitHubButton = FilledButton()
// gitHubButton.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
// gitHubButton.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
gitHubButton.clipsToBounds = true
gitHubButton.layer.cornerRadius = 6
// gitHubButton.backgroundColor = .black
// gitHubButton.tintColor = .white
这些样式通过重用被组合在一起,看起来我们在这里跳过了。 我们有几行环绕按钮边缘的线,与登录按钮共享。听起来我们需要另一个子类。
class RoundedButton: BaseButton {
override init(frame: CGRect) {
super.init(frame: frame)
self.clipsToBounds = true
self.layer.cornerRadius = 6
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
现在我们不得不考虑操作的顺序。Fillledbutton和RoundedButton都继承自BaseButton。现在,Fillledbutton应该继承RoundedButton以确保填充的按钮是圆角的。
class FilledButton: RoundedButton {
// …
}
我们可以继续沿着这些行定义BorderButton和TextButton子类,并找出它们在层次结构中的位置。在这个小屏幕上很容易找到它们的位置,但随着时间的推移,我们会发现我们有需要共享相同逻辑的分离的子类。
这就是菱形继承问题:给定一个有两个子类的基类,我们可能想要另一个继承这两个子类的子类。 Swift不支持菱形继承,所以让我们探索一些其他共享逻辑的替代方法。
4. Object Composition
在我们的行业里有句古老的格言:“比起继承,更喜欢组合。(prefer composition over inheritance)”在这个意义上,“合成”是什么意思?
我们不使用子类,而是定义一个静态属性来创建具有基样式的按钮。
extension UIButton {
static var base: UIButton {
let button = UIButton()
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
return button
}
}
我们可以为填充按钮样式创建另一个helper。
extension UIButton {
static var filled: UIButton {
let button = self.base
button.backgroundColor = .black
button.tintColor = .white
return button
}
}
这就是“composition”的作用所在。我们在进一步应用filled 样式之前,可以调用self.base使用我们的基本样式派生一个按钮。
然后我们可以添加一个圆形样式。
extension UIButton {
static var rounded: UIButton {
let button = self.filled
button.clipsToBounds = true
button.layer.cornerRadius = 6
return button
}
}
现在我们的GitHub按钮可以使用这些静态属性之一而不是子类。
let gitHubButton = UIButton.filled
哎呀! 我们的按钮是填充的,但不是四舍五入的。发生了什么事? 看来我们的行动顺序错了。看起来这个版本有与继承相同的问题,尽管它仍然更好一些。直接在UIButton上定义样式绝对更简洁,我们不需要定义那些嘈杂的*init(coder:)*初始化器。让我们看看是否能找到一种不那么混乱的方式来构建我们的样式。
5. Functions
让我们回到我们熟悉的老朋友,函数。我们可以定义一个baseButtonStyle函数,用我们的基样式给给定的按钮设置样式。
func baseButtonStyle(_ button: UIButton) {
button.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
button.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
又好又简洁。
What about a filledButtonStyle?
func filledButtonStyle(_ button: UIButton) {
button.backgroundColor = .black
button.tintColor = .white
}
现在我们的填充样式不需要继承或调用基样式。
How about a roundedButtonStyle?
func roundedButtonStyle(_ button: UIButton) {
button.clipsToBounds = true
button.layer.cornerRadius = 6
}
我们有! 三个样式函数不需要担心它们之间的继承或调用层次结构。他们只做一件事,而且做得很好。让我们使用它们。
let gitHubButton = UIButton(type: .system)
baseButtonStyle(gitHubButton)
roundedButtonStyle(gitHubButton)
filledButtonStyle(gitHubButton)
这看起来有点乱。在这个配置中有很多活动部件,似乎很容易错过一个步骤。我们还直接应用了baseButtonStyle,这看起来像是我们想让其他样式使用的东西。如果能够将这些风格组合成不那么随意的单位就好了。
6. Function Composition
在我们的“Side Effects”一集中,我们介绍了<>用于单一类型的合成。我们定义了两次:一次用于接受As作为输入并返回As作为输出的函数,一次用于从inout A到Void的值突变函数。
func <> <A>(f: @escaping (A) -> A, g: @escaping (A) -> A) -> (A) -> A {
return f >>> g
}
func <> <A>(f: @escaping (inout A) -> Void, g: @escaping (inout A) -> Void) -> (inout A) -> Void {
return { a in
f(&a)
g(&a)
}
}
我们的样式函数生活在引用类型突变的世界里,所以让我们看看是否可以重用这个*(inout A) -> Void*形状。
func <> <A: AnyObject>(f: @escaping (A) -> Void, g: @escaping (A) -> Void) -> (A) -> Void {
return { a in
f(a)
g(a)
}
}
我们将泛型A限制为AnyObject,这是所有引用类型都遵循的协议。在这个过程中,我们可以删除inout注释。
现在我们可以从我们的baseButtonStyle组合我们的fillledbuttonstyle。
let filledButtonStyle =
baseButtonStyle
<> {
$0.backgroundColor = .black
$0.tintColor = .white
}
让我们对roundedButtonStyle做同样的事情
let roundedButtonStyle =
baseButtonStyle
<> {
$0.clipsToBounds = true
$0.layer.cornerRadius = 6
}
我们的GitHub按钮不再需要配置baseButtonStyle。 让我们最终决定样式的层次结构,让fillledbuttonstyle由roundedButtonStyle组成。
let filledButtonStyle =
roundedButtonStyle
<> {
$0.backgroundColor = .black
$0.tintColor = .white
}
这种构图感觉起来灵活多了。我们可以快速混合、匹配和提取样式,每个函数本身只负责它的样式,不需要调用其他样式函数: 我们通过函数组合来组合样式。
我们现在还可以探索可重用的样式,这在类和静态属性世界中是不可能的! 我们的roundedButtonStyle是一个函数,它可以修改存在于所有UIView上的属性,而不仅仅是按钮。事实上,我们的UITextFields也是以同样的方式配置的。
我们现在可以编写一个在所有UIView上工作的rounddedstyle。
let roundedStyle: (UIView) -> Void = {
$0.clipsToBounds = true
$0.layer.cornerRadius = 6
}
而我们的roundedButtonStyle只需要用这个更可重用的功能来组合。
let roundedButtonStyle =
baseButtonStyle
<> roundedStyle
我们可以通过创建baseTextFieldStyle来重用这个roundedStyle。
let baseTextFieldStyle: (UITextField) -> Void =
roundedStyle
<> {
$0.layer.borderColor = UIColor(white: 0.75, alpha: 1).cgColor
$0.layer.borderWidth = 1
$0.borderStyle = .roundedRect
$0.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
我们可以把这个样式应用到两个文本框中,然后去掉很多代码。
let emailTextField = UITextField()
baseTextFieldStyle(emailTextField)
// …
let passwordTextField = UITextField()
baseTextFieldStyle(passwordTextField)
// …
更多的重用是通过观察来实现的:我们的baseTextFieldStyle以与边框按钮相同的方式配置边框。 我们应该能够编写一个双方都可以使用的borderStyle。
如果我们以非常基本的方式编写样式函数,就会遇到一个问题:
let borderStyle: (UIView) -> Void = {
$0.layer.borderColor = // ???
$0.layer.borderWidth = // ???
}
这里的配置在每种情况下都是不同的:我们的边框按钮有一个粗的黑色边框,而文本框有一个细的灰色边框。 让我们把这个配置带入函数。
func borderStyle(color: UIColor, width: CGFloat) -> (UIView) -> Void {
return { view in
view.layer.borderColor = color.cgColor
view.layer.borderWidth = width
}
}
为了进行组合,我们需要我们的函数先进行配置,然后返回一个全新的函数来样式化视图。
让我们试着用baseTextFieldStyle:
let baseTextFieldStyle: (UITextField) -> Void =
roundedStyle
<> borderStyle(color: UIColor(white: 0.75, alpha: 1), width: 1)
<> {
$0.borderStyle = .roundedRect
$0.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
我们有个小问题。
Value of type 'UIView' has no member 'borderStyle'
我们组合了一堆小函数,这些小函数通常会将UIView样式转换成一个函数,该函数特别需要UITextFields,类型系统已经达到了子类型、超类型和多行闭包之间的推断限制。这种错误应该很少见,但很容易修复。我们需要显式地使用闭包的类型签名。
let baseTextFieldStyle: (UITextField) -> Void =
roundedStyle
<> borderStyle(color: UIColor(white: 0.75, alpha: 1), width: 1)
<> { (tf: UITextField) in
tf.borderStyle = .roundedRect
tf.heightAnchor.constraint(equalToConstant: 44).isActive = true
}
这很有效。
现在我们有了一个borderStyle,可以很容易地创建一个borderButtonStyle!
let borderButtonStyle =
roundedStyle
<> borderStyle(color: .black, width: 2)
这是在任何UIView上流畅地使用样式函数的另一个例子。我们已经能够用diamond <>来解决我们的diamond继承问题。
如果我们进一步为所有东西编写样式函数,它会是什么样子?
// base
func autolayoutStyle<V: UIView>(_ view: V) -> Void {
view.translatesAutoresizingMaskIntoConstraints = false
}
func aspectRatioStyle<V: UIView>(size: CGSize) -> (V) -> Void {
return {
$0.widthAnchor
.constraint(equalTo: $0.heightAnchor, multiplier: size.width / size.height)
.isActive = true
}
}
func implicitAspectRatioStyle<V: UIView>(_ view: V) -> Void {
aspectRatioStyle(size: view.frame.size)(view)
}
func roundedRectStyle<View: UIView>(_ view: View) {
view.clipsToBounds = true
view.layer.cornerRadius = 6
}
// buttons
let baseButtonStyle: (UIButton) -> Void = {
$0.contentEdgeInsets = UIEdgeInsets(top: 12, left: 16, bottom: 12, right: 16)
$0.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium)
}
let roundedButtonStyle =
baseButtonStyle
<> roundedRectStyle
let filledButtonStyle =
roundedButtonStyle
<> {
$0.backgroundColor = .black
$0.tintColor = .white
}
let borderButtonStyle =
roundedButtonStyle
<> {
$0.layer.borderColor = UIColor.black.cgColor
$0.layer.borderWidth = 2
$0.setTitleColor(.black, for: .normal)
}
let textButtonStyle =
baseButtonStyle <> {
$0.setTitleColor(.black, for: .normal)
}
let imageButtonStyle: (UIImage?) -> (UIButton) -> Void = { image in
return {
$0.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 16)
$0.setImage(image, for: .normal)
}
}
let gitHubButtonStyle =
filledButtonStyle
<> imageButtonStyle(UIImage(named: "github"))
// text fields
let baseTextFieldStyle: (UITextField) -> Void =
roundedRectStyle
<> {
$0.borderStyle = .roundedRect
$0.heightAnchor.constraint(equalToConstant: 44).isActive = true
$0.layer.borderColor = UIColor(white: 0.75, alpha: 1).cgColor
$0.layer.borderWidth = 1
}
let emailTextFieldStyle =
baseTextFieldStyle
<> {
$0.keyboardType = .emailAddress
$0.placeholder = "blob@pointfree.co"
}
let passwordTextFieldStyle =
baseTextFieldStyle
<> {
$0.isSecureTextEntry = true
$0.placeholder = "••••••••••••••••"
}
// labels
func fontStyle(ofSize size: CGFloat, weight: UIFont.Weight) -> (UILabel) -> Void {
return {
$0.font = .systemFont(ofSize: size, weight: weight)
}
}
func textColorStyle(_ color: UIColor) -> (UILabel) -> Void {
return {
$0.textColor = color
}
}
let centerStyle: (UILabel) -> Void = {
$0.textAlignment = .center
}
// hyper-local
let orLabelStyle: (UILabel) -> Void =
centerStyle
<> fontStyle(ofSize: 14, weight: .medium)
<> textColorStyle(UIColor(white: 0.625, alpha: 1))
let finePrintStyle: (UILabel) -> Void =
centerStyle
<> fontStyle(ofSize: 14, weight: .medium)
<> textColorStyle(UIColor(white: 0.5, alpha: 1))
<> {
$0.font = .systemFont(ofSize: 11, weight: .light)
$0.numberOfLines = 0
}
let gradientStyle: (GradientView) -> Void =
autolayoutStyle <> {
$0.fromColor = UIColor(red: 0.5, green: 0.85, blue: 1, alpha: 0.85)
$0.toColor = .white
}
// stack views
let rootStackViewStyle: (UIStackView) -> Void =
autolayoutStyle
<> {
$0.axis = .vertical
$0.isLayoutMarginsRelativeArrangement = true
$0.layoutMargins = UIEdgeInsets(top: 32, left: 16, bottom: 32, right: 16)
$0.spacing = 16
}
final class SignInViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let gradientView = GradientView()
gradientStyle(gradientView)
let logoImageView = UIImageView(image: UIImage(named: "logo"))
implicitAspectRatioStyle(logoImageView)
baseButtonStyle(.appearance())
let gitHubButton = UIButton(type: .system)
gitHubButton.setTitle("Sign in with GitHub", for: .normal)
gitHubButtonStyle(gitHubButton)
let orLabel = UILabel()
orLabelStyle(orLabel)
orLabel.text = "or"
let emailField = UITextField()
emailTextFieldStyle(emailField)
let passwordField = UITextField()
passwordTextFieldStyle(passwordField)
let signInButton = UIButton(type: .system)
signInButton.setTitle("Sign in", for: .normal)
borderButtonStyle(signInButton)
let forgotPasswordButton = UIButton(type: .system)
forgotPasswordButton.setTitle("I forgot my password", for: .normal)
textButtonStyle(forgotPasswordButton)
let legalLabel = UILabel()
legalLabel.text = "By signing into Point-Free you agree to our latest terms of use and privacy policy."
finePrintStyle(legalLabel)
let rootStackView = UIStackView(arrangedSubviews: [
logoImageView,
gitHubButton,
orLabel,
emailField,
passwordField,
signInButton,
forgotPasswordButton,
legalLabel,
])
rootStackViewStyle(rootStackView)
self.view.addSubview(gradientView)
self.view.addSubview(rootStackView)
NSLayoutConstraint.activate([
gradientView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
gradientView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
gradientView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
gradientView.bottomAnchor.constraint(equalTo: self.view.centerYAnchor),
rootStackView.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor),
rootStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
rootStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
])
}
}
我们能够衍生出许多有趣的、可重用的风格! 我们有一个autolayoutStyle,它可能比translatesAutoresizingMaskIntoConstraints = false更容易阅读和记忆。我们有一个aspectRatioStyle维护图像视图的纵横比与自动布局。
我们有一个imageButtonStyle,它可以根据我们的需要用borderButtonStyle或fillledbuttonstyle来组合。
我们也有一个rootStackViewStyle,允许我们保持一致的边距和间距在我们的应用程序。
所有这些样式函数都存在于控制器之外,可以在任何地方自由使用和重用。
我们的视图控制器呢?嗯,它变得非常简洁! 每个视图都被实例化,然后在一行中设置样式。
7. What’s the point?
看一个真实世界的例子会让我们更容易地问自己:这有什么意义? 我们已经在日常用例中利用了<>,并且能够以一种真正强大而灵活的方式解决UIKit样式化的问题。我们的函数没有与UIKit战斗。事实上,他们玩得还不错!]
我们甚至可以使用带有样式功能的UIAppearance !
baseButtonStyle(UIButton.appearance())
或者,如果你感觉不错:
baseButtonStyle(.appearance())
这就是简单函数的美妙之处。它们是灵活的,让我们可以随意使用它们。我们并不是在用一层抽象来阻止我们看到正在发生的事情。
这是我们一直在用的东西。您可以简单地将它引入到代码库中,而无需依赖。