SwiftUI Pass two child views to View
Here is possible variant. Tested with Xcode 11.4 / iOS 13.4
struct FlippableView<V1: View, V2: View>: View {
@State private var flipped = false
@State private var degrees = 0.0
var frontCard: V1
var backCard: V2
@inlinable public init(@ViewBuilder content: () -> TupleView<(V1, V2)>) {
let t = content()
self.frontCard = t.value.0
self.backCard = t.value.1
}
var body: some View {
return Group() {
if self.degrees < 90 {
self.frontCard
} else {
self.backCard
.rotation3DEffect(Angle(degrees: 180), axis: (x: CGFloat(0), y: CGFloat(10), z: CGFloat(0)))
}
}
}
}
Sharing a @State var Bool between 2 child Views in SwiftUI
I have spent hours on Internet to learn more about using @State/@Binding or @EnvironmentObject
So you are on the right path because you do need to use @Binding
. Maybe you just had the wrong idea of how to connect the dots.
Basically you need the @State
property wrapper in your parent or ContentView
and since you want to share that same property in your child views and react to any mutation on it, you use the @Binding
property wrapper in the child views.
So you would have something like this:
struct ContentView: View {
@State var showHello = true
var body: some View {
VStack {
TopView(showHello: $showHello)
BottomView(showHello: showHello)
}
}
}
struct TopView: View {
@Binding var showHello: Bool
var body: some View {
Button("Show Hello", action: { showHello.toggle() })
.padding()
}
}
struct BottomView: View {
let showHello: Bool
var body: some View {
Text("This is your message:")
Text("Hello")
.opacity(showHello ? 1 : 0)
}
}
I would recommend some other good resources for SwiftUI:
- SwiftUI State Management
- @Binding
- SwiftUI Articles by John Sundell
How do I pass data from a child view to a parent view to another child view in SwiftUI?
You can use EnvironmentObject for stuff like this...
The nice thing about EnvironmentObject is that whenever and wherever you change one of it's variables, it will always make sure that every where that variable is used, it's updated.
Note: To get every variable to update, you have to make sure that you pass through the BindableObject class / EnvironmentObject on everyView you wish to have it updated in...
SourceRectBindings:
class SourceRectBindings: BindableObject {
/* PROTOCOL PROPERTIES */
var willChange = PassthroughSubject<Application, Never>()
/* Seems Like you missed the 'self' (send(self)) */
var sourceRect: CGRect = .zero { willSet { willChange.send(self) }
}
Parent:
struct ParentView: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
childViewA()
.environmentObject(sourceRectBindings)
childViewB()
.environmentObject(sourceRectBindings)
}
}
ChildViewA:
struct ChildViewA: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
// Update your frame and the environment...
// ...will update the variable everywhere it was used
self.sourceRectBindings.sourceRect = CGRect(x: 0, y: 0, width: 300, height: 400)
return Text("From ChildViewA : \(self.sourceRectBindings.sourceRect)")
}
}
ChildViewB:
struct ChildViewB: view {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
// This variable will always be up to date
return Text("From ChildViewB : \(self.sourceRectBindings.sourceRect)")
}
}
LAST STEPS TO MAKE IT WORK
• Go into your highest view you want the variable to update which is your parent view but i usually prefer my SceneDelegate ... so this is how i usually do it:
let sourceRectBindings = SourceRectBindings()
• Then in the same class 'SceneDelegate' is go to where i specify my root view and pass through my EnviromentObject
window.rootViewController = UIHostingController(
rootView: ContentView()
.environmentObject(sourceRectBindings)
)
• Then for the last step, I pass it through to may Parent view
struct ContentView : View {
@EnvironmentObject var sourceRectBindings: SourceRectBindings
var body: some View {
ParentView()
.environmentObject(sourceRectBindings)
}
}
If you run the code just like this, you'll see that your preview throws errors. Thats because it didn't get passed the environment variable.
To do so just start a dummy like instance of your environment:
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(SourceRectBindings())
}
}
#endif
Now you can use that variable how ever you like and if you want to pass anything else other that the rect ... you can just add the variable to the EnvironmentObject class and you can access and update it
How to access to the children views in SwiftUI?
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.
What is the correct way to share state variables between parent and child views in SwiftUI
When you want to share mutable state across views you probably want to take advantage of a class conforming to ObservableObject
. You might end up with something a little more like this:
class CustomState: ObservableObject {
@Published var options: [String]
@Published var selected:[Bool] = []
init(options: [String]) {
self.options = options
}
}
struct CustomParentView: View {
@ObservedObject var a = CustomState(options: ["a","b","c"])
@ObservedObject var b = CustomState(options: ["d","e","f"])
var body: some View {
ScrollView {
VStack{
CustomChildView(state: a)
CustomChildView(state: b)
}
}
}
}
struct CustomChildView: View {
@ObservedObject var state: CustomState
// ...
func selectButtonPressed(_ x:Int) {
for i in 0..<state.options.count {
state.selected[i] = (i == x)
}
}
}
Accessing @State variables in child views from parent views SwiftUI
You can declare your child view properties as @Binding
instead of @State
:
struct DetailedView: View {
...
@State public var exercise: String = "Bench Press"
}
struct ExerciseCard: View {
@Binding public var exercise: String
...
}
and then pass the variable from the parent view to the child view:
ExerciseCard(exercise: $exercise)
Related Topics
Querying in Firebase by Child of Child
How to Call a Method on a Uiview from Outside the Uiviewrepresentable in Swiftui
Same Class Extension in Two Different Modules
Pattern Matching in a Swift for Loop
Why Strings Are Not Equal in My Case
Swift How to Convert Parse Createdat Time to Time Ago
How Marquee Text of Label on Swift
Firebase Storage Overwriting Files
How to Use Implicitly Unwrapped Optionals
Is There a Number Type with Bigger Capacity Than U_Long/Uint64 in Swift
How to Customize the Font and Appearance of a Uialertcontroller in the New Xcode W/ iOS8
Get a List of Nodes in an Specific Area
Cannot Convert Value of Type 'Binding<String>' to Expected Argument Type 'Binding<String>'
How to Prompt for Accessibility Features in a MACos App (From the Appdelegate)
Fetch Coredata with One to Many Relationship in Swift
Swiftui [Bug] Navigationview and List Not Showing on iPad Simulator Only