Removing functions to another struct

I am trying to make a simple counter. I have the ContentView, and a separate struct that allows me to configure buttons in the ContentView. Each button calls a function in ContentView (Add 1, Add 2, etc). I feel that these functions should be in the MyButton struct but can't work out how to. Can anyone point me in the right direction please?

import SwiftUI

struct Test: View {
    @State private var score = 0
    
    var body: some View {
        ZStack {
            VStack {
                VStack {
                    Text("The score is:")
                        .font(.largeTitle)
                    Text("\(score)")
                        .font(.system(size: 90))
                }
                VStack(spacing: 20){
                    HStack(spacing: 20) {
                        MyButton1(name: "Add 1", color: .green, action: addOne)
                        MyButton1(name: "Add 2", color: .cyan, action: addTwo)
                        MyButton1(name: "Add 3", color: .red, action: addThree)
                    }
                    HStack(spacing: 20) {
                        MyButton(name: "Minus 1", color: .yellow, action: minusOne)
                        MyButton(name: "Reset", color: .brown, action: reset)
                    }
                }
                Spacer()
            }
        }
    }
    func addOne() {
        score += 1
    }
    func addTwo() {
        score += 2
    }
    func addThree() {
        score += 3
    }
    func minusOne() {
        score -= 1
    }
    func reset() {
        score = 0
    }
}

struct MyButton1: View {
    var name: String
    var color: Color
    var action: () -> Void
    
    var body: some View {
        ZStack {
            Button {
                action()
            } label:
            {Text(name)
                    .font(.title3)
                    .frame(width: 100, height: 40)
                    .background(color)
                    .cornerRadius(70)
                    .foregroundColor(.black)
            }
            Capsule()
                .stroke(Color.black, lineWidth: 2)
                .frame(width:100, height: 40)
        }
    }
}

#Preview {
    Test()
}

On the surface this appears to be a SwiftUI question, but deep down it is really a question about how to work with types in Swift.

So I am going to provide an answer, which is suboptimal but works. :slight_smile:

//
//  Test.swift
//  Questions
//
//  Created by ibex on 6/8/2024.
//

import SwiftUI

struct Test: View {
    @State private var score = 0
    
    var body: some View {
        ZStack {
            VStack {
                VStack {
                    Text("The score is:")
                        .font(.largeTitle)
                    Text("\(score)")
                        .font(.system(size: 90))
                }
                VStack(spacing: 20){
                    HStack(spacing: 20) {
                        MyButton1 (name: "Add 1", color: .green, value: $score, action: .ADD1)
                        MyButton1 (name: "Add 2", color: .cyan,  value: $score, action: .ADD2)
                        MyButton1 (name: "Add 3", color: .red,   value: $score, action: .ADD3)
                    }
                    HStack(spacing: 20) {
                        MyButton1 (name: "Minus 1", color: .yellow, value: $score, action: .SUB1)
                        MyButton1 (name: "Reset",   color: .brown,  value: $score, action: .RESET)
                    }
                }
                Spacer()
            }
        }
    }
}

#Preview {
    Test()
}

struct MyButton1: View {
    private var name: String
    private var color: Color
    private var _action: (() -> Void)?

    private var value: Binding <Int>?
    
    init (name: String, color: Color, value: Binding <Int>, action:Action) {
        self.name = name
        self.color = color
        self.value = value
        
        switch action {
        case .ADD1:
            self._action = self.add_1
        case .ADD2:
            self._action = self.add_2
        case .ADD3:
            self._action = self.add_3
        case .SUB1:
            self._action = self.sub_1
        case .RESET:
            self._action = self.reset
        }
    }
    
    var body: some View {
        ZStack {
            Button {
                _action?()
            } label:
            {Text(name)
                    .font(.title3)
                    .frame(width: 100, height: 40)
                    .background(color)
                    .cornerRadius(70)
                    .foregroundColor(.black)
            }
            Capsule()
                .stroke(Color.black, lineWidth: 2)
                .frame(width:100, height: 40)
        }
    }
}

extension MyButton1 {
    enum Action {
        case ADD1, ADD2, ADD3
        case SUB1
        case RESET
    }
    
}

extension MyButton1 {
    private func add_1() {
        value?.wrappedValue += 1
    }
    
    private func add_2() {
        value?.wrappedValue += 2
    }
    
    private func add_3() {
        value?.wrappedValue += 3
    }
}

extension MyButton1 {
    private func sub_1() {
        value?.wrappedValue -= 1
    }
    
    private func reset() {
        value?.wrappedValue = 0
    }
}

If I can suggest an improvement that will save many lines of code :slight_smile:

enum Action {
	case add(Int)
	case reset
}


struct MyButton1: View {
	@Binding var score: Int
	var name: String
	var color: Color
	var action: Action

	var body: some View {
		ZStack {
			Button {
				switch action {
					case .add(let value):
						score += value
					case .reset:
						score = 0
				}
			} label: {
				Text(name)
				// styling
			}
			Capsule()
			// styling
		}
	}
}

(Sidenote: it is best to have Capsule() inside the button label so that the entire area becomes tappable. You can overlay Text and Capsule with ZStack, or .overlay { })

1 Like

First, if I understand your purpose correctly, the view name Test is misleading. It isn't a test but a UI component you intended to use in your app (let's call it FooView). This is important because it affects how the code should be organzied.

Based on that assumption, I think your current code is OK - FooView has a state score and has several methods to change that state. I don't think it makes sense to move these methods to Buttton.

Alternative approach (this is the one I prefer to): you can define a custom struct for score and move those addXYZ() methods to it. Then you can specify these methods directly when constructing the UI:

    Button {
        score.addOne()
    }

I don't have access to my Mac at the moment, but I think it should work and is the most concise way to do it. No need to use binding. No need to define a custom button. And no need to pass operation parameter to that custom button (I think it's overkilling).

1 Like

Thanks everyone for your advice. Heaps to look at.