I'm working on the SwiftUI, and feeling it's very similar with React. Just now I'm customizing a Button of SwiftUI and have a problem which can't access to the children views of Button dynamically
Following codes is what I'm going to do:
struct FullButton : View {
var action: () -> Void
var body: some View {
Button(action: action) {
// render children views here even what is that
children
}
}
}
and usage:
VStack {
FullButton(action: {
print('touched')
}) {
Text("Button")
}
}
Please, do I have a wrong idea?
Update
Depends on @graycampbell 's answer I tried as following
struct FullButton<Label> where Label : View {
var action: () -> Void
var label: () -> Label
init(action: @escaping () -> Void, @ViewBuilder label: @escaping () -> Label) {
self.action = action
self.label = label
}
var body: some View {
Button(action: action, label: label)
}
}
So the FullButton
looks well as itself. But I have another compile error in usage at this time.
VStack {
FullButton(action: { print("touched") }) {
Text("Fullbutton")
}
}
The error is Referencing initializer 'init(alignment:spacing:content:)' on 'VStack' requires that 'FullButton<Text>' conform to 'View'
.
It means FullButton
hasn't return the body
now?
I'm not sure why it is because the FullButton
still extends View
class.
Please let me know what's the correct body
definition of that type of class.
This is what you're looking for, if I'm understanding your question correctly:
struct FullButton<Label>: View where Label: View {
var action: () -> Void
var label: () -> Label
var body: some View {
Button(action: self.action, label: self.label)
}
}
This would allow you to pass whatever content you want to be displayed on your button, meaning that the code you have here would now work:
FullButton(action: {
print("touched")
}) {
Text("Button")
}
Update
After looking over your question several times, I've realized that your confusion is stemming from a misunderstanding of what is happening when you create a normal Button
.
In the code below, I'm creating a Button
. The button takes two arguments - action
and label
.
Button(action: {}, label: {
Text("Button")
})
If we look at the documentation for Button
, we see that it is declared like this:
struct Button<Label> where Label : View
If we then look at the initializers, we see this:
init(action: @escaping () -> Void, @ViewBuilder label: () -> Label)
Both action
and label
expect closures. action
expects a closure with a return type of Void
, and label
expects a @ViewBuilder
closure with a return type of Label
. As defined in the declaration for Button
, Label
is a generic representing a View
, so really, label
is expecting a closure that returns a View
.
This is not unique to Button
. Take HStack
, for example:
struct HStack<Content> where Content : View
init(alignment: VerticalAlignment = .center, spacing: Length? = nil, @ViewBuilder content: () -> Content)
Content
serves the same purpose here that Label
does in Button
.
Something else to note - when we create a button like this...
Button(action: {}) {
Text("Button")
}
...we're actually doing the same thing as this:
Button(action: {}, label: {
Text("Button")
})
In Swift, when the last argument in a method call is a closure, we can omit the argument label and append the closure to the outside of the closing parenthesis.
In SwiftUI, you cannot implicitly pass content to any View
. The View
must explicitly accept a @ViewBuilder
closure in its initializer.
And so, you cannot pass a @ViewBuilder
closure to FullButton
unless FullButton
accepts a @ViewBuilder
closure as an argument in its initializer, as shown at the beginning of my answer.