Reputation: 509
This is my first question on stackoverflow and I started working with iOS, Swift and SwiftUI only a month ago. Please be understanding and tell me if I did something wrong.
I would like to determine if a String changed within a struct that has a bound String(@Binding) to it. I copy the original String to a second string on start (within body). Both Strings are always the same, as if the reference was copied, instead of the value. As far as I understood the documentation on String a String is always copied using its value. Please correct me if I am wrong. Here is some example code:
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()
})
}
}
}
this struct is a sheet, that gets called like this:
...
@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)
}
...
Upvotes: 2
Views: 2226
Reputation: 8517
You are facing a problem with State updates, which override your old value. You are passing textToEdit
as a Binding to your EditTextSheet
View. Whenever this value changes, you Parent View with that State gets reloaded. Hence, it will reload your EditTextSheet and sets originalText
to the current value. Hence, originalText
and textToEdit
will always be the same. That's why you compare them and they are always the same. Strings
are value types, not reference types. But in your case, they always have the same value.
To fix this issue you will need a local State variable in your EditTextSheet
, which takes the value of the Binding. This State is local in your view and will store the new value. When running your action, you will compare your unchanged Binding with that State (Which represents the TextField) and run your action. Here is a possible change to your code
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"
}
Then your TextField looks like that
TextField(textToEdit, text: $actualText)
Now in your button action, we can use following code:
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
}
We check if the old value matches the new value. Here we compare the local State (which changes) with the old Binding (which has never changed so far). At the end we will use self.textToEdit = actualText
to update the Binding with the acutal value, as we didn't have assigned that Binding to the TextField. TextToEdit will be passed back to our parent view, when we dismiss this view.
Upvotes: 1
Reputation: 4235
Your code is fine, except for the fact that you are initializing 'let originalText: String = textToEdit' within the body of the view. When the '@Binding var textToEdit: String' variable changes, the entire view is rerendered, which also initializes originalText again. The solution is to move originalText outside of the body.
The easy way is to add originalText as another variable to the initializer:
@Binding var textToEdit: String
let originalText: String
var body: some View {
Upvotes: 1