ghard123
(Greg)
1
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()
}
ibex10
2
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. 
//
// 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
}
}
crontab
(Hovik Melikyan)
3
If I can suggest an improvement that will save many lines of code 
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
rayx
(Huan Xiong)
4
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
ghard123
(Greg)
5
Thanks everyone for your advice. Heaps to look at.