我目前正在使用Combine和SwiftUI,并已使用MVVM模式构建了一个原型应用程序。该应用程序利用计时器,并通过PassThroughSubject(不确定地)将控制该按钮的按钮状态绑定到视图模型。
按下按钮时,应切换状态变量的值;此值传递给视图模型的主题(使用.send),该主题应在每次按钮按下时发送一个事件。但是,似乎发生了递归或奇怪的事情,因为向该主题发送了多个事件,并且在没有更新UI的情况下导致了运行时崩溃。
这有点令人困惑,我不确定这是否是Combine中的错误,还是我错过了什么。任何指针将不胜感激。下面的代码-我知道它很乱;-)我已将其修整为看起来相关的内容,但是如果您需要更多,请告诉我。
查看:
struct ControlPanelView : View {
@State private var isTimerRunning = false
@ObjectBinding var viewModel: ControlPanelViewModel
var body: some View {
HStack {
Text("Case ID") // replace with binding to viewmode
Spacer()
Text("00:00:00") // repalce with binding to viewmodel
Button(action: {
self.isTimerRunning.toggle()
self.viewModel.apply(.isTimerRunning(self.isTimerRunning))
print("Button press")
}) {
isTimerRunning ? Image(systemName: "stop") : Image(systemName: "play")
}
}
// .onAppear(perform: { self.viewModel.apply(.isTimerRunning(self.isTimerRunning)) })
.font(.title)
.padding(EdgeInsets(top: 0, leading: 32, bottom: 0, trailing: 32))
}
}
Viewmodel:
final class ControlPanelViewModel: BindableObject, UnidirectionalDataType {
typealias InputType = Input
typealias OutputType = Output
private let didChangeSubject = PassthroughSubject<Void, Never>()
private var cancellables: [AnyCancellable] = []
let didChange: AnyPublisher<Void, Never>
// MARK:- Input
...
private let isTimerRunningSubject = PassthroughSubject<Bool, Never>()
....
enum Input {
...
case isTimerRunning(Bool)
...
}
func apply(_ input: Input) {
switch input {
...
case .isTimerRunning(let state): isTimerRunningSubject.send(state)
...
}
}
// MARK:- Output
struct Output {
var isTimerRunning = false
var elapsedTime = TimeInterval(0)
var concernId = ""
}
private(set) var output = Output() {
didSet { didChangeSubject.send() }
}
// MARK:- Lifecycle
init(timerService: TimerService = TimerService()) {
self.timerService = timerService
didChange = didChangeSubject.eraseToAnyPublisher()
bindInput()
bindOutput()
}
private func bindInput() {
utilities.debugSubject(subject: isTimerRunningSubject)
let timerToggleStream = isTimerRunningSubject
.subscribe(isTimerRunningSubject)
...
cancellables += [
timerToggleStream,
elapsedTimeStream,
concernIdStream
]
}
private func bindOutput() {
let timerToggleStream = isTimerRunningSubject
.assign(to: \.output.isTimerRunning, on: self)
...
cancellables += [
timerToggleStream,
elapsedTimeStream,
idStream
]
}
}
答案 0 :(得分:0)
在您的bindInput
方法中,isTimerRunningSubject
进行了订阅。我怀疑这不是您想要的,并且可能解释了您正在描述的怪异递归。也许您在某处缺少self.
?
奇怪的是,bindInput
和bindOutput
都将所有流添加到cancellables
数组中,因此它们将在其中两次。
希望这会有所帮助。
答案 1 :(得分:0)
此示例按预期工作,但是我在此过程中发现,无法在带有@Published的原始代码(内部结构定义输入和输出)中使用该模式。这会导致一些非常奇怪的错误(以及Playground中的BAD_ACCESS),并且是Combine beta 3中报告的错误。
final class ViewModel: BindableObject {
var didChange = PassthroughSubject<Void, Never>()
@Published var isEnabled = false
private var cancelled = [AnyCancellable]()
init() {
bind()
}
private func bind() {
let t = $isEnabled
.map { _ in }
.eraseToAnyPublisher()
.subscribe(didChange)
cancelled += [t]
}
}
struct ContentView : View {
@ObjectBinding var viewModel = ViewModel()
var body: some View {
HStack {
viewModel.isEnabled ? Text("Button ENABLED") : Text("Button disabled")
Spacer()
Toggle(isOn: $viewModel.isEnabled, label: { Text("Enable") })
}
.padding()
}
}