如何在 SwiftUI 视图上使用组合

Posted

技术标签:

【中文标题】如何在 SwiftUI 视图上使用组合【英文标题】:How to use Combine on a SwiftUI View 【发布时间】:2019-09-13 11:25:35 【问题描述】:

这个问题与这个问题有关: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 函数。由于这是保存到文件,因此应该减慢/节流。

所以我的问题是:

    在下面的代码中放置组合操作的正确位置在哪里。 我要使用什么组合代码来完成:(A) 字符串不能包含空格。 (B) 字符串长度必须为 5 个字符。 (C) 字符串必须去抖动/减速

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

【问题讨论】:

【参考方案1】:

你应该在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 发布者来延迟字符串的清理和对保存方法的调用。根据文档:

去抖动(for:scheduler:options:)

当您想等待交付的暂停时使用此运算符 来自上游发布者的事件。例如,上调用 debounce 发布者从一个文本字段只接收用户时的元素 暂停或停止输入。当他们再次开始打字时,去抖动 将事件传递保持到下一次暂停。

当用户停止输入时,debounce 发布者会等待指定的时间(在我的示例中为 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)")
    

【讨论】:

看起来代码在我运行时出现了一些问题。有时在 0.5 秒后,我在文本字段中的文本被删除。但这是一个很好的起点,让我试试你提供的东西,看看我是否可以改进它。如果我破解它,我会返回以将其标记为正确。 嗨@iosCalendarpatchthecode.com 您是在模拟器上还是在预览版上尝试代码?因为我无法在实际模拟器中重现该错误。 @iOSCalendarpatchthecode.com 同样,请务必复制粘贴当前版本的代码,我进行了多次编辑。 @iOSCalendarpatchthecode.com 啊,好吧,不用担心 xD 我开始考虑 MacOS 上的一些错误,iPhone 模拟器中不存在。

以上是关于如何在 SwiftUI 视图上使用组合的主要内容,如果未能解决你的问题,请参考以下文章

在 SwiftUi 中水平组合和打破垂直滚动视图?

SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图

SwiftUI之深入解析如何使用组合矩形GeometryReader创建条形(柱状)图

SwiftUI:如何计算文本视图的宽度?

如何在 SwiftUI 上创建一个空的“视图”数组?

如何在启动时显示 NavigationView (SwiftUI) 的详细视图