SwiftUI - Button - How to pass a function request to parent
The best examples on closures and how they can be used is found in the official swift documentation.
This is a small example on how to pass a closure to your child view which then calls a function of the parent:
struct ChildView: View {
var function: () -> Void
var body: some View {
Button(action: {
self.function()
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(function: { self.fuctionCalledInPassedClosure() })
}
func fuctionCalledInPassedClosure() {
print("I am the parent")
}
}
I hope this helps!
Pass a function
And here is an example to pass the function:
struct ChildView: View {
var function: () -> Void
var body: some View {
Button(action: {
self.function()
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(function: self.passedFunction)
}
func passedFunction() {
print("I am the parent")
}
}
Pass a function with parameters
struct ChildView: View {
var myFunctionWithParameters: (String, Int) -> Void
var body: some View {
Button(action: {
self.myFunctionWithParameters("parameter", 1)
}, label: {
Text("Button")
})
}
}
struct ParentView: View {
var body: some View {
ChildView(myFunctionWithParameters: self.passedFunction)
}
func passedFunction(myFirstParameter: String, mySecondParameter: Int) {
print("I am the parent")
}
}
Call parent view's function from child view with parameters
Here is fixed variant. Tested with Xcode 11.4 / iOS 13.4
struct ChildView: View {
var onUpdate: (Double) -> () // << no labels, just types !!
var body: some View {
VStack {
Text("child view")
Button(action: {
self.onUpdate(3.0)
}) {
Text("onUpdate")
}
}
}
}
SwiftUI How to pass data from child to parent as done in C# with the 'Delegate-EventHandler-EventArgs' way
Here is possible solution. Tested with Xcode 11.4 / iOS 13.4
struct ChildView: View {
var function: (String) -> Void
@State private var value = "Child Value"
var body: some View {
Button(action: {
self.function(self.value)
}, label: {
Text("Button")
})
}
}
struct ContentView: View {
var body: some View {
ChildView { self.setViewBackToNil(myStringParameter: $0) }
}
func setViewBackToNil(myStringParameter: String) {
print("I am the parent: \(myStringParameter)")
}
}
How do you call a function from a subview in SwiftUI?
Here is what I would do, I would create a function in the second view and invoke it under any circumstance, say when a button in the second view is pressed then invoke this function. And I would pass the function's callback in the main view.
Here is a code to demonstrate what I mean.
import SwiftUI
struct StackOverflow23: View {
var body: some View {
VStack {
Text("First View")
Divider()
// Note I am presenting my second view here and calling its function ".onAdd"
SecondView()
// Whenever this function is invoked inside `SecondView` then it will run the code in between these brackets.
.onAdd {
print("Run any function you want here")
}
}
}
}
struct StackOverflow23_Previews: PreviewProvider {
static var previews: some View {
StackOverflow23()
}
}
struct SecondView: View {
// Define a variable and give it default value
var onAdd = {}
var body: some View {
Button(action: {
// This button will invoke the callback stored in the variable, this can be invoked really from any other function. For example, onDrag call self.onAdd (its really up to you when to call this).
self.onAdd()
}) {
Text("Add from a second view")
}
}
// Create a function with the same name to keep things clean which returns a view (Read note 1 as why it returns view)
// It takes one argument which is a callback that will come from the main view and it will pass it down to the SecondView
func onAdd(_ callback: @escaping () -> ()) -> some View {
SecondView(onAdd: callback)
}
}
NOTE 1: The reason our onAdd
function returns a view is because remember that swiftui is based on only views and every modifier returns a view itself. For example when you have a Text("test")
and then add .foregroundColor(Color.white)
to it, what essentially you are doing is that you are not modifying the color of the text but instead you are creating a new Text
with a custom foregroundColor
value.
And this is exactly what we are doing, we are creating a variable that can be initialized when calling SecondView
but instead of calling it in the initializer we created it as a function modifier that will return a new instance of SecondView
with a custom value to our variable.
I hope this makes sense. If you have any questions don't hesitate to ask.
And here is your code modified:
ContentView.swift
import SwiftUI
struct ContentView: View {
@State var childInstances: [Child] = [Child(stateBinding: .constant(.zero))]
@State var childInstanceData: [CGSize] = [.zero]
@State var childIndex = 0
func addChild() {
self.childInstanceData.append(.zero)
self.childInstances.append(Child(stateBinding: $childInstanceData[childIndex]))
self.childIndex += 1
}
var body: some View {
ZStack {
ForEach(childInstances.indices, id: \.self) { index in
self.childInstances[index]
.onAddChild {
self.addChild()
}
}
ForEach(childInstanceData.indices, id: \.self) { index in
Text("y: \(self.childInstanceData[index].height) : x: \(self.childInstanceData[index].width)")
.offset(y: CGFloat((index * 20) - 300))
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Child.swift
import SwiftUI
struct Child: View {
@Binding var stateBinding: CGSize
@State var isInitalDrag = true
@State var isOnce = true
@State var currentPosition: CGSize = .zero
@State var newPosition: CGSize = .zero
var onAddChild = {} // <- The variable to hold our callback
var body: some View {
Circle()
.frame(width: 50, height: 50)
.foregroundColor(.blue)
.offset(self.currentPosition)
.gesture(
DragGesture()
.onChanged { value in
if self.isInitalDrag && self.isOnce {
// Call function in ContentView here... How do I do it?
self.onAddChild() <- // Here is your solution
self.isOnce = false
}
self.currentPosition = CGSize(
width: CGFloat(value.translation.width + self.newPosition.width),
height: CGFloat(value.translation.height + self.newPosition.height)
)
self.stateBinding = self.currentPosition
}
.onEnded { value in
self.newPosition = self.currentPosition
self.isOnce = true
self.isInitalDrag = false
}
)
}
// Our function which will initialize our variable to store the callback
func onAddChild(_ callaback: @escaping () -> ()) -> some View {
Child(stateBinding: self.$stateBinding, isInitalDrag: self.isInitalDrag, isOnce: self.isOnce, currentPosition: self.currentPosition, newPosition: self.newPosition, onAddChild: callaback)
}
}
struct Child_Previews: PreviewProvider {
static var previews: some View {
Child(stateBinding: .constant(.zero))
}
}
How to send an event from a parent view to a child view in SwiftUI
you could try this broad approach, where the @StateObject var vm = ChildVM()
is
declared at the top level (the one source of truth), and the ChildViews
all observe
the changes using the passed in ChildVM
, through an environmentObject
.
struct ParentView : View {
@StateObject var vm = ChildVM() // <-- here at top level
var body : some View {
VStack {
Button( action: { vm.refetchData() }) { // <-- here, all ChildViews will know about it
ChildView1()
ChildView2()
ChildView3()
}
}.environmentObject(vm) // <-- here, will pass it to all ChildViews
}
}
// similarly for ChildView2, ChildView3
struct ChildView1 : View {
@EnvironmentObject var vm: ChildVM // <-- here
var body: some View {
Text("ChildView1")
}
}
class ChildVM: ObservableObject {
init() {
// fetch data
}
func refetchData() {
// refetches data
}
}
See also: https://developer.apple.com/documentation/swiftui/stateobject
Passing a state variable to parent view
In general, your state should always be owned higher up the view hierarchy. Trying to access the child state from a parent is an anti-pattern.
One option is to use @Binding
s to pass the values down to child views:
struct BookView: View {
@Binding var title : String
@Binding var author : String
var body: some View {
TextField("Title", text: $title)
TextField("Author", text: $author)
}
}
struct ContentView: View {
@State private var presentNewBook: Bool = false
@State private var title = ""
@State private var author = ""
var body: some View {
NavigationView {
VStack {
Text("Title: \(title)")
Text("Author: \(author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(title: $title, author: $author)
}
}
}
Another possibility is using an ObservableObject
:
class BookState : ObservableObject {
@Published var title = ""
@Published var author = ""
}
struct BookView: View {
@ObservedObject var bookState : BookState
var body: some View {
TextField("Title", text: $bookState.title)
TextField("Author", text: $bookState.author)
}
}
struct ContentView: View {
@State private var presentNewBook: Bool = false
@StateObject private var bookState = BookState()
var body: some View {
NavigationView {
VStack {
Text("Title: \(bookState.title)")
Text("Author: \(bookState.author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(bookState: bookState)
}
}
}
I've altered your example views a bit because to me the structure was unclear, but the concept of owning the state at the parent level is the important element.
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 get variable value of child view in parent view on SwiftUI?
As mentioned in the comments, state should be owned by a parent view and then passed to the child views.
In your case, you don't actually need to use a custom initializer for CancelRow
in the example you gave (you could rely on just the synthesized initializer), but as request, this uses an initializer.
struct CancelRow: View {
@Binding var isSelected: Bool
init(isSelected: Binding<Bool>) {
self._isSelected = isSelected
}
@ViewBuilder var body: some View {
HStack {
Text(verbatim: "Hello, \(isSelected)")
Button("Toggle") {
isSelected.toggle()
}
}
}
}
struct SubmitButtonView : View {
var buttonTitle : String
var buttonCallBack : () -> Void
var isActive : Bool
var body: some View {
Text("Submit: \(isActive ? "true" : "false")")
}
}
struct ContentView: View {
@State private var isSelected = false
var title : String {
"Title"
}
var body: some View {
VStack {
CancelRow(isSelected: $isSelected)
SubmitButtonView(buttonTitle: title, buttonCallBack: {
goToOtherScreen()
}, isActive: isSelected )
}
}
func goToOtherScreen() {
print("Navigate")
}
}
Related Topics
How to Create Generic Convenience Initializer in Swift
Can't Load Images on MAC Screensaver Release Build (It Works on Xcode Debug Build)
How to Detect If The User Was Deleted from Firebase Auth
Convert/Wrap Swift Struct as Nsvalue for Caanimation Purposes
Why Would One Use Nested Classes
Iocreateplugininterfaceforservice Returns Mysterious Error
How to Draw a Line Between Two Points Over an Image in Swift 3
Shorthand for Wrapping a Swift Variable in an Optional
Passing in Variable Number of Args from One Function to Another in Swift
Issue with Returning a Directory Enumerator from Nsfilemanager Using Enumeratoraturl in Swift
Swift Display Image UIimageview
Difference Between Sort and Sortinplace in Swift 2
Using Getters and Setters to Modify Values W/O Subclassing in Swift
Alamofire Post Request with JSON Encoding