You need to use combine with SwiftUI for validation
Use TextFieldWithValidator to validate textField
import SwiftUI
import Combine
// MARK: FIELD VALIDATION
@available(iOS 13, *)
public struct FieldChecker {
public var errorMessage:String?
public var valid:Bool {
self.errorMessage == nil
}
public init( errorMessage:String? = nil ) {
self.errorMessage = errorMessage
}
}
@available(iOS 13, *)
public class FieldValidator<T> : ObservableObject where T : Hashable {
public typealias Validator = (T) -> String?
@Binding private var bindValue:T
@Binding private var checker:FieldChecker
@Published public var value:T
{
willSet {
self.doValidate(newValue)
}
didSet {
self.bindValue = self.value
}
}
private let validator:Validator
public var isValid:Bool {
self.checker.valid
}
public var errorMessage:String? {
self.checker.errorMessage
}
public init( _ value:Binding<T>, checker:Binding<FieldChecker>, validator:@escaping Validator ) {
self.validator = validator
self._bindValue = value
self.value = value.wrappedValue
self._checker = checker
}
public func doValidate( _ newValue:T? = nil ) -> Void {
self.checker.errorMessage =
(newValue != nil) ?
self.validator( newValue! ) :
self.validator( self.value )
}
}
// MARK: FORM FIELD
@available(iOS 13, *)
public struct TextFieldWithValidator : View {
// specialize validator for TestField ( T = String )
public typealias Validator = (String) -> String?
var title:String?
var onCommit:() -> Void
@ObservedObject var field:FieldValidator<String>
public init( title:String = "",
value:Binding<String>,
checker:Binding<FieldChecker>,
onCommit: @escaping () -> Void,
validator:@escaping Validator ) {
self.title = title;
self.field = FieldValidator(value, checker:checker, validator:validator )
self.onCommit = onCommit
}
public init( title:String = "", value:Binding<String>, checker:Binding<FieldChecker>, validator:@escaping Validator ) {
self.init( title:title, value:value, checker:checker, onCommit:{}, validator:validator)
}
public var body: some View {
VStack {
TextField( title ?? "", text: $field.value, onCommit: self.onCommit )
.onAppear { // run validation on appear
self.field.doValidate()
}
}
}
}
@available(iOS 13, *)
public struct SecureFieldWithValidator : View {
// specialize validator for TestField ( T = String )
public typealias Validator = (String) -> String?
var title:String?
var onCommit:() -> Void
@ObservedObject var field:FieldValidator<String>
public init( title:String = "",
value:Binding<String>,
checker:Binding<FieldChecker>,
onCommit: @escaping () -> Void,
validator:@escaping Validator ) {
self.title = title;
self.field = FieldValidator(value, checker:checker, validator:validator )
self.onCommit = onCommit
}
public init( title:String = "", value:Binding<String>, checker:Binding<FieldChecker>, validator:@escaping Validator ) {
self.init( title:title, value:value, checker:checker, onCommit:{}, validator:validator)
}
public var body: some View {
VStack {
SecureField( title ?? "", text: $field.value, onCommit: self.onCommit )
.onAppear { // run validation on appear
self.field.doValidate()
}
}
}
}
in your View
import SwiftUI
import Combine
class DataItem: ObservableObject { // observable object
@Published var username:String = "" // observable property
}
struct FormWithValidator : View {
@EnvironmentObject var item:DataItem // data model reference
@State var usernameValid = FieldChecker() // validation state of username field
func username() -> some View {
VStack {
TextFieldWithValidator( title: "username",
value: $item.username,
checker: $usernameValid,
onCommit: submit) { v in
// validation closure where ‘v’ is the current value
if( v.isEmpty ) {
return "username cannot be empty"
}
return nil
}
.padding(.all)
.border( usernameValid.valid ? Color.clear : Color.red )
.background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0))
.autocapitalization(.none)
if( !usernameValid.valid ) {
Text( usernameValid.errorMessage ?? "" )
.fontWeight(.light)
.font(.footnote)
.foregroundColor(Color.red)
}
}
}
var isValid:Bool {
usernameValid.valid
}
func submit() {
if( isValid ) {
print( "submit:\nusername:\(self.item.username)")
}
}
var body: some View {
NavigationView {
Form {
Section {
username()
}
Section {
Button( "Submit" ) {
self.submit()
}
.disabled( !self.isValid )
} // end of section
} // end of form
.navigationBarTitle( Text( "Sample Form" ), displayMode: .inline )
} // NavigationView
}
}
#if DEBUG
struct FormVithValidator_Previews: PreviewProvider {
static var previews: some View {
FormWithValidator()
.environmentObject( DataItem() )
}
}
#endif
Credits and inspiration