SwiftUI TextField在使用Binding<String>计算属性的奇怪情况下不会更改其内容

huangapple go评论85阅读模式
英文:

SwiftUI TextField won't change its contents under weird corner case with Binding<String> computed property

问题

在SwiftUI的body中,我有一个TextField。我通过一个中介绑定将其绑定到@State var,该绑定允许我获取和设置计算值...

struct ExampleView: View {

    @State var symbolToBeValidated: String = ""

    var body: some View {
        let binding = Binding<String> (get: {
            return self.symbolToBeValidated
        }, set: {
            var newString = $0.uppercased()
            self.symbolToBeValidated = newString // <- fig. 1: 我希望不必这样重复赋值

            newString = newString.replacingOccurrences(
                of: "[^A-Z]",
                with: "",
                options: .regularExpression
            )

            self.symbolToBeValidated = newString // <- fig. 2: 最终、真正有效的赋值
        })

        let form = Form {
            Text("你想分析哪个符号?")
            TextField("例如:AAPL", text: binding)
            // [...]
        }
    }
}

我使用中介绑定以便可以将字符串转换为始终为大写格式且仅包含字母A-Z的格式(如我的.regularExpression引用所示)。我试图使TextField仅在每次按键时显示格式正确的股票符号。

这个方法在某种程度上有效。我发现的问题是,如果我不调用两次赋值(如图1所示),TextField将开始显示数字和字母(尽管它没有包含在symbolToBeValidated字符串中)。我怀疑这是因为SwiftUI在内部检查旧值与新值时,因为后台没有改变,它不会调用刷新以再次获取内部值。我找到的方法是在.replacingOccurences调用之前包含额外的赋值。

这会导致数字或符号在用户键入时在屏幕上闪烁一下,然后通过.replacingOccurences调用正确地删除。

肯定有一种更加优雅的方法来做到这一点。我尝试了Formatter类类型,只是因为Formatter导致了类似的行为,其中错误的字符会在屏幕上闪烁一下,然后被删除。

如果有人知道如何在任何内容显示在屏幕上之前拦截这个问题,我将不胜感激。这有点挑剔,但我只是在寻找正确的答案。

英文:

I have a TextField inside a SwiftUI body. I have bound it to a @State var through an intermediary binding which lets me get and set a computed value...

struct ExampleView: View {

@State var symbolToBeValidated: String = &quot;&quot;

var body: some View {
    let binding = Binding&lt;String&gt; (get: {
        return self.symbolToBeValidated
    }, set: {
        var newString = $0.uppercased()
        self.symbolToBeValidated = newString // &lt;- fig. 1: redundant assignment I wish I didn&#39;t have to put

        newString = newString.replacingOccurrences(
            of: #&quot;[^A-Z]&quot;#,
            with: &quot;&quot;,
            options: .regularExpression
        )
        
        self.symbolToBeValidated = newString // &lt;- fig. 2: the final, truly valid assignment
    })
    
    let form = Form {
         Text(&quot;What symbol do you wish to analyze?&quot;)
         TextField(&quot;ex.AAPL&quot;, text: binding)
// [...]

I'm using the intermediary Binding so that I can transform the string to always be an Uppercased format only containing letters A-Z (as referenced by my .regularExpression). (I'm trying to make it so that the TextField only shows a validly formatted Stock Symbol on each keypress).

This works, somewhat. The problem I discovered is that if I don't call the assignment twice (as seen in fig 1) the TextField will begin to show numbers and letters (even though it isn't included in the symbolToBeValidated string. This happens, I suspect, because SwiftUI is checking the oldValue against the newValue internally, and because it hasn't changed in the background, it doesn't call a refresh to get the internal value again. The way I've found to thwart this is to include an extra assignment before the .replacingOccurences call.

This results in the number or symbol being flashed on the screen for a blip as it is being typed by the user, then it is correctly removed by the .replacingOccurences call.

There must be a more elegant way to do this. I went down the Formatter class type and tried this alternative only because Formatter resulted in a similar behavior where the errant character would blip on the screen before being removed.

If someone knows a way for this to be intercepted before anything is displayed on the screen, I would appreciate it. This is super nit-picky, but I'm just fishing for the right answer here.

答案1

得分: 1

扩展Binding,其中Value类型为String的情况
public func validated() -> Self {
    return .init(
        get: { self.wrappedValue },
        set: {
            var newString = $0.uppercased()
            newString = newString.replacingOccurrences(
                of: "[^A-Z]",
                with: "",
                options: .regularExpression
            )
            self.wrappedValue = newString
        }
    )
}

// ...

TextField("ex.AAPL", text: self.$symbolToBeValidated.validated())
英文:

Try this:


extension Binding where Value == String {
    public func validated() -&gt; Self {
        return .init(
            get: { self.wrappedValue },
            set: {
                var newString = $0.uppercased()
                newString = newString.replacingOccurrences(
                    of: #&quot;[^A-Z]&quot;#,
                    with: &quot;&quot;,
                    options: .regularExpression
                )
                self.wrappedValue = newString
            }
        )
    }
}

// ...

TextField(&quot;ex.AAPL&quot;, text: self.$symbolToBeValidated.validated())

This way also allows you to reuse and test the validation code.

huangapple
  • 本文由 发表于 2020年1月3日 14:03:14
  • 转载请务必保留本文链接:https://go.coder-hub.com/59573882.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定