如何在 SwiftUI 中检测 TextField 的实时变化?
Posted
技术标签:
【中文标题】如何在 SwiftUI 中检测 TextField 的实时变化?【英文标题】:How to detect live changes on TextField in SwiftUI? 【发布时间】:2020-01-12 11:48:33 【问题描述】:我有一个简单的 TextField,像这样绑定到状态“位置”,
TextField("Search Location", text: $location)
我想在每次这个字段改变时调用一个函数,像这样:
TextField("Search Location", text: $location)
self.autocomplete(location)
但是这不起作用。我知道有回调,onEditingChanged - 但是这似乎只在字段被聚焦时触发。
如何在每次更新字段时调用此函数?
【问题讨论】:
【参考方案1】:我发现最有用的是 TextField 有一个称为 onEditingChanged 的属性,它在编辑开始和编辑完成时调用。
TextField("Enter song title", text: self.$userData.songs[self.songIndex].name, onEditingChanged: (changed) in
if changed
print("text edit has begun")
else
print("committed the change")
saveSongs(self.userData.songs)
)
.textFieldStyle(RoundedBorderTextFieldStyle())
.font(.largeTitle)
【讨论】:
onEditingChanged 不会在文本更改时调用,而是在编辑开始或停止时调用。当您在文本字段内点击时调用一次,然后在您点击完成时调用 next。它确实是这样工作的。如果您正在寻找每次击键时调用的东西,这不是它。但就其本身而言,这是一个非常好的解决方案。 这个问题具体是关于文本何时更改,而不是编辑开始/停止时。这个问题的答案不正确。 哦,你是对的。有用,但不是他们要的。【参考方案2】:虽然其他答案可能有效,但这个答案对我有用,我需要聆听文本更改并对其做出反应。
第一步创建一个扩展函数。
extension Binding
func onChange(_ handler: @escaping (Value) -> Void) -> Binding<Value>
Binding(
get: self.wrappedValue ,
set: newValue in
self.wrappedValue = newValue
handler(newValue)
)
现在对 TextField 中的绑定调用 change,如下所示。
TextField("hint", text: $text.onChange( (value) in
//do something here
))
来源:HackingWithSwift
【讨论】:
【参考方案3】:SwiftUI 2.0
从 ios 14、macOS 11 或任何其他包含 SwiftUI 2.0 的操作系统开始,有一个名为 .onChange
的新修饰符可以检测给定 state
的任何更改:
struct ContentView: View
@State var location: String = ""
var body: some View
TextField("Your Location", text: $location)
.onChange(of: location)
print($0) // You can do anything due to the change here.
// self.autocomplete($0) // like this
SwiftUI 1.0
对于较旧的 iOS 和其他 SwiftUI 1.0 平台,您可以使用onReceive
:
.onReceive(location.publisher)
print($0)
请注意它返回更改而不是整个值。如果您需要与onChange
相同的行为,您可以使用 combine 并按照@pawello2222 提供的答案进行操作。
【讨论】:
【参考方案4】:SwiftUI 1 & 2
使用onReceive
:
import Combine
import SwiftUI
struct ContentView: View
@State var location: String = ""
var body: some View
TextField("Search Location", text: $location)
.onReceive(Just(location)) location in
// print(location)
【讨论】:
这实际上是一种非常好的、干净和聪明的方法! 这真的有效吗?您不需要将 Just(location) 存储在某个地方以订阅它并接收它的值吗? @Xaxxus 它工作得很好。我鼓励您自己尝试一下。 @pawello2222 我最终需要为我的用例做一些不同的事情。我有一个文本字段,其下方有一个错误标签。错误标签只需要显示是否有错误。并且触发该错误标签的验证需要在击键(300 毫秒)去抖动时运行。所以我最终做的是为我的文本字段制作一个可观察的对象视图模型。使用 text 和 errorText 发布属性而我 .onRecieve(viewModel.$text.debounce(300, runloop.main)) 使用 .debounce 触发我的验证的 $text 变量就像一个魅力。【参考方案5】:如果您需要使用ViewModel
,另一种解决方案可能是:
import SwiftUI
import Combine
class ViewModel: ObservableObject
@Published var location = ""
didSet
print("set")
//do whatever you want
struct ContentView: View
@ObservedObject var viewModel = ViewModel()
var body: some View
TextField("Search Location", text: $viewModel.location)
【讨论】:
嗨@superpuccio,如果你要使用ViewModel,你可以把代码放在didSet ... 中。不需要:可取消/下沉。 ;-) 这是一个比公认的 IMO 更清晰的答案。如果你的 body var 中有多个绑定,它可能会很快变得混乱。 它需要是一个可观察的对象吗? @Zorayr 是的,否则您将无法获得已发布属性的更新。 @FrankCheng 重绘视图时(即再次请求视图主体时)不会重新创建观察到的对象。当您的视图位于另一个视图的主体内并且该视图被重绘时,它们将被重新创建(即再次请求其主体,在该主体中,您的子视图包含观察到的对象,因此您的视图完全重新创建了它的所有属性,包括您观察到的目的)。如果是这种情况,您应该从外部将观察到的对象注入到您的视图中,或者,如果您可以使用 iOS 14,请尝试使用新的 StateObject,这正是为此目的。【参考方案6】:您可以使用自定义闭包创建绑定,如下所示:
struct ContentView: View
@State var location: String = ""
var body: some View
let binding = Binding<String>(get:
self.location
, set:
self.location = $0
// do whatever you want here
)
return VStack
Text("Current location: \(location)")
TextField("Search Location", text: binding)
【讨论】:
嗨康蒂基!只是关于您的有趣答案的问题(我从未想过以这种方式使用Binding
):我想知道为什么如果我在location
var 上附加一个didSet,didSet 根本不会被调用。像@State var location: String = "" didSet //do something
这样的东西我不明白为什么这个函数没有被调用。谢谢。
didSet on location 永远不会触发,因为您通过绑定更改的是 location.wrappedValue,而不是 location。
是的,这完全有道理。我很困惑。像往常一样:谢谢你:)
从 SwiftUI 中的 textFieldDidChange() 调整的绝佳解决方案,谢谢!
我得到 函数声明了一个不透明的返回类型,但它的主体中没有返回语句来推断底层类型以上是关于如何在 SwiftUI 中检测 TextField 的实时变化?的主要内容,如果未能解决你的问题,请参考以下文章
检测用户何时停止/暂停在 TextField 中写入 - SwiftUI
如何防止 TextField 在 SwiftUI 列表中消失?
如何检测 SwiftUI 中的 @Published 值发生了啥变化?
我如何在SWIFTUI中把textField作为firstResponder,就像Swift一样:textField.becomesFirstResponder。