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?
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")
}
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.
There is a ViewInspector library that uses Swift's reflection for extracting SwiftUI views from any hierarchy.
let view = FullButton()
let button = try view.inspect().button()
let children = button.anyView().view(OtherView.Type)
// By the way, you can even tap the button programmatically:
try button.tap()
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With