如何在SwiftUI视图上使用Combine

iOS*_*com 4 ios swift swiftui

这个问题与此有关:如何使用SwiftUI和Combine观察TextField的值?

但是我要问的是比较笼统的。这是我的代码:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

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

所以我的问题是:

  1. 将合并操作放在下面的代码中的适当位置在哪里。
  2. 我要完成什么组合代码:(A)字符串不能包含空格。(B)字符串必须为5个字符长。(C)弦必须去抖动/掉线

我想在这里使用响应作为一种通用模式:我们应该如何处理SwiftUI应用程序(而非UIKit应用程序)中的组合内容。

sup*_*cio 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)
    }
}
Run Code Online (Sandbox Code Playgroud)

您应该将自己的字符串附加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)")
    }
}
Run Code Online (Sandbox Code Playgroud)