如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?

Posted

技术标签:

【中文标题】如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?【英文标题】:How to use Combine's CurrentValueSubject and access it in a SwiftUI View? 【发布时间】:2020-07-22 12:38:15 【问题描述】:

我的理解是,Combine 中的 CurrentValueSubject 发布者适合按需访问,而不是发出一次值的常规发布者。因此,我尝试在环境对象中使用一个来存储 HKWorkout 中燃烧的总能量,以便在 SwiftUI 视图中完成锻炼后访问它。使用下面的代码,我得到了编译器错误Cannot convert return expression of type 'AnyCancellable' to return type 'Double',所以我认为我需要进行某种类型的转换但无法弄清楚?

class WorkoutManager: NSObject, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate, ObservableObject  
    
    var finishedWorkoutTotalEnergyBurned = CurrentValueSubject<Double, Never>(0.0)
    
    func stopWorkout() 
        self.finishedWorkoutTotalEnergyBurned.value = unwrappedWorkout.totalEnergyBurned!.doubleValue(for: .kilocalorie())
    


struct SummaryView: View 

    @StateObject var workoutManager = WorkoutManager()
    
    var body: some View 
        Text("\(getFinishedWorkoutTotalEnergyBurned())")
            .navigationBarHidden(true)
        //.navigationTitle("Workout Complete")
    
    
    func getFinishedWorkoutTotalEnergyBurned() -> Double 
        workoutManager.finishedWorkoutTotalEnergyBurned.sink(receiveValue:  $0 )
    

【问题讨论】:

【参考方案1】:

我认为您的问题表明您需要更多地了解 SwiftUI 和 Combine 背后的基本原理(关于“CurrentValueSubject vs regular Publisher”的说法是完全错误的)。

您只需在您的WorkoutManager 上公开一个@Published 属性,并在需要时将其设置为您想要的值:

class WorkoutManager: NSObject, HKWorkoutSessionDelegate, HKLiveWorkoutBuilderDelegate, ObservableObject  
    
   @Published var finishedWorkoutTotalEnergyBurned = 0.0
    
   func stopWorkout() 
      finishedWorkoutTotalEnergyBurned = unwrappedWorkout.totalEnergyBurned!.doubleValue(for: .kilocalorie())
   

struct SummaryView: View 

  @StateObject var workoutManager = WorkoutManager()
    
  var body: some View 
     Text("\(workoutManager.finishedWorkoutTotalEnergyBurned)")
        .navigationBarHidden(true)
  

@Published 确实在后台使用了 Combine 发布者,它与 @StateObject 结合使用会导致视图更新。但是您需要在这里推理的是如何以及何时设置这些属性 - 并且视图将自动更新。在您展示的情况下,可能不需要直接使用发布者。

【讨论】:

我也应该说的是,锻炼实际上是在此之前的视图中完成的。这就是我所说的发布者触发一次(在创建 SummaryView 之前),所以我需要能够存储 finishedWorkoutTotalEnergyBurned 以便在完成锻炼并导航到 SummeryView 后可以访问它,这就是为什么我认为 CurrentValueSubject 可能工作。 如果完成,将值存储在某处(例如视图模型),然后直接显示 - 不需要发布者 对我的锻炼管理器,我认为是我的视图模型,所以我试图将它存储在那里并读取它 @GarySabo - 这听起来像是正确的整体方法。从你的问题中很难知道这些对象的生命周期是什么,所以听起来WorkoutManager 可能不是SummaryView“拥有”的,所以它不是SummaryViewStateObject正确的方法。无论如何,在您确定这是您需要的并且根据您目前的使用情况,它不是之前,不要跳到组合发布者 我想通了,你是对的,SummaryView 没有通过带有数据的 WorkoutManager 的实例。我从@StateObject 更改为@EnvironmentObject private var workoutManager: WorkoutManager 并通过SummaryView().environmentObject(workoutManager) 在父视图中设置它,现在我可以从锻炼管理器中读取数据。 ?

以上是关于如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?

如何使用 combine Publisher 更改线程?

如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?

如何使用 Combine + Swift 复制 PromiseKit 风格的链式异步流

使用 SwiftUI/Combine,如何避免在 ViewModel 中放置可取消项

如何修复使用 combine 解析 json 期间描述的错误?