web-dev-qa-db-ja.com

Swift Combine:監視可能なオブジェクトでのタイマーパブリッシャーの使用

この質問が この他の質問 の重複としてマークされる前に、パブリッシャーが予期しない動作をするときの動作を理解しようとしています。

前述の質問の回答と同じ例を使用します。

// Let's define the view model with my view...
import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
  private let cancellable: AnyCancellable?

  let intervalPublisher = Timer.TimerPublisher(
                            interval: 1.0, 
                            runLoop: .main, 
                            mode: .default)

  init() {
    self.cancellable = timerPublisher.connect() as? AnyCancellable
  }

  deinit {
    self.cancellable?.cancel()
  }
}

struct Clock : View {
  @EnvironmentObject var viewModel: TimerViewModel
  @State private var currentTime: String = "Initial"


  var body: some View {
    VStack {
      Text(currentTime)
    }
    .onReceive(timer.intervalPublisher) { newTime in
      self.currentTime = String(describing: newTime)
    }
  }
}

この段階でやりたかったのは、値を直接公開するためのビューモデルだけです。ビューがこれらの種類の値を受け取ることを宣言する必要はありません。

理想的には、パブリッシャーを適切にパブリッシュしたいのですが、次のコードが機能するはずです。

// Let's define the view model with my view...
import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
  private let cancellable: AnyCancellable?
  private let assignCancellable: AnyCancellable?

  let intervalPublisher = Timer.TimerPublisher(
                            interval: 1.0, 
                            runLoop: .main, 
                            mode: .default)
 @Published var tick: String = "0:0:0"

  init() {
    cancellable = intervalPublisher.connect() as? AnyCancellable

    assignCancellable = intervalPublisher
                              .map { new in String(describing: new) }
                              .assign(to: \TimerViewModel.tick, on: self)
  }

  deinit {
    cancellable?.cancel()
    assignCancellable?.cancel()
  }
}

struct Clock : View {
  @EnvironmentObject var viewModel: TimerViewModel
  @State private var currentTime: String = "Initial"


  var body: some View {
    VStack {
      Text(currentTime)
      Text(viewModel.tick) // why doesn't this work?
    }
    .onReceive(timer.intervalPublisher) { newTime in
      self.currentTime = String(describing: newTime)
    }
  }
}

assignの何が問題になっていますか?

なぜトリガーされないのですか?

編集:クロックビューが作成されると、環境オブジェクトがSceneDelegateに設定されました。除外されたコードは以下に添付されています:

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
        // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
        // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

        // Create the SwiftUI view that provides the window contents.
        let view = Clock().environmentObject(TimerViewModel())

        // Use a UIHostingController as window root view controller.
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: view)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
2
rodrigoelp

"@EnvironmentObject"は、祖先ビューにモデルオブジェクトを設定する必要があります。

私はこれを見ませんでした。

だから、私はあなたのコードを書き直しました。

import SwiftUI
import Combine

struct ContentView: View {
    let timer = TimerViewModel()
    var body: some View {
        VStack {
            Text("Hello World")
            TimerView().environmentObject(timer)
        }
    }
}

struct TimerView: View {
    @EnvironmentObject var timer: TimerViewModel

    var body: some View {
        Text(timer.time)
    }
}

class TimerViewModel: ObservableObject {
    @Published var time = "init"

    private let innerTimer = Timer.TimerPublisher(interval: 1.0, runLoop: .main, mode: .default)
    private let cancellable: Cancellable
    private var anyCancellable: AnyCancellable?

    init() {
        cancellable = innerTimer.connect()
        anyCancellable = innerTimer
            .map({ $0.description })
            .assign(to: \TimerViewModel.time, on: self)
    }

    deinit {
        cancellable.cancel()
        anyCancellable?.cancel()
    }
}
0
adf