Reputation: 23
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
Reputation: 4006
There are 2 questions that I have identified in your request that are the key to your problem.
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
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
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 Student
s, 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