在使用交互式SwiftUI视图时,我们经常使用闭包来定义我们希望在各种事件发生时执行的动作。例如,下面的AddItemView 有两个交互式元素,一个TextField 和一个Button ,它们都能让用户在我们的应用程序中添加一个新的基于文本的Item:
struct AddItemView: View {
var handler: (Item) -> Void
@State private var title = ""
var body: some View {
HStack {
TextField("Add item",
text: $title,
onCommit: {
guard !title.isEmpty else {
return
}
let item = Item(title: title)
handler(item)
title = ""
}
)
Button("Add") {
let item = Item(title: title)
handler(item)
title = ""
}
.disabled(title.isEmpty)
}
}
}
除了在我们的文本字段的onCommit 动作中领先的guard 语句(在我们的按钮动作中不需要,因为我们在文本为空时禁用按钮),我们的两个闭包是完全相同的,所以通过将这些动作从我们的视图的body 中移开,摆脱代码重复的来源将是非常好的。
要做到这一点的一个方法是使用一个计算属性来创建我们的闭包。这可以让我们一次性定义我们的逻辑,如果我们还包括我们的TextField 所需要的guard 语句,那么我们就可以为我们的两个UI控件使用完全相同的闭包实现:
private extension AddItemView {
var addAction: () -> Void {
return {
guard !title.isEmpty else {
return
}
let item = Item(title: title)
handler(item)
title = ""
}
}
}
有了上面的方法,我们现在可以简单地将我们新的addAction 属性传递给我们的两个子视图,我们已经成功地摆脱了代码的重复,而且我们的视图的body 实现现在也更加紧凑。
struct AddItemView: View {
var handler: (Item) -> Void
@State private var title = ""
var body: some View {
HStack {
TextField("Add item",
text: $title,
onCommit: addAction
)
Button("Add", action: addAction)
.disabled(title.isEmpty)
}
}
}
虽然以上是一个完美的解决方案,但在SwiftUI的上下文中,还有一个最初可能并不明显的选择,那就是使用与使用UIKit的目标/动作模式相同的技术--将我们的动作处理程序定义为一个方法,而不是一个闭包。
要做到这一点,首先让我们把之前的addAction 属性重构为一个addItem 方法,看起来像这样:
private extension AddItemView {
func addItem() {
guard !title.isEmpty else {
return
}
let item = Item(title: title)
handler(item)
title = ""
}
}
然后,就像我们之前将我们的addAction 属性传递给我们的TextView 和Button 一样,我们现在可以对我们的addItem 方法做同样的事情--这给了我们下面的实现:
struct AddItemView: View {
var handler: (Item) -> Void
@State private var title = ""
var body: some View {
HStack {
TextField("Add item",
text: $title,
onCommit: addItem
)
Button("Add", action: addItem)
.disabled(title.isEmpty)
}
}
}
在使用SwiftUI时,我们很容易陷入这样的误区,即认为某个视图的布局、子视图和动作都需要在其body ,如果我们仔细想想,这正是使用UIKit时经常导致大量视图控制器的那种方法。
然而,由于SwiftUI的高度可组合设计,通常很容易将一个视图的body 分割成独立的部分,这甚至可能不需要创建任何新的View 类型。有时,我们所要做的就是把我们的一些逻辑提取到一个单独的方法中,最后我们会得到更优雅的代码,更容易阅读和维护。