I am building a SwiftUI App and have all my UI related stuff within a global observable class UILogic. That class itself has a @Published variable called bp which is of type BoxParameters (struct).
My SwiftUI View observes this published variable, which has a lot of components: aspectRatio, frameWidth, xOffset, yOffset, etc. If I want my View to be wider for example, I just call the setWidth() function like this:
struct BoxParameters {
private(set) var frameWidth: CGFloat = 175
mutating func setWidth(newWidth: Double) {
self.frameWidth = newWidth
}
}
class UILogic: ObservableObject {
@Published var bp = BoxParameters
func doubleWidth() {
bp.setWidth(bp.frameWidth * 2)
}
}
This works fine: because it’s mutating, it creates a new struct instance, which triggers @Published to send an update and the view changes with the new width.
What I'm struggling to do is to change the frameWidth (or any other struct variable) with a timer. So let’s say I don’t want to change the value instantly, but want to change it by incrementing the value 10 times every second.
My first guess was to use timer directly:
mutating func setWidth(newWidth: Double, slow: Bool = false) {
if !slow {
self.frameWidth = newWidth
} else {
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { timer in
self.frameWidth += 1
if self.frameWidth >= newWidth {
self.frameWidth = newWidth
timer.invalidate()
}
}
}
}
This code doesn't compile and throws an error: Escaping closure captures mutating 'self' parameter
This has already made me scratch my head a bit, so I started digging around for solutions: https://stackoverflow.com/a/47173607/12596719\ https://developer.apple.com/forums/thread/652094\
Those two threads sparked a hope that my problem might be finally solved didn’t change anything: the compiler was still complaining.
What seemed to solve my problem was this thread, so I tried to adapt it in my code (just to test out it is a void function just increases the frameWidth by 50):
struct BoxParameters {
...
var timerLogic: TimerLogic!
class TimerLogic {
var structRef: BoxParameters!
var timer: Timer!
init(_ structRef: BoxParameters){
self.structRef = structRef;
self.timer = Timer.scheduledTimer(
timeInterval: 0.1,
target: self,
selector: #selector(timerTicked),
userInfo: nil,
repeats: true)
}
func stopTimer(){
self.timer?.invalidate()
self.structRef = nil
}
@objc private func timerTicked(){
self.structRef.timerTicked()
}
}
mutating func startTimer(){
print("Start Timer")
self.timerLogic = TimerLogic(self)
}
mutating func stopTimer() {
print("Stop Timer")
self.timerLogic.stopTimer()
self.timerLogic = nil
}
mutating func timerTicked(){
self.frameWidth += 50
print("Timer: new frame width: \(self.frameWidth)")
}
}
Expected behavior: it increased the frameWidth by 50
What happens: it prints that the frame width has been increased by 50 (printing value is correct), but nothing changes.
BUT: if I call the function timerTicked manually, the frameWidth changes by 50 as expected! ugh!
What I think is happening is that the timer is changing the frameWidth of a copy of the struct without changing the real struct, but then again, the timerTicked function should change the parent struct itself. (because of self.
)
Anyone knows a way to solve this issue? Changing the struct to an observed class would've been an option but due to Swift's design, a change of a @Published variable inside a @Published class doesn’t notify SwiftUI of a change...