菜鸟教程小白 发表于 2022-12-12 15:12:47

ios - 如何在 SwiftUI View 上使用组合


                                            <p><p>这个问题与这个问题有关:<a href="https://stackoverflow.com/questions/56735382/how-to-observe-a-textfield-value-with-swiftui-and-combine" rel="noreferrer noopener nofollow">How to observe a TextField value with SwiftUI and Combine?</a> </p>

<p>但我要问的是更笼统的问题。
这是我的代码:</p>

<pre><code>struct MyPropertyStruct {
    var text: String
}

class TestModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: &#34;initialText&#34;)

    func saveTextToFile(text: String) {
      print(&#34;this function saves text to file&#34;)
    }
}

struct ContentView: View {
    @ObservedObject var testModel = TestModel()
    var body: some View {
      TextField(&#34;&#34;, text: $testModel.myproperty.text)
    }
}
</code></pre>

<p><strong>场景:</strong>当用户在文本字段中输入内容时,应该调用 <em>saveTextToFile</em> 函数。由于这是保存到文件,因此应该减慢/节流。 </p>

<p><strong>所以我的问题是:</strong></p>

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

<p>我想在这里使用响应作为一般模式:<em>我们应该如何处理 SwiftUI 应用程序(而不是 UIKit 应用程序)中的组合内容。</em></p></p>
                                    <br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
                                            <p><p>你应该在你的 <code>ViewModel</code> 中做你想做的事。您的 View 模型是 <code>TestModel</code> 类(我建议您在 <code>TestViewModel</code> 中重命名它)。这是您应该在模型和 View 之间放置逻辑的地方。 <code>ViewModel</code> 应该准备好模型以便为可视化做好准备。这是放置组合逻辑的正确位置(当然,如果它与 View 相关)。</p>

<p>现在我们可以使用您的具体示例来实际制作示例。老实说,根据您<strong>真正</strong>想要实现的目标,有几个略有不同的解决方案。但现在我会尽量保持通用,然后你可以告诉我解决方案是否良好或需要一些改进:</p>

<pre><code>struct MyPropertyStruct {
    var text: String
}

class TestViewModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: &#34;initialText&#34;)
    private var canc: AnyCancellable!

    init() {
      canc = $myproperty.debounce(for: 0.5, scheduler: DispatchQueue.main).sink { 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) -&gt; String {
      //remove all the spaces
      let resultStr = String(text.unicodeScalars.filter {
            $0 != &#34; &#34;
      })

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

    private func saveTextToFile(text: String) {
      print(&#34;text saved&#34;)
    }
}

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

    var body: some View {
      TextField(&#34;&#34;, text: $testModel.myproperty.text)
    }
}
</code></pre>

<p>您应该将自己的 <code>subscriber</code> 附加到 <code>TextField</code> <code>publisher</code> 并使用 <code>debounce</code> 发布者来延迟清理字符串和对保存方法的调用。根据文档:</p>

<blockquote>
<p>debounce(for:scheduler:options:)</p>

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

<p>当用户停止输入时,debounce 发布者会等待指定的时间(在我的示例中为 0.5 秒以上),然后使用新值调用其订阅者。</p>

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

<pre><code>class TestViewModel : ObservableObject {
    @Published var myproperty = MyPropertyStruct(text: &#34;initialText&#34;)
    private var saveCanc: AnyCancellable!
    private var updateCanc: AnyCancellable!

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

      updateCanc = $myproperty.sink { 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) -&gt; String {
      //remove all the spaces
      let resultStr = String(text.unicodeScalars.filter {
            $0 != &#34; &#34;
      })

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

    private func saveTextToFile(text: String) {
      print(&#34;text saved: \(text)&#34;)
    }
}
</code></pre></p>
                                   
                                                <p style="font-size: 20px;">关于ios - 如何在 SwiftUIView 上使用组合,我们在Stack Overflow上找到一个类似的问题:
                                                        <a href="https://stackoverflow.com/questions/57922766/" rel="noreferrer noopener nofollow" style="color: red;">
                                                                https://stackoverflow.com/questions/57922766/
                                                        </a>
                                                </p>
                                       
页: [1]
查看完整版本: ios - 如何在 SwiftUI View 上使用组合