Search code examples
iosswiftuigrand-central-dispatchswift-playgroundxcode11

SwiftUI State updates in Xcode 11 Playground


I have not been able to find anything through the standard Google search, but is there any reason why the ContentView is not updating through the ObservableObject? Feel like I am missing something but I am not quite sure what.

import SwiftUI
import PlaygroundSupport

let start = Date()
let seconds = 10.0 * 60.0

func timeRemaining(minutes: Int, seconds: Int) -> String {
    return "\(minutes) minutes \(seconds) seconds"
}

class ViewData : ObservableObject {
    @Published var timeRemaining: String = "Loading..."
}

// View

struct ContentView: View {
    @ObservedObject var viewData: ViewData = ViewData()

    var body: some View {
        VStack {
            Text(viewData.timeRemaining)
        }
    }
}


let contentView = ContentView()
let viewData = contentView.viewData
let hosting = UIHostingController(rootView: contentView)


// Timer

let timer = DispatchSource.makeTimerSource()
timer.schedule(deadline: .now(), repeating: .seconds(1))
timer.setEventHandler {
    let diff = -start.timeIntervalSinceNow
    let remaining = seconds - diff
    let mins = Int(remaining / 60.0)
    let secs = Int(remaining) % 60
    let timeRemaning = timeRemaining(minutes: mins, seconds: secs)
    viewData.timeRemaining = timeRemaning
    print(timeRemaning)
}
timer.resume()

DispatchQueue.main.asyncAfter(deadline: .now() + seconds) {
    timer.cancel()
    PlaygroundPage.current.finishExecution()
}

PlaygroundPage.current.setLiveView(contentView)
PlaygroundPage.current.needsIndefiniteExecution = true


Solution

  • The reason is that GCD based timer works on own queue, so here is the fix - view model have to be updated on main, UI, queue as below

    DispatchQueue.main.async {
        viewData.timeRemaining = timeRemaning
    }