How to access to the children views in SwiftUI?

2019-08-22 05:37发布

问题:

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.

回答1:

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.



标签: swiftui