Обновления SwiftUI State в игровой площадке Xcode 11 - PullRequest
1 голос
/ 21 января 2020

Мне не удалось найти что-либо через стандартный поиск Google, но есть ли причина, по которой ContentView не обновляется через ObservableObject? Чувствую, что я что-то упускаю, но я не совсем уверен, что.

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

Ответы [ 2 ]

1 голос
/ 21 января 2020

Причина в том, что основанный на GCD таймер работает в собственной очереди, поэтому здесь исправление - модель представления должна обновляться в основной, пользовательском интерфейсе и очереди, как показано ниже

DispatchQueue.main.async {
    viewData.timeRemaining = timeRemaning
}
0 голосов
/ 24 января 2020

Основная утилита таймеров GCD по сравнению со стандартными таймерами заключается в том, что они могут работать в фоновой очереди. Как сказал Аспери, вы можете отправлять обновления в основную очередь, если ваш таймер GCD сам не использует основную очередь.

Но вы также можете просто запланировать таймер GCD в главной очереди из get go, и тогда вам вообще не нужно вручную отправлять в главную очередь:

let timer = DispatchSource.makeTimerSource(queue: .main)

Но, если вы собираетесь запустить это в основном потоке, вы можете просто использовать Timer, или, в проектах SwiftUI, вы можете предпочесть Combine TimerPublisher :

import Combine

...

var timer: AnyCancellable? = nil
timer = Timer.publish(every: 1, on: .main, in: .common)
    .autoconnect()
    .sink { _ in
        let remaining = start.addingTimeInterval(seconds).timeIntervalSince(Date())

        guard remaining >= 0 else {
            viewData.timeRemaining = "done!"
            timer?.cancel()
            return
        }

        let mins = Int(remaining / 60.0)
        let secs = Int(remaining) % 60

        viewData.timeRemaining = timeRemaining(minutes: mins, seconds: secs)
}

Когда вы включаете свой таймер в свой код SwiftUI (а не глобальный, как здесь), приятно оставаться в парадигме Combine Publisher.

Я также думаю, что это возможно, лучше отменить таймер, когда он истекает в обработчике таймера, вместо того, чтобы делать отдельный asyncAfter.


Не связанный, но вы можете рассмотреть возможность использования DateComponentsFormatter в вашей функции timeRemaining, например :

let formatter: DateComponentsFormatter = {
    let formatter = DateComponentsFormatter()
    formatter.allowedUnits = [.minute, .second]
    formatter.unitsStyle = .full
    return formatter
}()

func timeRemaining(_ timeInterval: TimeInterval) -> String {
    formatter.string(from: timeInterval) ?? "Error"
}

Тогда

  • Это избавит вас от необходимости вычислять минуты и секунды самостоятельно;
  • Строка будет локализована; и
  • Это обеспечит грамматически правильную формулировку; например, когда осталось 61 секунда, и существующая процедура выдаст грамматически неверное сообщение «1 минута 1 секунда».

DateComponentsFormatter избавляет вас от трудностей, связанных с такими крайними случаями, когда вы хотите использовать единственное число вместо множественного числа или языки, отличные от Engli sh.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...