【问题标题】:Combine: update values each other合并:相互更新值
【发布时间】:2022-01-03 10:01:00
【问题描述】:

这里的示例代码非常简单。滑块更新双精度值,但不是相反。使用 Combine 如何更新两个或多个滑块?

struct Centimeters {
    var value: Double
    
    func updateInches() -> Double {
        return value / 2.54
    }
}

struct Inches {
    var value: Double
    
    func updateCentimeters() -> Double {
        return value * 2.54
    }
}

class SizeValueModel: ObservableObject {
    @Published var centimeters: Centimeters
    @Published var inches: Inches
    var cancellables = Set<AnyCancellable>()
    
    init() {
        self.centimeters = Centimeters(value: 1.0)
        self.inches = Inches(value: 0.393701)
        
        $centimeters.sink {
            self.inches.value = $0.updateInches()
        }.store(in: &cancellables)
        
//        $inches.sink {
//            self.centimeters.value = $0.updateCentimeters()
//        }.store(in: &cancellables)
    }
}

struct ContentView: View {
    @StateObject var model = SizeValueModel()
    var body: some View {
        Slider(value: $model.centimeters.value, in: 0...100, label: {
            Text("\(model.centimeters.value)")
        })
        Slider(value: $model.inches.value, in: 0...39.3701, label: {
            Text("\(model.inches.value)")
        })
    }
}

【问题讨论】:

标签: swift swiftui combine


【解决方案1】:

正如您在尝试添加当前注释的第二个 sink 时所看到的,您最终会得到英寸和厘米之间的循环依赖关系。相反,我建议您存储 one 值并为另一个使用自定义绑定:

struct Centimeters {
    var value: Double
}

class SizeValueModel: ObservableObject {
    @Published var centimeters: Centimeters
    
    var inchesBinding : Binding<Double> {
        .init {
            self.centimeters.value / 2.54
        } set: {
            self.centimeters.value = $0 * 2.54
        }

    }
    
    init() {
        self.centimeters = Centimeters(value: 1.0)
    }
}

struct ContentView: View {
    @StateObject var model = SizeValueModel()
    var body: some View {
        Slider(value: $model.centimeters.value, in: 0...100, label: {
            Text("\(model.centimeters.value)")
        })
        Slider(value: model.inchesBinding, in: 0...39.3701, label: {
            Text("\(model.inchesBinding.wrappedValue)")
        })
    }
}

【讨论】:

  • 是的,循环依赖是问题所在。 With Binding 是一个避免使用Combine 的简单解决方案。谢谢!
【解决方案2】:

我猜你的问题是你创建了一个无限循环,因为你观察到两个相互改变的值。

  1. 英寸变化 -> 厘米更新
  2. 厘米变化 -> 英寸得到更新
  3. 英寸变化 -> 厘米更新
  4. 厘米变化 -> 英寸得到更新
  5. ...等等

@jnpdx 答案是正确的答案恕我直言(它允许您拥有单一的事实来源)

或者(可能对其他用例有用),您可以检查值是否实际更改,以避免触发无用的更新。

首先让你的结构符合Equatable

struct Centimeters: Equatable {
    // ...
}

struct Inches: Equatable {
    // ...
}

然后,在您的视图模型中,对发布者应用一个修饰符,以避免在值未实际更改时触发事件。

$centimeters
    .removeDuplicates()
    .sink {
        self.inches.value = $0.updateInches()
    }.store(in: &cancellables)
$inches
    .removeDuplicates()
    .sink {
        self.centimeters.value = $0.updateCentimeters()
    }.store(in: &cancellables)

【讨论】:

  • 感谢您抽出宝贵时间。是的,正如你写的@jnpdx 答案,只有一个事实来源,避免使用Combine。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-11-23
  • 1970-01-01
  • 2014-09-18
  • 1970-01-01
  • 2020-04-18
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多