Каков наиболее краткий способ отображения изменяющегося значения с помощью Combine и SwiftUI? - PullRequest
2 голосов
/ 26 октября 2019

Я пытаюсь обернуть голову вокруг SwiftUI и Combine. Я хочу сохранить некоторый текст в пользовательском интерфейсе со значением. В данном случае это уровень заряда батареи устройства, например.

Вот мой код. Прежде всего, кажется, что это довольно много кода для достижения того, что я хочу сделать, поэтому мне интересно, смогу ли я обойтись без этого. Кроме того, этот код использовался в течение лета, но теперь он дает сбой, вероятно, из-за изменений в SwiftUI и Combine.

Как это можно исправить для работы с текущей версией SwiftUI и Combine? И можно ли сократить количество кода здесь, чтобы сделать то же самое?

import SwiftUI
import Combine

class ViewModel: ObservableObject {

    var willChange = PassthroughSubject<Void, Never>()

    var batteryLevelPublisher = UIDevice.current
        .publisher(for: \.batteryLevel)
        .receive(on: RunLoop.main)

    lazy var batteryLevelSubscriber = Subscribers.Assign(object: self,
                                                         keyPath: \.batteryLevel)

    var batteryLevel: Float = UIDevice.current.batteryLevel {
        didSet {
            willChange.send()
        }
    }

    init() {
        batteryLevelPublisher.subscribe(batteryLevelSubscriber)
    }
}

struct ContentView: View {

    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        Text("\(Int(round(viewModel.batteryLevel * 100)))%")
    }
}

1 Ответ

5 голосов
/ 27 октября 2019

Минимальный рабочий пример для вставки на площадку iPadOS Swift.

По сути, это код выдачи, соответствующий последним изменениям из SwiftUI и Combine.

  • используйте @Оболочка опубликованного свойства для любых свойств, которые вы хотите наблюдать в своем представлении (Документы: по умолчанию ObservableObject синтезирует издателя objectWillChange, который выдает измененное значение до изменения любого из своих свойств @Published.). Это позволяет избежать использования пользовательских сеттеров и objectWillChange.

  • , отменяемый - это выходной файл Publishers.Assign, его можно использовать для отмены подписки вручную и для лучшей практики он будет сохраненв «CancellableBag», таким образом, отмените подписку на deinit. Эта практика вдохновлена ​​другими реактивными средами, такими как RxSwift и ReactiveUI.

  • Я обнаружил, что без включения уведомлений об уровне заряда батареи издатель KVO для уровня заряда батареи будет излучать только один раз -1,0.

// iPadOS playground
import SwiftUI
import Combine
import PlaygroundSupport

class BatteryModel : ObservableObject {
    @Published var level = UIDevice.current.batteryLevel
    private var cancellableSet: Set<AnyCancellable> = []

    init () {
        UIDevice.current.isBatteryMonitoringEnabled = true
        assignLevelPublisher()
    }

    private func assignLevelPublisher() {
        _ = UIDevice.current
            .publisher(for: \.batteryLevel)
            .assign(to: \.level, on: self)
            .store(in: &self.cancellableSet)
    }
}
struct ContentView: View {

    @ObservedObject var batteryModel = BatteryModel()

    var body: some View {
        Text("\(Int(round(batteryModel.level * 100)))%")
    }
}

let host = UIHostingController(rootView: ContentView())
PlaygroundPage.current.liveView = host

...