如何在SwiftUI视图上使用Combine

时间:2019-09-13 11:25:35

标签: ios swift swiftui

这个问题与此有关:How to observe a TextField value with SwiftUI and Combine?

但是我要问的有点笼统。 这是我的代码:

struct MyPropertyStruct {
    var text: String
}

class TestModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: "initialText")

    func saveTextToFile(text: String) {
        print("this function saves text to file")
    }
}

struct ContentView: View {
    @ObservedObject var testModel = TestModel()
    var body: some View {
        TextField("", text: $testModel.myproperty.text)
    }
}

方案::当用户在文本字段中键入内容时,应调用 saveTextToFile 函数。由于这是保存到文件,因此应放慢/限制它。

所以我的问题是:

  1. 将合并操作放在下面的代码中的适当位置在哪里。
  2. 我要完成的Combine代码是什么:(A)字符串不能包含空格。 (B)字符串必须为5个字符长。 (C)必须对字符串进行反跳/下沉

我想在此处将响应用作以下常规模式:我们应如何处理SwiftUI应用(而不是UIKit应用)中的合并内容。

1 个答案:

答案 0 :(得分:7)

您应该在ViewModel中进行所需的操作。您的视图模型是TestModel类(我建议您在TestViewModel中对其重命名)。在这里应该将逻辑放在模型和视图之间。 ViewModel应该准备好模型以便进行可视化。这是放置合并逻辑的正确位置(当然,如果它与视图有关)。

现在,我们可以使用您的特定示例来实际举例。老实说,根据您确实想要实现的目标,有几种稍微不同的解决方案。但是现在,我将尝试尽可能通用一些,然后您可以告诉我解决方案是否完善或需要一些改进:

struct MyPropertyStruct {
    var text: String
}

class TestViewModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: "initialText")
    private var canc: AnyCancellable!

    init() {
        canc = $myproperty.debounce(for: 0.5, scheduler: DispatchQueue.main).sink { [unowned self] newText in
            let strToSave = self.cleanText(text: newText.text)
            if strToSave != newText.text {
                //a cleaning has actually happened, so we must change our text to reflect the cleaning
                self.myproperty.text = strToSave
            }
            self.saveTextToFile(text: strToSave)
        }
    }

    deinit {
        canc.cancel()
    }

    private func cleanText(text: String) -> String {
        //remove all the spaces
        let resultStr = String(text.unicodeScalars.filter {
            $0 != " "
        })

        //take up to 5 characters
        return String(resultStr.prefix(5))
    }

    private func saveTextToFile(text: String) {
        print("text saved")
    }
}

struct ContentView: View {
    @ObservedObject var testModel = TestViewModel()

    var body: some View {
        TextField("", text: $testModel.myproperty.text)
    }
}

您应该将自己的subscriber附加到TextField publisher上,并使用debounce发布者来延迟字符串的清理和对save方法的调用。根据文档:

  

去抖动(for:scheduler:options:)

     

当您要等待传送的暂停时使用此运算符   来自上游发布者的事件。例如,呼叫在上进行反跳   来自文本字段的发布者,仅当用户   暂停或停止输入。当他们再次开始键入时,反跳   保持事件传递直到下一个暂停。

当用户停止键入时,防抖动发布者将等待指定的时间(在我的示例中为0.5秒以上),然后使用新值调用其订户。

上述解决方案会同时延迟保存字符串 TextField。这意味着在更新发生之前,用户会看到原始字符串(带有空格且可能超过5个字符的字符串)一段时间。这就是为什么在这个答案的开头,我说根据需要有几种不同的解决方案。如果确实确实要延迟字符串的保存,但是我们希望禁止用户输入空格字符或长度超过5个字符的字符串,则可以使用两个订阅者(我将仅发布更改的代码,即TestViewModel类):

class TestViewModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: "initialText")
    private var saveCanc: AnyCancellable!
    private var updateCanc: AnyCancellable!

    init() {
        saveCanc = $myproperty.debounce(for: 0.5, scheduler: DispatchQueue.main)
            .map { [unowned self] in self.cleanText(text: $0.text) }
            .sink { [unowned self] newText in
            self.saveTextToFile(text: self.cleanText(text: newText))
        }

        updateCanc = $myproperty.sink { [unowned self] newText in
            let strToSave = self.cleanText(text: newText.text)
            if strToSave != newText.text {
                //a cleaning has actually happened, so we must change our text to reflect the cleaning
                DispatchQueue.main.async {
                    self.myproperty.text = strToSave
                }
            }
        }
    }

    deinit {
        saveCanc.cancel()
        updateCanc.cancel()
    }

    private func cleanText(text: String) -> String {
        //remove all the spaces
        let resultStr = String(text.unicodeScalars.filter {
            $0 != " "
        })

        //take up to 5 characters
        return String(resultStr.prefix(5))
    }

    private func saveTextToFile(text: String) {
        print("text saved: \(text)")
    }
}