Reputation: 1349
I am trying to make a view where once a user pressed a button, 5 circles (lights) will turn on one after another. However, when the model changes the status of the light, the view doesn't update to represent each lights status.
struct ReactionLightsView: View {
@ObservedObject var viewModel: ReactionLightsViewModel
var body: some View {
VStack {
VStack(spacing: 0) {
HStack {
ForEach(viewModel.lights) { light in
Circle()
.foregroundColor(light.color)
}
}
} .padding()
Button(action: viewModel.start ) {
Text("Start")
}
}
}
}
class ReactionLightsViewModel: ObservableObject {
@Published private var model: ReactionLightsModel
init(){
model = ReactionLightsModel()
}
var lights: [Light] {
model.lights
}
func start() {
model.startTest()
}
}
struct ReactionLightsModel {
private(set) var lights: [Light] = [Light(), Light(), Light(), Light(), Light()]
private(set) var start: UInt64?
private(set) var stop: UInt64?
private(set) var reactionTimeNanoseconds: UInt64?
mutating func startTest() {
print("start reaction test")
for i in 0..<5 {
lights[i].turnOn();
sleep(1)
}
for i in 0..<5 {
lights[i].turnOff()
}
start = DispatchTime.now().rawValue
print("done with reaction test")
}
mutating func stopTest() {
stop = DispatchTime.now().rawValue
reactionTimeNanoseconds = (stop! - start!)
print(reactionTimeNanoseconds)
}
}
Initially, I had the lights as an array of Booleans, with this implementation the lights would turn red (on) but only all at once once the 5 seconds had elapsed. However, not I changed each light to be its own object and the view does not update at all.
struct Light: Identifiable {
private(set) var isLit: Bool = false
private(set) var color: Color = .gray
let id = UUID()
mutating func turnOn() {
isLit = true
color = .red
}
mutating func turnOff() {
isLit = false
color = .gray
}
}
Would appreciate any advice on how to fix this and any other recommendations on how I can improve my code.
Upvotes: 1
Views: 1479
Reputation: 52312
Right now, you're trying to sleep
on the main thread -- my suspicion is that the system won't even let you do that. Generally, sleep
should probably be avoided in general, since it halts execution of the thread and especially not on the UI thread. Judging by your variable names, it looks like you're getting ready to test someone's reaction time to the lights all going on or off, which also wouldn't work with the sleep
strategy, because any button press would be blocked by the sleep
strategy. I suppose you could still measure after the sleep
calls, but you'd lose the ability to measure anything that happened early.
If you remove the sleep
and the code to turn the lights off, you'll see that the lights do in fact get turned on, so it's not that the UI isn't updating with data changes.
I'd suggest rewriting your code to either use a Timer
or DispatchQueue
asyncAfter
. You'll need to keep track of the state of the lights and which should be lit next.
Update, showing the beginnings of a Timer
example:
struct ReactionLightsModel {
private(set) var lights: [Light] = [Light(), Light(), Light(), Light(), Light()]
private var currentLight = 0
private(set) var start: UInt64?
private(set) var stop: UInt64?
private(set) var reactionTimeNanoseconds: UInt64?
mutating func turnOnNextLight() {
lights[currentLight].turnOn()
currentLight += 1
if currentLight == lights.count {
currentLight = 0
}
}
}
class ReactionLightsViewModel: ObservableObject {
@Published private var model: ReactionLightsModel
var timer : Timer?
init(){
model = ReactionLightsModel()
}
var lights: [Light] {
model.lights
}
func start() {
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.model.turnOnNextLight()
}
}
}
Upvotes: 1