SwiftUI - Button - How to pass a function request to parent
Asked Answered
M

2

44

How can I have a button perform an action which triggers a function in its 'parent' view? I'm trying to refactor my code so that components are as small as possible.

In this case, the button performs a few tasks, but one of them is to run a function:

Button(
 action: {
  self.setViewBackToNil()
 }){
  Text("Button")
}

// which triggers a function
func setViewBackToNil(){
 self.userData.image = nil
 self.isProcessing = false
 .... etc
}

Now, if I turn the button into its own view, I can't pass self.setViewBackToNil because it's contained within the struct of the parent.

Is there a way for a component to trigger a function within its parent?

Malmo answered 22/1, 2020 at 13:48 Comment(1)
unless you pass the function to your child component I don't think there is. (as a closure for example)Electroacoustics
E
110

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")
    }
}
Electroacoustics answered 22/1, 2020 at 14:2 Comment(10)
How do you make var function: () -> Void optional?Bethezel
I believe like so: var function: (() -> Void)?Electroacoustics
The PreviewProvider for the ChildView is missing the function parameter. How can I fix that?Manhole
Maybe add something like this: struct ChildView_Preview: PreviewProvider { static var previews: some View { ChildView() } }Electroacoustics
@Electroacoustics The ChildView() expects a function. Any idea how to do that?Manhole
Okay I just fixed it by replacing ChildView() with ChildView(function: {})Manhole
can anyone let me know how we can use this code to push to new view upon clicking the button instead of just printing using a functionSemiramis
I think this is worth another question and should not be discussed in the comments.Electroacoustics
are methods with parameters allowed? or is it always a no param function for callbacks?Robalo
@Robalo I have added an example with parameters.Electroacoustics
P
-1

Be aware that doing so will break the swift UI's ability to check equality between UI's update and will update your view all the time.

This is due to the fact that there is no comparator for a closure.

So when performance is critical on your child view, you can do so:

(Example from accepted answer)

struct ChildView: View & Equatable {
    var function: () -> Void
    
    var body: some View {
        Button(action: {
            self.function()
        }, label: {
            Text("Button")
        })
    }

    func ==(lhs: ChildView, rhs: ChildView) -> Bool {
        // here you can compare your views property yourself
        // In this case there is nothing to compare so we might give
        // true always
        true
    }
}

struct ParentView: View {
    var body: some View {
        ChildView(function: { self.fuctionCalledInPassedClosure() })
           .equatable()
    }
    
    func fuctionCalledInPassedClosure() {
        print("I am the parent")
    }
}

What I added, is the conformance to the Equatable protocol and added a comparator function to tell swift UI whether my view has changed. In our case there is no property except the callback. You must also say to swift UI that your view is an Equatable view. So in your ParentView you add a .equatable()

If you add the debug prints, you will see the difference.

    var body: some View {
        Self._printChanges()
        return Button(action: {
            self.function()
        }, label: {
            Text("Button")
        })
    }
Palaeontography answered 4/12, 2023 at 13:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.