Is it possible to do something with @Observable class to make it constantly monitored and updatable?

Using SwiftUI, the timecode (seconds notation) has been referenced using ObservableObject as follows. In this case, the timecode values were reflected in Text in real time without any problem.

struct ContentView: View {
                       .
                       .
                       .
    var body: some View {
        NavigationStack {
         // Time Code Text
          Text(String(format:"%02d:%02d:%02d", sMinute, sSecond, sMsec))
                .font(Font(UIFont.monospacedDigitSystemFont(ofSize: 30.0, weight: .regular)))
class ViewModel: ObservableObject {
// Time Code "%02d:%02d:%02d"
  @Published var sMinute = 0
  @Published var sSecond = 0
  @Published var sMsec = 0

When I changed it to @Observable class as follows, the timecode operation is stopped and the values are updated only when the operation is finished.

@Observable class ViewModel {
// Time Code "%02d:%02d:%02d"
  var sMinute = 0
  var sSecond = 0
  var sMsec = 0

Is it possible to do something with the @Observable class that would allow it to be constantly monitored and updated in real time?

Or should we change it back?

If we have a history of changing to @Observable in relation to other functions, and we have no choice but to revert back, is this where we need to make a change that would keep it functional separately?

Accepted Reply

I’m not sure what’s going on here but there’s no magic required to make updates from an @Observable object update the SwiftUI state. Consider this version of your view model:

import Foundation
import Observation

@Observable
class ViewModel {
    var hours: Int = 0
    var minutes: Int = 0
    var seconds: Int = 0
    
    init() {
        print("init")
        Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
            self.seconds += 1
            if self.seconds == 60 {
                self.minutes += 1
                if self.minutes == 60 {
                    self.hours += 1
                }
            }
        }
    }
}

and this view:

import SwiftUI

struct ContentView: View {
    @State var model = ViewModel()
    var body: some View {
        VStack {
            Text("\(model.hours):\(model.minutes):\(model.seconds):")
        }
        .padding()
    }
}

When I run this on my Mac (macOS 14.4) it updates the text as the timer ticks.

The timer in this example runs 5 times faster than real time, just to make the effect obvious.

IMPORTANT I’ve taken a bunch of shortcuts here to produce a simple app. It’s not appropriate for a real app to manage time in this way. I’d be happy to discuss this in more depth.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks for answering. Indeed, it is working as it should. However, it does not work when it is closer to the actual method used as shown below, is this the wrong way to do it?

  • @Observable class ViewModel { static let shared = ViewModel() var hours: Int = 0 var minutes: Int = 0 var seconds: Int = 0

    func testTimer() { print("init") Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in self.seconds += 1 if self.seconds == 60 { self.minutes += 1 if self.minutes == 60 { self.hours += 1 } } } } }

  • Thanks! It seems there was a problem with the timer and the content of the part that outputs the numbers after all. Until now, the problem was hidden by @Published and I didn't seem to notice it.

Add a Comment

Replies

I’m not sure what’s going on here but there’s no magic required to make updates from an @Observable object update the SwiftUI state. Consider this version of your view model:

import Foundation
import Observation

@Observable
class ViewModel {
    var hours: Int = 0
    var minutes: Int = 0
    var seconds: Int = 0
    
    init() {
        print("init")
        Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
            self.seconds += 1
            if self.seconds == 60 {
                self.minutes += 1
                if self.minutes == 60 {
                    self.hours += 1
                }
            }
        }
    }
}

and this view:

import SwiftUI

struct ContentView: View {
    @State var model = ViewModel()
    var body: some View {
        VStack {
            Text("\(model.hours):\(model.minutes):\(model.seconds):")
        }
        .padding()
    }
}

When I run this on my Mac (macOS 14.4) it updates the text as the timer ticks.

The timer in this example runs 5 times faster than real time, just to make the effect obvious.

IMPORTANT I’ve taken a bunch of shortcuts here to produce a simple app. It’s not appropriate for a real app to manage time in this way. I’d be happy to discuss this in more depth.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks for answering. Indeed, it is working as it should. However, it does not work when it is closer to the actual method used as shown below, is this the wrong way to do it?

  • @Observable class ViewModel { static let shared = ViewModel() var hours: Int = 0 var minutes: Int = 0 var seconds: Int = 0

    func testTimer() { print("init") Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in self.seconds += 1 if self.seconds == 60 { self.minutes += 1 if self.minutes == 60 { self.hours += 1 } } } } }

  • Thanks! It seems there was a problem with the timer and the content of the part that outputs the numbers after all. Until now, the problem was hidden by @Published and I didn't seem to notice it.

Add a Comment

Thanks for answering. Indeed, it is working as it should. However, it does not work when it is closer to the actual method used as shown below, is this the wrong way to do it?

@Observable
class ViewModel {
    static let shared = ViewModel()
    var hours: Int = 0
    var minutes: Int = 0
    var seconds: Int = 0
    
    
func testTimer() {
        print("init")
        Timer.scheduledTimer(withTimeInterval: 0.2, repeats: true) { _ in
            self.seconds += 1
            if self.seconds == 60 {
                self.minutes += 1
                if self.minutes == 60 {
                    self.hours += 1
                }
            }
        }
    }
}
struct ContentView: View {
    let viewmodel = ViewModel.shared
    @State var model = ViewModel()
    var body: some View {
        VStack {
            Text("\(model.hours):\(model.minutes):\(model.seconds):")
            Button {
                viewmodel.testTimer()
            } label: {
                Text("Timer Start")
            }
        }
    }
}

The problem with your code is this:

let viewmodel = ViewModel.shared
@State var model = ViewModel()

You now have two instances of the ViewModel class, one in viewmodel and one in model. Your Text view is showing values from the model instance and your Button is starting the timer on the viewmodel instance. So, the timer updates the viewmodel instance properties, but the views don’t care about those.

To get your code to work:

  • Get rid of the shared static property.

  • Get rid of the viewmodel property.

  • Change the button to call testTimer() on model.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

  • Thanks! It seems there was a problem with the timer and the content of the part that outputs the numbers after all. Until now, the problem was hidden by @Published and I didn't seem to notice it.

Add a Comment