I put everything into a TextFieldStyle
struct AppTextFieldStyle: TextFieldStyle {
@Environment(\.colorScheme) var colorScheme
var label: String = ""
@Binding var fieldValue: String
var placeholderText: String = ""
var systemImage: String?
var isPassword: Bool = false
@State var showPassword = false
@Binding var hasError: Bool
@Binding var validationMessage: String
func _body(configuration: TextField<Self._Label>) -> some View {
VStack(spacing: 5) {
HStack {
Text(label)
.foregroundColor(.black)
Spacer()
}
HStack {
HStack(alignment: .bottom, spacing: 0) {
if let systemImage {
Image(systemName: systemImage)
.font(.headline)
.foregroundColor(
Color.gray.opacity(0.7))
.padding(.trailing, 10)
}
HStack {
if isPassword && showPassword {
TextField(placeholderText, text: self.$fieldValue)
} else {
configuration
}
}
.frame(minHeight: 23) // for some reason the textfield is not same height as secure textfield
if isPassword {
Button(
action: {
showPassword.toggle()
},
label: {
Image(systemName: self.showPassword ? "eye.slash" : "eye")
.accentColor(.gray)
}
)
.font(.headline)
.foregroundColor(hasError ? .red : .gray.opacity(0.70))
.padding(.leading, 10)
}
}
.padding(.all, 10)
.background(colorScheme == .dark ?
LinearGradient(
gradient: AppGradients.DarkTextFieldGradient.getGradient(),
startPoint: .top,
endPoint: .bottom
) : nil)
.background(colorScheme != .dark ? Color.white : nil)
}
.cornerRadius(10)
// .shadow(color: .black.opacity(0.40), radius: 2, x: 1, y: 1)
.overlay {
RoundedRectangle(cornerRadius: 10)
.stroke(
hasError ? .red : .gray.opacity(0.90), lineWidth: 1)
}
.padding(.top, 5)
.foregroundColor(.black)
if hasError {
HStack {
Image(systemName: "exclamationmark.triangle.fill")
.font(.headline)
Text(validationMessage)
.frame(maxWidth: .infinity, alignment: .leading)
.font(.footnote)
}.foregroundColor(.red)
}
}
}
}
then I use it like so
TextField("test", text: self.$email)
.textFieldStyle(
AppTextFieldStyle(
label: "Email",
fieldValue: self.$email,
systemImage: "person.circle",
hasError: .constant(true),
validationMessage: .constant("Please enter your email")
)
)
.focused($focus, equals: .email)
.foregroundColor(Color(.label))
.textInputAutocapitalization(.never)
.privacySensitive()
.disableAutocorrection(true)
//
SecureField("password", text: self.$password)
.textFieldStyle(
AppTextFieldStyle(
label: "Password",
fieldValue: self.$password,
placeholderText: "password",
systemImage: "lock.circle",
isPassword: true,
hasError: .constant(false),
validationMessage: .constant("please enter your password")
)
)
.focused($focus, equals: .password)
.textInputAutocapitalization(.never)
.privacySensitive()
.disableAutocorrection(true)
btw the focus state is defined in the form view
enum FocusableField: Hashable {
case email
case password
}
@FocusState private var focus: FocusableField?