I have a SwiftUI view that takes another view as a parameter and includes it as a “subview.” My question is about whether the subview should be passed as a closure or as a View
.
Approach 1
This button class takes a parameter to use as the button label:
struct CheckmarkButton<TextType: View>: View {
let title: TextType
init(titleBuilder: @escaping () -> TextType) {
self.title = titleBuilder()
}
var body: some View {
Button {
// Action goes here
} label: {
VStack {
Image(systemName: "checkmark.circle.fill")
title
}
}
}
}
#Preview {
CheckmarkButton {
Text("This is the button label")
}
}
The closure that builds the label is called in the button’s initializer, and the result of that closure is stored. This seems to work fine.
Approach 2
You can seemingly get the same behavior by storing the closure itself and calling it at the appropriate place in the view:
struct CheckmarkButton<TextType: View>: View {
let titleBuilder: () -> TextType
init(titleBuilder: @escaping () -> TextType) {
self.titleBuilder = titleBuilder
}
var body: some View {
Button {
// Action goes here
} label: {
VStack {
Image(systemName: "checkmark.circle.fill")
titleBuilder()
}
}
}
}
The call site looks the same as in approach 1.
Approach 3
You can also forego the closures entirely and pass a View
directly to the button’s initializer:
struct CheckmarkButton<TextType: View>: View {
let title: TextType
init(title: TextType) {
self.title = title
}
var body: some View {
Button {
// If this were a real button there would be an action here
} label: {
VStack {
Image(systemName: "checkmark.circle.fill")
title
}
}
}
}
In this case, the call site looks like
CheckmarkButton(title: Text("This is the button label"))
My question
Apart from the slightly less idiomatic-looking call site in approach 3, is there any reason to prefer one of these approaches over the other? Do any of them run the risk of a closure being evaluated more times than necessary (wasting CPU cycles) or fewer times than necessary (allowing part of the view to become stale)?