合并:相互更新值

Combine: update values each other

这里的示例代码非常简单。滑块更新 double 值,但反之则不然。使用 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 = [=10=].updateInches()
        }.store(in: &cancellables)
        
//        $inches.sink {
//            self.centimeters.value = [=10=].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)")
        })
    }
}

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

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 = [=10=] * 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)")
        })
    }
}

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

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

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

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

首先让你的结构符合Equatable

struct Centimeters: Equatable {
    // ...
}

struct Inches: Equatable {
    // ...
}

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

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