如何将Alert SwiftUI反馈与分支的Combine Publisher链合并

时间:2019-12-18 05:35:20

标签: swift swiftui combine

我有以下情况,对于基于回调的代码pre-Combine / SwiftUI而言,这非常简单

  1. 以一个值(例如整数)开头
  2. 对该值运行验证例程。如果验证失败,请向用户显示UIAlert,以选择一个分辨率选项,该值将产生新的值。如果验证通过-该值保持不变
  3. 获取可能更改的值并传递到下一个验证回调

我认为这很自然地适合Combine框架。

我一直在努力寻找一个好的运营商组合,使我可以将结果从Combine中提取出来并进行检查,然后再决定要在没有订阅者和重新发布者的情况下接下来应用什么 的运营商在每个阶段。有条件地选择操作以及从Future合并类似Alert的结果的方法对我来说并不明显。

我有进行这种工作的代码-它遵循上述概念并带有整数值。但是它是具有多个发布者/主题的庞然大物-我不愿意向其他人解释此代码。还有一个问题,我如何在Alert(即title)的表示中使用该值,而又不将其取出并缓存(我请执行以下操作。

是否存在一种更清洁的方法来执行上述操作,而无需拆分/存储和重新发布相同的数据?当然不使用普通的旧回调-这可能是我在现实生活中的情况

import SwiftUI
import Combine

struct MultipleChainOfAlertsView: View {

    @State var showFirst = false
    @State var showSecond = false
    @State var showThird = false

    @State var initialValuePublisher: AnyPublisher<Int, Never>? = nil
    @State var previousValuePublisher: AnyPublisher<Int, Never>? = nil
    @State var currentValuePublisher: AnyPublisher<Int, Never>? = nil
    @State var future1Publisher = CurrentValueSubject<Int?, Never>(nil)
    @State var future2Publisher = CurrentValueSubject<Int?, Never>(nil)
    @State var future3Publisher = CurrentValueSubject<Int?, Never>(nil)

    @State var currentHandlerSubscriber: AnyCancellable? = nil
    @State var currentAlertSubscriber: AnyCancellable? = nil
    @State var finalSubscriber: AnyCancellable? = nil

    @State var value: Int = 0
    @State var finalValue: Int? = nil

    func beginSeq() {
        value = Int.random(in: 0...100)
        initialValuePublisher = Just<Int>(value).eraseToAnyPublisher()
        currentValuePublisher = initialValuePublisher

        let setupStep3 = {
            self.currentHandlerSubscriber?.cancel()
            self.currentHandlerSubscriber = self.currentValuePublisher?.receive(on: DispatchQueue.main).sink(receiveValue: { val in
                if val < 90 {
                    DispatchQueue.main.async {
                        self.showFirst = false
                        self.showSecond = false
                        self.showThird = true
                    }
                    self.previousValuePublisher = self.currentValuePublisher
                    self.currentValuePublisher = self.future3Publisher.compactMap { $0 }.eraseToAnyPublisher()
                    self.finalise()
                }
                else {
                    self.finalise()
                }
            })
        }

        let setupStep2 = {
            self.currentHandlerSubscriber?.cancel()
            self.currentHandlerSubscriber = self.currentValuePublisher?.receive(on: DispatchQueue.main).sink(receiveValue: { val in
                if val > 2 {
                    DispatchQueue.main.async {
                        self.showFirst = false
                        self.showSecond = true
                        self.showThird = false
                    }
                    self.previousValuePublisher = self.currentValuePublisher
                    self.currentValuePublisher = self.future2Publisher.compactMap { $0 }.eraseToAnyPublisher()
                    setupStep3()
                }
                else {
                    setupStep3()
                }
            })
        }

        self.currentHandlerSubscriber?.cancel()
        self.currentHandlerSubscriber = self.currentValuePublisher?.receive(on: DispatchQueue.main).sink(receiveValue: { val in
            if val % 2 == 0 {
                DispatchQueue.main.async {
                    self.showFirst = true
                    self.showSecond = false
                    self.showThird = false
                }
                self.previousValuePublisher = self.currentValuePublisher
                self.currentValuePublisher = self.future1Publisher.compactMap { $0 }.eraseToAnyPublisher()
                setupStep2()
            }
            else {
                setupStep2()
            }
        })
    }

    func finalise() {
        finalSubscriber = currentValuePublisher?.sink { val in
            self.finalValue = val

            // Reset everything
            self.currentHandlerSubscriber?.cancel()
            self.currentHandlerSubscriber = nil
            self.currentAlertSubscriber?.cancel()
            self.currentAlertSubscriber = nil
            self.finalSubscriber?.cancel()
            self.finalSubscriber = nil

            self.initialValuePublisher = nil
            self.previousValuePublisher = nil
            self.currentValuePublisher = nil
            self.future1Publisher.send(nil)
            self.future2Publisher.send(nil)
            self.future3Publisher.send(nil)
        }
    }

    var body: some View {
        return ZStack {
            Button(action: { self.beginSeq() }) {
                Text( finalValue != nil ? "Value is \(finalValue!)" : "Press to roll value")
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showFirst) {
                Alert(
                    title: Text("CB1"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 1")) {
                        self.showFirst = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.value = val + 1
                                self.future1Publisher.send(self.value)
                            }
                    },
                    secondaryButton: .default(Text("Make 0")) {
                        self.showFirst = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.value = 0
                                self.future1Publisher.send(self.value)
                            }
                    }
                )
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showSecond) {
                Alert(
                    title: Text("CB2"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 3")) {
                        self.showSecond = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.value = val + 3
                                self.future2Publisher.send(self.value)
                            }
                    },
                    secondaryButton: .default(Text("Mult 2")) {
                        self.showSecond = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.value = val * 2
                                self.future2Publisher.send(self.value)
                            }
                    }
                )
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showThird) {
                Alert(
                    title: Text("CB3"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 20")) {
                        self.showThird = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.value = val + 20
                                self.future3Publisher.send(self.value)
                            }
                    },
                    secondaryButton: .default(Text("Change Nothing")) {
                        // Nothing
                        self.showThird = false
                        self.currentAlertSubscriber = self.previousValuePublisher?
                            .sink { val in
                                self.future3Publisher.send(self.value)
                            }
                    }
                )
            }
        }
    }
}

代码上的一些警告

  • 警报的状态更改在main.async中完成,因为当两个布尔状态在同一渲染周期中处于活动状态时,警报将不会渲染
  • 不可见的矩形用于附加多个警报项-您不能在一个视图上附加多个

编辑: 我还重写了上面的代码以仅使用回调-无需合并。它更容易阅读,变量更少,并且可以正常工作。它非常静态-但这是其他地方简化的权衡。我肯定在这里想念什么-这就是Combine擅长的地方

struct TestingStructure {
    var originalValue: Int? = nil
    var currentValue: Int? = nil
    var checked1 = false
    var checked2 = false
    var checked3 = false
}

struct MultipleChainOfAlertsView: View {

    @State var showFirst = false
    @State var showSecond = false
    @State var showThird = false

    @State var testingStructure: TestingStructure? = nil

    var value: Int {
        testingStructure?.currentValue ?? 0
    }
    @State var finalValue: Int? = nil

    func beginSeq() {
        let value = Int.random(in: 0...100)

        let testingStructure = TestingStructure(originalValue: value, currentValue: value, checked1: false, checked2: false, checked3: false)

        testStructure(testingStructure)
    }

    func testStructure(_ testingStructure: TestingStructure) {
        var structure = testingStructure
        if let value = structure.currentValue
        {
            if structure.checked1 == false {
                if value % 2 == 0 {
                    showFirst = true
                    self.testingStructure = structure
                    return
                }
                else {
                    structure.checked1 = true
                }
            }

            if structure.checked2 == false {
                if value > 2 {
                    showSecond = true
                    self.testingStructure = structure
                    return
                }
                else {
                    structure.checked2 = true
                }
            }

            if structure.checked3 == false {
                if value < 90 {
                    showThird = true
                    self.testingStructure = structure
                    return
                }
                else {
                    structure.checked3 = true
                }
            }

            finalValue = structure.currentValue
        }
    }

    var body: some View {
        return ZStack {
            Button(action: { self.beginSeq() }) {
                Text( finalValue != nil ? "Value is \(finalValue!)" : "Press to roll value")
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showFirst) {
                Alert(
                    title: Text("CB1"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 1")) {
                        if var structure = self.testingStructure,
                            let value = structure.currentValue
                        {
                            structure.currentValue = value + 1
                            structure.checked1 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    },
                    secondaryButton: .default(Text("Make 0")) {
                        if var structure = self.testingStructure
                        {
                            structure.currentValue = 0
                            structure.checked1 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    }
                )
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showSecond) {
                Alert(
                    title: Text("CB2"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 3")) {
                        if var structure = self.testingStructure,
                            let value = structure.currentValue
                        {
                            structure.currentValue = value + 3
                            structure.checked2 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    },
                    secondaryButton: .default(Text("Mult 2")) {
                        if var structure = self.testingStructure,
                            let value = structure.currentValue
                        {
                            structure.currentValue = value * 2
                            structure.checked2 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    }
                )
            }
            Rectangle().frame(width: 0, height: 0)
            .alert(isPresented: $showThird) {
                Alert(
                    title: Text("CB3"),
                    message: Text("Value is \(value)"),
                    primaryButton: .default(Text("Add 20")) {
                        if var structure = self.testingStructure,
                            let value = structure.currentValue
                        {
                            structure.currentValue = value + 20
                            structure.checked3 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    },
                    secondaryButton: .default(Text("Change Nothing")) {
                        if var structure = self.testingStructure
                        {
                            structure.checked3 = true
                            DispatchQueue.main.async{ self.testStructure(structure) }
                        }
                    }
                )
            }
        }
    }
}

0 个答案:

没有答案