I have a SwiftUI view that takes user name in a String declared as a State property. How can I add an additional attribute to the same variable?
struct Login: View {
@Trimmed(characterSet: .whitespaces)
@State private var userName: String = ""
var body: some View {
VStack {
TextEditor(text: $userName)
}
}
}
In the code above, I am trying to add a property wrapper Trimmed that will automatically keep the user input string clean.
I am getting an error saying Cannot convert value of type 'State<String>' to expected argument type 'String'
Is there a way out here?
Update:
Here is the code for the property wrapper
@propertyWrapper
struct Trimmed {
private var value: String!
private let characterSet: CharacterSet
var wrappedValue: String {
get { value }
set { value = newValue.trimmingCharacters(in: characterSet) }
}
init(wrappedValue: String) {
self.characterSet = .whitespacesAndNewlines
self.wrappedValue = wrappedValue
}
init(wrappedValue: String, characterSet: CharacterSet) {
self.characterSet = characterSet
self.wrappedValue = wrappedValue
}
}
See the Swift Evolution Proposal for how property wrapper composition works. By writing
@Trimmedfirst, you are actually doing:If you put
Statefirst,then you get:
Which is correct as far as types are concerned.
However, such a
userNamehas aprojectedValueofBinding<Trimmed>, which is probably not what you want. You can't use this in aTextFieldfor example.So you actually have to put
Trimmedfirst, so thatTrimmedcan take aStateand have its ownprojectedValueof typeBinding<String>. But that still won't work. You can't detect when theStatechanges and remove the whitespaces, becauseStatehas anonmutatingsetter.I think you'd have to dig into the implementation details of SwiftUI to make something like this work.
I would suggest just using the
onChangeview modifier instead. That is the "correct way" to detect a change in@State.Extract this as a view modifier if you like.