Pasco
Pasco

Reputation: 23

Changing Button Label and Title dynamically for Buttons created from Array (in swift)

My environment: The code below is written in swift Playgrounds 4 on my company IPad.

Goal of the project: I am trying to improve upon the classroom management tool of my school, which is basically a paper traffic light with a clothespin for each student. I want an App in which all of my 28 students are represented by a button each Those buttons are supposed to changes from green to yellow when tapped 4 times and to red if tapped thrice more. As a nice-to-have feature the total number ob button tabs (aka. warnings) should be displayed on the buttons. Also the buttons should be structured in a meaningful way (like the tables of my students; a 7 x 4 grid).

My progress so far: I wrote a class (student), and created instances of that class to represent four of my favorite students. I packed them into an array (students) to easily create a button for each of them. In this class “state” is the amount of times the student has been warned (the button has been tapped) and “color” is supposed to be the color of the respective button.

As of this writing I need a variable (col) for handling of the color. I think that would be unnecessary but did not get the color change to work in a different way.

After structuring in a grid I create a button for each element in the array (students). On buttonPress the state and color are updated for the respective student and (for reasons unknown to me) col is updated as well.

The label of the button then displays the name, state and color of the respective student... or does it?

My problems at this point: The state (as displayed on the buttons) updates only when the color changes and when it does it changes the color for all the buttons and not just the one. I would like the buttons to change their color individually and the label to update on each press of a button. Sadly I have not found the correct syntax to do so.

My questions:

How can I set a default color (green) within my class? How do I persuade my buttons to change color individually? How would I get my labels to update on buttonPress?

Thanks in advance!

Code

import SwiftUI

public class student{
    public var first: String
    public var last: String
    public var state: Int
    public var color: Color
    public init(first: String, last: String, color: Color){
        self.first = first
        self.last = last
        state = Int()
        self.color = color
    }
}
var John = student(first: "John", last: "Lennon", color: .green)
var Paul = student(first: "Paul", last: "McCartney", color: .green)
var Georg = student(first: "Georg", last: "Harrison", color: .green)
var Ringo = student(first: "Ringo", last: "Starr", color: .green)

var students = [John, Paul, Georg, Ringo]

struct ContentView: View {
    
    @State private var col = Color.green
    
    let columnLayout = Array(repeating: GridItem(), count: 7)
    
    
    var body: some View {
        NavigationView{
            ScrollView{
                LazyVGrid(columns: columnLayout){
                    ForEach(students, id: \.last) { student in
                        
                        Button{
                            student.state += 1
                            //print(student.state)
                            
                            if (student.state < 4) {
                                student.color = .green
                            }
                            
                            else if (student.state >= 7){
                                student.color = .red
                            }
                            
                            else {
                                student.color = .yellow
                            }
                            
                            col = student.color
                            //print(col)
                            
                        } label:{
                            ZStack{
                                RoundedRectangle(cornerRadius: 10, style: .continuous)
                                    .foregroundColor(col)
                                    .aspectRatio(2.0, contentMode: ContentMode.fit)
                                
                                Text("\(student.first) - \(student.state)")
                                    .foregroundColor(.black)
                                //.font(.largeTitle)
                                    .scaledToFit()
                            }
                        }
                    }
                }
            }.navigationTitle("Classroom Management")
        }.navigationViewStyle(.stack)
    }
}

Upvotes: 2

Views: 1479

Answers (2)

HunterLion
HunterLion

Reputation: 4006

There are 2 questions that I have identified in your request that are the key to your problem.

  1. How can I set a default color (green) within my class?

Just create a default value inside your class definition. Replace the existing init() of your class with:

public init(first: String, last: String, color: Color = .green)   // Default value, you can even omit the colour in your initializer
  1. How do I persuade my buttons to change color individually?

First, your view has a variable col that retains the same value throughout the view. That's the color of your button. Just delete it and use each student's color:

.foregroundColor(student.color)

Edit

Second, students is an array of structs, they will not publish changes. One workaround (promoted also by Apple due to the benefits of SwiftUI) is to create an additional view that will change the state every time the single student changes.

So, create another view called like "StudentButton", and inside that view you can have the @State property for the single student.

Here's the code that I tested and works (I have modified a little bit the layout but you can setup your view as you wish):

struct Example: View {

    @State private var students = [john, paul, georg, ringo]
    
    let columnLayout = Array(repeating: GridItem(), count: 4)
    
    var body: some View {
        NavigationView{
            ScrollView{
                LazyVGrid(columns: columnLayout){
                    ForEach(students, id: \.last) { student in
                        
                        // Call another view here
                        StudentButton(student)
                        
                    }
                }
            }.navigationTitle("Classroom Management")
        }.navigationViewStyle(.stack)
    }
}


// Here's the other view
struct StudentButton: View {
    
    // This is the state var of each single student
    @State private var student: Student
    
    init(_ student: Student) {
        self.student = student
    }
    
    var body: some View {
        Button{
            student.state += 1
            
            if (student.state < 4) {
                student.color = .green
            }
            
            else if (student.state >= 7){
                student.color = .red
            }
            
            else {
                student.color = .yellow
            }
            
        } label:{
            
            // I changed this just to visualize better, but just do as you please
            Text("\(student.first) - \(student.state)")
                .foregroundColor(.black)
            .padding()
            .background {
                RoundedRectangle(cornerRadius: 10, style: .continuous)
                    .foregroundColor(student.color)
                    .aspectRatio(2.0, contentMode: .fit)
            }
        }
    }
}

Upvotes: 0

jnpdx
jnpdx

Reputation: 52565

There are a couple of issues going on here. The first is that in SwiftUI, generally you want your model to be a struct, since SwiftUI Views respond well to changes in value types (like a struct) out-of-the-box.

public struct Student {
    public var first: String
    public var last: String
    public var state: Int
    public var color: Color
    public init(first: String, last: String, color: Color){
        self.first = first
        self.last = last
        state = Int()
        self.color = color
    }
}

var john = Student(first: "John", last: "Lennon", color: .green)
var paul = Student(first: "Paul", last: "McCartney", color: .green)
var georg = Student(first: "Georg", last: "Harrison", color: .green)
var ringo = Student(first: "Ringo", last: "Starr", color: .green)

Note that I've also changed your code a bit to use the Swift conventions of capitalizing type names and lowercasing variable names.

Next, since you're trying to change properties on the Students, you'll want to represent them with a @State array. Then, by using the new element binding syntax on the ForEach line (see the $ characters), you can get a reference to each Student that you can modify.

struct ContentView: View {
    
    @State private var students = [john, paul, georg, ringo]
    
    let columnLayout = Array(repeating: GridItem(), count: 7)
    
    var body: some View {
        NavigationView{
            ScrollView{
                LazyVGrid(columns: columnLayout){
                    ForEach($students, id: \.last) { $student in
                        
                        Button{
                            student.state += 1
                            
                            if (student.state < 4) {
                                student.color = .green
                            }
                            
                            else if (student.state >= 7){
                                student.color = .red
                            }
                            
                            else {
                                student.color = .yellow
                            }
                            
                        } label:{
                            ZStack{
                                RoundedRectangle(cornerRadius: 10, style: .continuous)
                                    .foregroundColor(student.color)
                                    .aspectRatio(2.0, contentMode: .fit)
                                
                                Text("\(student.first) - \(student.state)")
                                    .foregroundColor(.black)
                                    .scaledToFit()
                            }
                        }
                    }
                }
            }.navigationTitle("Classroom Management")
        }.navigationViewStyle(.stack)
    }
}

Upvotes: 1

Related Questions