I've been playing around with SwiftUI and Combine and feel like there is probably a way to get a hold of the existing @State properties in a view and create a new one.
For example, I have a password creation View which holds a password and a passwordConfirm field for the user. I want to take those two @State properties and derive a new @State that I can use in my view that asserts if the input is valid. So for simplicity: not empty and equal.
The Apple docs say there is a publisher on a binding, though I can't appear to get ahold of it.
This is some non-functioning pseudo code:
import SwiftUI
import Combine
struct CreatePasswordView : View {
@State var password = ""
@State var confirmation = ""
lazy var valid = {
return self.$password.publisher()
.combineLatest(self.$confirmation)
.map { $0 != "" && $0 == $1 }
}
var body: some View {
SecureField($password, placeholder: Text("password"))
SecureField($confirmation, placeholder: Text("confirm password"))
NavigationButton(destination: NextView()) { Text("Done") }
.disabled(!valid)
}
}
Anyone found. the appropriate way of going about this / if it's possible?
UPDATE Beta 2:
As of beta 2 publisher is available so the first half of this code now works. The second half of using the resulting publisher within the View I've still not figured out (disabled(!valid)
).
import SwiftUI
import Combine
struct CreatePasswordView : View {
@State var password = ""
@State var confirmation = ""
lazy var valid = {
Publishers.CombineLatest(
password.publisher(),
confirmation.publisher(),
transform: { String($0) != "" && $0 == $1 }
)
}()
var body: some View {
SecureField($password, placeholder: Text("password"))
SecureField($confirmation, placeholder: Text("confirm password"))
NavigationButton(destination: NextView()) { Text("Done") }
.disabled(!valid)
}
}
Thanks.
I wouldn't be playing with @State/@Published
as Combine
is in beta at the moment, but here's a simple workaround for what you're trying to achieve.
I'd implement a view model to hold password, password confirmation, and whether it's valid or not
class ViewModel: NSObject, BindableObject {
var didChange = PassthroughSubject<Void, Never>()
var password: String = "" {
didSet {
didChange.send(())
}
}
var passwordConfirmation: String = "" {
didSet {
didChange.send(())
}
}
var isPasswordValid: Bool {
return password == passwordConfirmation && password != ""
}
}
In this way, the view is recomputed anytime the password or the confirmation changes.
Then I would make a @ObjectBinding
to the view model.
struct CreatePasswordView : View {
@ObjectBinding var viewModel: ViewModel
var body: some View {
NavigationView {
VStack {
SecureField($viewModel.password,
placeholder: Text("password"))
SecureField($viewModel.passwordConfirmation,
placeholder: Text("confirm password"))
NavigationButton(destination: EmptyView()) { Text("Done") }
.disabled(!viewModel.isPasswordValid)
}
}
}
}
I had to put the views in a NavigationView
, because NavigationButton
doesn't seem to enable itself if it isn't in one of them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With