4

在这个问题被标记为另一个问题的重复之前,我试图了解发布者是如何以我不期望的方式工作的。

使用与先前所述问题的答案相同的示例:

// 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()
        }
    }
4

3 回答 3

7

这与您的原始版本有点不同,但我希望没有任何重要的改变。

import Combine
import SwiftUI

class TimerViewModel: ObservableObject {
    private var assignCancellable: AnyCancellable? = nil

    @Published var tick: String = "0:0:0"

    init() {
        assignCancellable = Timer.publish(every: 1.0, on: .main, in: .default)
            .autoconnect()
            .map { String(describing: $0) }
            .assign(to: \TimerViewModel.tick, on: self)
    }
}


struct ContentView: View {
    @State private var currentTime: String = "Initial"
    @ObservedObject var viewModel = TimerViewModel()

    var body: some View {
        VStack {
            Text(currentTime)
            Text(viewModel.tick) // why doesn't this work?
        }
        .onReceive(Timer.publish(every: 0.9, on: .main, in: .default).autoconnect(),
                perform: {
                    self.currentTime = String(describing: $0)
                }
        )
    }
}

我将 viewModel 设为 ObservedObject 只是为了简化代码。

Timer.publish 方法与 autoconnect 一起使 Timer 更易于使用。我发现使用具有多个订阅者的同一个发布者会导致问题,因为第一次取消会杀死发布者。

我删除了 deinit() 因为取消对订阅者来说似乎是隐含的。

来自 onReceive 和 viewModel 的更新之间存在干扰,但将 onReceive 更改为 0.9 修复了该问题。

最后我发现,Combine 中的 print() 方法对于查看管道非常有用。

于 2019-09-05T13:48:43.327 回答
1

首先尝试Text("Timer: \(date, style:.timer)")完全自动地为您提供计数计时器。这也是一个很好的演示,可以向那些使用 SwiftUI 走错路的人展示,例如 MVVM 人群。

还要考虑它的优点Timer.publisher,它是一个可以存储的结构,@State所以你甚至不需要ObservableObject.

import Combine
import SwiftUI

struct Clock : View {
  @State private var timer = Timer.publish(every: 1, on: .main, in:.common).autoconnect()
  @State private var currentTime: String = "Initial"

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

Timer拥有as的好处@State是,如果由于某种原因不再显示时钟,那么计时器也会停止并被丢弃。

但是,如果您确实决定使用,ObservableObject那么您可以简单地执行以下操作:

class MyTimer : ObservableObject {
    var objectWillChange = Timer.publish(every: 1, on: .main, in:.common).autoconnect()
}

struct Clock2: View {
    @StateObject var timer = MyTimer() // causes body to run every second
    var body: some View {
        VStack {
            Text("Hello, World! \(Date())")
        }
    }
}

这是另一种对日期字符串进行更准确更改的方法:

class MyTimer : ObservableObject {
    var timer : Timer? = nil
    @Published var timeString = ""

    init() {
        timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { timer in
            self.timeString = timer.fireDate.description
        }
    }
}

struct ContentView: View {
    @StateObject var timer = MyTimer()

    var body: some View {
        VStack {
            Text("Hello, World! \(timer.timeString)")
        }
    }
}
于 2020-09-24T15:11:33.193 回答
0

“@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()
    }
}
于 2019-09-05T08:31:06.443 回答