この質問が この他の質問 の重複としてマークされる前に、パブリッシャーが予期しない動作をするときの動作を理解しようとしています。
前述の質問の回答と同じ例を使用します。
// 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()
}
}
"@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()
}
}