Reputation: 1718
I have a few views that contain the same button with some different content. Because of this I made a ContainerView
that houses the shared Button
layout, and has room for a generic ContentView
.
I want the ContentView
to respond when the ContainerView
button is tapped.
Using UIKit, I would hold a reference to the ContentView
in the ContainerView
and call a function on it when the button was hit. However, because SwiftUI has all the views as structs, the contentView
is copied when put into the ContainerView
's body
. Thus the reference and the shown ContentView
are different & I cannot send the subview a message.
Code:
struct ContainerView: View {
let contentView = ContentView()
var body: some View {
Group {
/// When this button is tapped, I would like to send a message to the `ContentView`.
Button(action: self.reset, label: { Text("RESET") })
/// Unfortunately, this seemes to send a copy of the `contentView`. So I am unable to send the
/// corrent struct a message.
///
/// How can I send a subview a message from the superview?
self.contentView
}
}
func reset() {
self.contentView.reset()
}
}
struct ContentView: View {
@State private var count: Int = 0
var body: some View {
Group {
Text("Count: \(self.count)")
Button(action: self.increment, label: { Text("Increment") })
}
}
func increment() {
self.count += 1
}
/// When this is called from the `ContainerView`, it is accessing a different ContentView
/// struct than is being displayed.
func reset() {
self.count = 0
}
}
So the question is: how can I send a message to & run some code in the ContentView
when a button in the ContainerView
is tapped?
Upvotes: 2
Views: 3312
Reputation: 1376
Instead of trying to store a reference to the subview, why not make a binding between them? In your example, this could be by binding to the count.
struct ContainerView: View {
@State private var count = 0
var body: some View {
// Your Button wrapping the ContentView
ContentView(count: $count)
}
func reset() {
self.count = 0
}
}
struct ContentView: View {
@Binding var count: Int
// ContentView's body
}
When the ContainerView
resets the count, the binding will update the child.
EDIT: I see your comments about wanting ContentView
to control the reset logic. What about trying to replicate some of the functionality of things like NavigationLink
, where an isActive:
bool is set, then reset, by the system on navigation?
In your case, you could try the following:
struct ContainerView: View {
@State private var shouldReset: Bool = false
var body: some View {
// Your Button wrapping the ContentView
ContentView(shouldReset: $shouldReset)
}
func reset() {
self.shouldReset = true
}
}
struct ContentView: View {
@Binding var shouldReset: Bool {
didSet {
if shouldReset {
// Call your reset logic here
}
shouldReset = false
}
}
// ContentView's body
}
Your ContentView
will know the change, we'll see it as a separate "state", and then that state is reset once the action is complete.
It's probably not the ideal solution, but to me it seems to replicate a pattern shown by some first party SwiftUI components.
Upvotes: 6