In an earlier post, I mentioned an issue that the deadline list rows were not updated properly while looking at the deadline coming and going. And that the solution was to do this:
@Observable
class Deadline: Codable {
// ...
var viewUpdateNeeded: Bool = false
//...
var isReached: Bool {
viewUpdateNeeded = date <= Date.now
return viewUpdateNeeded
}
That is, to update an observed value viewUpdateNeeded
to generate a change event to the @Observable
so that SwiftUI view would get updated properly.
I do not know if the code above always had an issue I just didn’t see. Or did an update to Xcode and Swift/SwiftUI libraries change something. But anyways, what I did notice afterwards was this:
- Start the app.
- Hide the left side course list view (when the
ContentView
showedContentUnavailableView
since no course was selected).
In this use case scenario, everything works OK. But if I did this:
- Start the app.
- Select a course from the course list on the left (
ContentView
displaying the deadlines of the currently selected course). - Hide the left side course list view.
Then the app would get stuck, freeze totally, at step 3 (not at step 2). Why this happened is that the list row view got updated repeatedly, so that the app used 100% of the CPU.
I saw this happening when testing the app in Instruments and looking at which operation was taking the most of the execution time. Then I placed a breakpoint into that piece of code and repeated the two usage scenarios. That showed me what was happening – app was not frozen but too busy updating the list rows that it became unusable.
The reason was the line viewUpdateNeeded = date <= Date.now
“changed” the value of viewUpdateNeeded
from false
to false
repeatedly and SwiftUI then repeatedly rebuild the view. While I was in the impression that this was not supposed to be a change. Only if the value would actually change from false
to true
or true
to false
would be a change that would cause view rebuilding.
Either I had misunderstood how SwiftUI works, and my original solution did not work at all, but I just didn’t see that happening until I dit the second use case scenario with the app. Or something changed in how SwiftUI works, and then this became a bug.
Whichever, I do not know. I have no idea why this happened only when hiding the list of courses, since the course deadlines were already visible at step 2 in the second scenario.
Anyways, this change got rid of the bug:
@Observable
class Deadline: Codable {
//...
var viewUpdateNeeded: Bool = false
//...
var isReached: Bool {
if date <= Date.now && !viewUpdateNeeded {
viewUpdateNeeded = true
}
return viewUpdateNeeded
}
That is, change the value of viewUpdateNeeded
only when the condition becomes true.
UI testing is important, folks. Unfortunately I did not implement any automated UI testing for this app, so finding the bug was just by a chance. Regression testing is also important, to check if the app still works after updating the tools, frameworks and libraries used by the app, to catch any changes and possible issues caused by the changes there.