Functions as function parameters

I am having a problem passing functions into a function as a parameter. I want to pass the function 'addOne()' (at the bottom of the below code) into the 'buttonView' function above it. The parameters relate to a button (there are five in the full code), and change the color, the title, and finally the action of the button. In this case it is meant to add 1 to the score. Can someone assist me please?

 func button(name: String, color: Color) -> some View {
        
        var buttonView: some View {
            
            ZStack {
                Button(action: {
                    addOne()
                }) {
                    Text(name)
                        .fontWeight(.bold)
                        .font(.title)
                        .frame(width: 100, height: 100)
                        .background(color)
                        .cornerRadius(70)
                        .foregroundColor(.black)
                }
                Circle()
                    .stroke(Color.black, lineWidth: 5)
                    .frame(width:150, height: 100)
            }
            
        }
        
        return buttonView
    }
    
    func addOne() {
        score += 1
    }

I’ve moved this from Evolution to Using Swift.

Could you put triple back-ticks (```) around your code example to format it?

This will do
struct ButtonView: View {
    let name: String
    let color: Color
    let execute: () -> Void
    
    var body: some View {
        ZStack {
            Button(action: execute) {
                Text(name)
                    .fontWeight(.bold)
                    .font(.title)
                    .frame(width: 100, height: 100)
                    .background(color)
                    .cornerRadius(70)
                    .foregroundColor(.black)
            }
            Circle()
                .stroke(Color.black, lineWidth: 5)
                .frame(width:150, height: 100)
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            ButtonView(name: "first", color: .red) {
                print("do first")
            }
            ButtonView(name: "second", color: .green) {
                print("do second")
            }
            ButtonView(name: "third", color: .blue) {
                print("do third")
            }
        }
    }
}

Thanks Tera. How do I pass in the individual functions to the ButtonView though??

func addOne() {
        score += 1
    }
    func addTwo() {
        score += 2
    }

I have modified @Tera's code to show how to pass in a member function to ButtonView :slight_smile:

Here is the key:

let action: (ButtonView) -> (() -> Void)

Pass in Individual Functions
//
//  ContentView.swift
//
//  ButtonViewPassFunctions
//
//  Created by ibex10 on 16/5/2022.
//

import SwiftUI

struct ButtonView: View {
    let name: String
    let color: Color
    
    let action: (ButtonView) -> (() -> Void)
    
    func execute () -> Void {
        action (self)()
    }

    var body: some View {
        ZStack {
            Button (action: execute) {
                Text(name)
                    .fontWeight(.bold)
                    .font(.title)
                    .frame(width: 100, height: 100)
                    .background(color)
                    .cornerRadius(70)
                    .foregroundColor(.black)
            }
            Circle()
                .stroke(Color.black, lineWidth: 5)
                .frame(width:150, height: 100)
        }
    }
    
    func addOne() {
        print (#function)
    }
    
    func addTwo() {
        print (#function)
    }
    
    func addThree() {
        print (#function)
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            ButtonView (name: "first",  color: .red,   action: ButtonView.addOne)
            ButtonView (name: "second", color: .green, action: ButtonView.addTwo)
            ButtonView (name: "third",  color: .blue,  action: ButtonView.addThree)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I think the answer you’re looking for is this:

Button(action: addOne)
1 Like

Thanks. I am just not sure how to write the parameter for the function. Below I have 'action:' but then what? I appreciate the help with this.

  func buttonView(name: String, color: Color, action: ????) -> some View {

What's the signature that Button itself uses? What happens when you use that?

Hi Jon and thanks for helping. There are 5 functions that the 5 buttons use (all below). As an example, button 1 will have a label "+1" on it, it will be red, and action the first func below. The second button will have label "+2", be yellow, and perform the second func below, and so on. I can get the labels and colours ok, but I can't get how to pass the below funcs into the main button func as parameters.

func addOne() {
        score += 1
    }
    func addTwo() {
        score += 2
    }
    func addThree() {
        score += 3
    }
    func reset() {
        score = 0
    }
    func minusOne() {
        score -= 1
    }

The type of each of those functions is () -> Void (a function that takes zero parameters and returns nothing). So, that’s what you’d put as the type of the action parameter:


func buttonView(name: String, color: Color, action: () -> Void) -> some View {

Got it thanks. Can you tell me what I then put in the Button braces to activate action? I have tried inserting 'action', (action), and 'action()', but none work. Again thanks for your help

func buttonView(name: String, color: Color, action: () -> Void) -> some View {
        
        var buttonView: some View {
            
            ZStack {
                
                Button {
                    
                } label:
                {Text(name)
                        .fontWeight(.bold)
                        .font(.title)
                        .frame(width: 100, height: 100)
                        .background(color)
                        .cornerRadius(70)
                        .foregroundColor(.black)
                }
                Circle()
                    .stroke(Color.black, lineWidth: 5)
                    .frame(width:120, height: 100)
            }
        }
        return buttonView
    }

Not just "don't work", they are telling you:
Passing non-escaping parameter 'action' to function expecting an @escaping closure
or Escaping closure captures non-escaping parameter 'action'

The thing it wants is the escaping attribute:

func buttonView(name: String, color: Color, action: @escaping () -> Void) -> some View {

then you can write either:

Button(action: action) {
    Text("Hello, World")
}

or:

Button {
    action()
} label: {
    Text("Hello, World")
}

If you were using the view like I written above "escaping" attribute is assumed:

struct ButtonView: View {
    let name: String
    let color: Color
    let execute: () -> Void
    ...
}

ButtonView(..., execute: action) or 
ButtonView(...) { action() }

The way you have it is ok, just there is no great need for the internal variable, it could be just:

func buttonView(name: String, color: Color, action: @escaping () -> Void) -> some View {
    Button(action: action) {
        ...
    }
}
// i am simplifying, in your case it is ZStack

You probably figured out the @escaping part yourself and are wondering why you don’t see anything change when it calls your function. What you are likely running into is less of a Swift issue and more of an understanding how SwiftUI draws Views issue.

For example, put this in Swift Playgrounds and run it and you’ll see your button and a number of times it’s been tapped above it, but when you tap on it the number stays at zero, despite indications that your addOne function runs every tap.

import SwiftUI
import PlaygroundSupport

var score: Int = 0

func addOne() {
    score += 1
}

func button(name: String, color: Color, action: @escaping () -> Void) -> some View {
    
    var buttonView: some View {
        ZStack {
            Button(action: action) {
                Text(name)
                    .fontWeight(.bold)
                    .font(.title)
                    .frame(width: 100, height: 100)
                    .background(color)
                    .cornerRadius(70)
                    .foregroundColor(.black)
            }
            Circle()
                .stroke(Color.black, lineWidth: 5)
                .frame(width:150, height: 100)
        }
        
    }
    
    return buttonView
}

struct StaticContentView: View {
    var body: some View {
        VStack {
            Text("\(score)") 
            button(name: "add", color: .primary, action: addOne)
        }
    }
}

PlaygroundPage.current.setLiveView(StaticContentView())

The issue here is that SwiftUI doesn’t know the score changed, so it isn’t redrawing. Now, if your model was in an actual @ObservableObject and your score @Published so SwiftUI can see changes made to the model, then it would know when to redraw.

For example, add this to the playground and you’ll see the score updates are reflected in the View.

class Scores: ObservableObject {
    @Published var score1: Int = 0
    func addOne() {
        score1 += 1
    }
}

struct DynamicContentView: View {
    @ObservedObject var model = Scores()
    var body: some View {
        VStack {
            Text("\(model.score1)")
            button(name: "add", color: .primary) { model.addOne() }
        }
    }
}

PlaygroundPage.current.setLiveView(DynamicContentView())

I very highly recommend watching the Stanford CS193p class that’s available for free on YouTube and it will teach you a great deal about SwiftUI.

Wow. That helps heaps. Thanks Tera and Toph for all of the assistance. Greg

1 Like

I have started the Stanford course. It looks fantastic.

1 Like