如何在 SwiftUI 视图上使用组合 [英] How to use Combine on a SwiftUI View

查看:18
本文介绍了如何在 SwiftUI 视图上使用组合的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题与这个问题有关:如何使用 SwiftUI 观察 TextField 值并合并?

This question relates to this one: How to observe a TextField value with SwiftUI and Combine?

但我要问的是更笼统的.这是我的代码:

But what I am asking is a bit more general. Here is my code:

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 函数.由于这是保存到文件,因此应该减慢/节流.

Scenario: As the user types into the textfield, the saveTextToFile function should be called. Since this is saving to a file, it should be slowed-down/throttled.

所以我的问题是:

  1. 在下面的代码中放置组合操作的正确位置是什么.
  2. 我用什么组合代码来完成:(A) 字符串不能包含空格.(B) 字符串长度必须为 5 个字符.(C) 字符串必须去抖动/减慢
  1. Where is the proper place to put the combine operations in the code below.
  2. What Combine code do I put to accomplish: (A) The string must not contain spaces. (B) The string must be 5 characters long. (C) The String must be debounced/slown down

我想将这里的响应用作以下一般模式:我们应该如何处理 SwiftUI 应用程序(而不是 UIKit 应用程序)中的组合内容.

I wanted to use the response here to be a general pattern of: How should we handle combine stuff in a SwiftUI app (not UIKit app).

推荐答案

你应该在你的 ViewModel 中做你想做的事.你的视图模型是 TestModel 类(我建议你在 TestViewModel 中重命名它).这是您应该在模型和视图之间放置逻辑的地方.ViewModel 应该准备好模型以进行可视化.这是放置合并逻辑的正确位置(当然,如果它与视图相关).

You should do what you want in your ViewModel. Your view model is the TestModel class (which I suggest you rename it in TestViewModel). It's where you are supposed to put the logic between the model and the view. The ViewModel should prepare the model to be ready for the visualization. And that is the right place to put your combine logic (if it's related to the view, of course).

现在我们可以使用您的具体示例来实际制作一个示例.老实说,根据您真正想要实现的目标,有几种略有不同的解决方案.但现在我会尽量做到通用,然后你可以告诉我解决方案是否合适或需要一些改进:

Now we can use your specific example to actually make an example. To be honest there are a couple of slight different solutions depending on what you really want to achieve. But for now I'll try to be as generic as possible and then you can tell me if the solution is fine or it needs some refinements:

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

You should attach your own subscriber to the TextField publisher and use the debounce publisher to delay the cleaning of the string and the calling to the saving method. According to the documentation:

去抖动(for:scheduler:options:)

debounce(for:scheduler:options:)

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

Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call debounce on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the debounce holds event delivery until the next pause.

当用户停止输入时,去抖动发布者等待指定的时间(在我上面的例子中为 0.5 秒),然后它用新值调用其订阅者.

When the user stops typing the debounce publisher waits for the specified time (in my example here above 0.5 secs) and then it calls its subscriber with the new value.

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

The solution above delays both the saving of the string and the TextField update. This means that users will see the original string (the one with spaces and maybe longer than 5 characters) for a while, before the update happens. And that's why, at the beginning of this answer, I said that there were a couple of different solutions depending on the needs. If, indeed, we want to delay just the saving of the string, but we want the users to be forbidden to input space characters or string longer that 5 characters, we can use two subscribers (I'll post just the code that changes, i.e. the TestViewModel class):

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)")
    }
}

这篇关于如何在 SwiftUI 视图上使用组合的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆