使用 Binding 确定 SwiftUI 中的字符串更改

Posted

技术标签:

【中文标题】使用 Binding 确定 SwiftUI 中的字符串更改【英文标题】:Determine String changes in SwiftUI with Binding 【发布时间】:2020-12-16 23:21:18 【问题描述】:

这是我在 *** 上的第一个问题,一个月前我才开始使用 ios、Swift 和 SwiftUI。如果我做错了什么,请理解并告诉我。

我想确定一个 String 是否在一个绑定了 String(@Binding) 的结构中发生了变化。我在开始时将原始字符串复制到第二个字符串(在正文内)。两个字符串总是相同的,就好像引用被复制了,而不是值。据我了解 String 的文档始终使用其值复制 String 。如果我错了,请纠正我。下面是一些示例代码:

struct EditTextSheet: View 

var title: String
@Binding var showSheetView: Bool
@Binding var userConfirmedChange: Bool
@Binding var textToEdit: String

var body: some View

    // use to determine if user made any changes to text
    let originalText: String = textToEdit
    
    
    NavigationView
    
        Form
        
            Section(header: Text(title))
            
                TextField(textToEdit, text: $textToEdit)
            
            
        
        
        .navigationBarTitle("", displayMode: .inline)
        // buttons within navigation Bar OK top right, cancel top left. OK also sets userConfirmedChange to true
        
        // Cancel BUTTON
        .navigationBarItems(leading: Button(action: 
            self.showSheetView = false
            
        ) 
            Text("Cancel")
        , trailing: Button(action: 
            
            // OK BUTTON
            if textToEdit.isEmpty
            
                // tell user that String cannot be empty
                ...
            
            else
            



                // ***BELOW IS ALWAYS THE SAME***
                // if "Test" was passed as textToEdit and user then
                // changes textToEdit via the TextField to "Test1234" 
                // below if clause results in "Test1234" == "Test1234"
                // what I want is "Test" == "Test1234"




                if textToEdit == originalText
                
                    self.userConfirmedChange = false
                    self.showSheetView = false
                
                else
                
                    self.userConfirmedChange = true
                    self.showSheetView = false
                
                
            
            
        ) 
            Text("OK")
                .bold()
        )
        
    
    
    



这个结构是一个工作表,被这样调用:

    ...
    @State var showEditSheet = false
    @State var userConfirmedChange = false
    @State var editText: String = ""
    ...
   
    Button(action: 
            editText = "Test"
            userConfirmedChange = false
            showEditSheet.toggle()
        ) 
            Text ("Edit Test")
        .sheet(isPresented: $showEditSheet, onDismiss: 
            if userConfirmedChange
            
                print("\(editText)")
            
            else
            
                print("cancelled")
            
        ) 
            EditTextSheet(title: "some title", showSheetView: $showEditSheet, userConfirmedChange: $userConfirmedChange, textToEdit: $editText)
        

         ...

【问题讨论】:

如果一个结构体中的一个字符串发生了改变,当一个字符串改变时你想调用一些东西来通知你字符串已经改变?那“有一个绑定的字符串(@Binding)”是什么意思?如果您将变量声明为绑定,它当然会绑定?! "that has a bound String(@Binding)" 只是为了强调,我从中复制的 var 是 (AT)Binding var :String 而不是“普通” var: String。 (AT)Binding 不应影响“let originalText = textToEdit” var,只会影响传递给此结构的 var。至少这是我想要实现的,以确定用户在与 TextField 交互时是否更改了字符串 【参考方案1】:

您面临状态更新的问题,它会覆盖您的旧值。您将 textToEdit 作为绑定传递给您的 EditTextSheet 视图。每当此值更改时,具有该状态的父视图就会重新加载。因此,它将重新加载您的 EditTextSheet 并将 originalText 设置为当前值。因此,originalTexttextToEdit 将始终相同。这就是为什么你比较它们,它们总是相同的。 Strings 是值类型,而不是引用类型。但在您的情况下,它们始终具有相同的值。

要解决此问题,您需要在 EditTextSheet 中使用本地 State 变量,该变量采用 Binding 的值。此状态在您看来是本地的,并将存储新值。运行您的操作时,您会将未更改的绑定与该状态(代表文本字段)进行比较并运行您的操作。这是您的代码的可能更改

struct EditTextSheet: View 

    var title: String
    @Binding var showSheetView: Bool
    @Binding var userConfirmedChange: Bool
    @Binding var textToEdit: String
    @State var actualText : String//<< this is the local State which will be used for the TextField

    init(title: String, showSheetView: Binding<Bool>, userConfirmedChange: Binding<Bool>, textToEdit: Binding<String>) 
        self.title = title
        self._showSheetView = showSheetView
        self._userConfirmedChange = userConfirmedChange
        self._textToEdit = textToEdit
        self._actualText = State(initialValue: textToEdit.wrappedValue) //<< create new State with the value of the Binding, now it will be initialized with "Test"
    

然后你的 TextField 看起来像这样

TextField(textToEdit, text: $actualText)

现在在您的按钮操作中,我们可以使用以下代码:

if actualText.isEmpty

    

else

    //This is our new value which is binded to the textField
    print(actualText)
    //This is our old value
    print(textToEdit)

    if actualText == textToEdit
    
        self.textToEdit = actualText
        self.userConfirmedChange = false
        self.showSheetView = false
    
    else
    
        self.textToEdit = actualText
        self.userConfirmedChange = true
        self.showSheetView = false
    

我们检查旧值是否与新值匹配。在这里,我们将本地 State(更改)与旧 Binding(迄今为止从未更改)进行比较。最后,我们将使用self.textToEdit = actualText 用实际值更新绑定,因为我们没有将该绑定分配给文本字段。当我们关闭这个视图时,TextToEdit 将被传回我们的父视图。

【讨论】:

感谢您的解释以及使用自定义初始化方法获取绑定的第一个状态的解决方案。最后,我只是将 originalText 设置为 editText 的第一个状态,就像你对 actualText 所做的那样,然后保持我原来的行为。工作得很好,似乎比将 editText 设置为实际文本更容易理解(对我来说)。但再次感谢!【参考方案2】:

您的代码很好,除了您在视图主体内初始化“let originalText: String = textToEdit”。当 '@Binding var textToEdit: String' 变量发生变化时,会重新渲染整个视图,这也会再次初始化 originalText。解决方案是将 originalText 移到正文之外。

简单的方法是将 originalText 作为另一个变量添加到初始化器中:

   @Binding var textToEdit: String
   let originalText: String

    var body: some View 

【讨论】:

感谢您的回答并澄清我的错误所在。你的回答很好。我更喜欢避免传递相同的字符串两次调用我的视图的冗余。

以上是关于使用 Binding 确定 SwiftUI 中的字符串更改的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI 将@Published viewmodel 对象值传递给@Binding

SwiftUI 自定义 List 不能在 ForEach 中使用 Binding

SwiftUI 中的可选绑定

从 @Binding var 修改 @State var 不会刷新 SwiftUI 中的视图

如何在 SwiftUI 中使用 inout 而不是 Binding?

使用 SwiftUI @Binding 预览崩溃:与应用程序的通信中断