SwiftUI与Combine问题:无法获取含前后值的Publisher流
问题分析
你的代码中前值始终为nil的核心原因是:每次视图因count变化而刷新时,publisher都会被重新创建。Just(count)只会发送当前count的单次值,且scan的初始状态每次都重置为nil,导致每次新生成的流都无法累积之前的状态,因此previous永远是nil。
兼容iOS13的解决方案
要解决这个问题,需要让监听的流持续存在且能累积状态,最合适的方式是将count封装到ObservableObject中,通过@Published属性提供持续的数据流,避免视图刷新时重建整个流。
步骤1:创建ViewModel
import Combine import SwiftUI class CounterViewModel: ObservableObject { @Published var count: Int = 0 }
步骤2:重构ContentView
struct ContentView: View { // iOS13需使用@ObservedObject,iOS14+可替换为@StateObject @ObservedObject private var viewModel = CounterViewModel() @State private var previous: Int? @State private var current: Int? var body: some View { VStack { Button("Increment") { viewModel.count += 1 } Text("Previous: \(previous?.description ?? "nil")") Text("Current: \(current?.description ?? "nil")") } .onReceive(publisher) { prev, curr in self.previous = prev self.current = curr } } private var publisher: AnyPublisher<(previous: Int?, current: Int), Never> { viewModel.$count // 初始状态设为(nil, 初始count),确保第一次能获取初始值 .scan((previous: Int?, current: Int)?(nil, viewModel.count)) { lastState, newCount in // 用上次的current作为本次的previous,新值作为current (lastState?.current, newCount) } .compactMap { $0 } .eraseToAnyPublisher() } }
方案说明
ObservableObject+@Published:viewModel.$count是一个持续发送count变化的Publisher,不会因视图刷新而重建,确保scan能持续累积状态。scan的初始值:设置为(nil, viewModel.count),第一次流触发时就能正确输出初始的previous: nil和current: 0。- iOS13兼容:使用
@ObservedObject而非@StateObject(后者是iOS14+引入),保证在iOS13上正常运行。
备选方案(手动管理Subject)
如果不想使用ObservableObject,也可以用CurrentValueSubject手动包装count的变化,但需要手动发送值,代码如下:
struct ContentView: View { @State private var count: Int = 0 @State private var countSubject = CurrentValueSubject<Int, Never>(0) @State private var previous: Int? @State private var current: Int? var body: some View { VStack { Button("Increment") { count += 1 countSubject.send(count) } Text("Previous: \(previous?.description ?? "nil")") Text("Current: \(current?.description ?? "nil")") } .onReceive(publisher) { prev, curr in previous = prev current = curr } .onAppear { countSubject.send(count) } } private var publisher: AnyPublisher<(previous: Int?, current: Int), Never> { countSubject .scan((previous: Int?, current: Int)?(nil, count)) { last, new in (last?.current, new) } .compactMap { $0 } .eraseToAnyPublisher() } }
内容的提问来源于stack exchange,提问作者Oliver Pearmain
相关产品推荐
相关产品推荐

