I've built a login screen in SwiftUI. I want to focus on the password SecureField
when the user is finished entering their email. How can I do this?
struct LoginView: View {
@State var username: String = ""
@State var password: String = ""
var body: some View {
ScrollView {
VStack {
TextField("Email", text: $username)
.padding()
.frame(width: 300)
.background(Color(UIColor.systemGray5))
.cornerRadius(5.0)
.padding(.bottom, 20)
.keyboardType(.emailAddress)
SecureField("Password", text: $password)
.padding()
.frame(width: 300)
.background(Color(UIColor.systemGray5))
.cornerRadius(5.0)
.padding(.bottom, 20)
Button(action: {
}, label: {
Text("Login")
.padding()
.frame(width: 300)
.background((username.isEmpty || password.isEmpty) ? Color.gray : Color(UIColor.cricHQOrangeColor()))
.foregroundColor(.white)
.cornerRadius(5.0)
.padding(.bottom, 20)
}).disabled(username.isEmpty || password.isEmpty)
If you're supporting only iOS 15 and later, you can activate and dismiss the keyboard for a text field by focusing and unfocusing it. In its simplest form, this is done using the @FocusState property wrapper and the focusable() modifier – the first stores a Boolean that tracks whether the second is currently focused.
I've improved on the answer from Gene Z. Ragan and Razib Mollick. Fixes a crash, this allows for any amount of textfields, supports passwords and made it into its own class.
struct UITextFieldView: UIViewRepresentable {
let contentType: UITextContentType
let returnVal: UIReturnKeyType
let placeholder: String
let tag: Int
@Binding var text: String
@Binding var isfocusAble: [Bool]
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.textContentType = contentType
textField.returnKeyType = returnVal
textField.tag = tag
textField.delegate = context.coordinator
textField.placeholder = placeholder
textField.clearButtonMode = UITextField.ViewMode.whileEditing
if textField.textContentType == .password || textField.textContentType == .newPassword {
textField.isSecureTextEntry = true
}
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
if uiView.window != nil {
if isfocusAble[tag] {
if !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
} else {
uiView.resignFirstResponder()
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: UITextFieldView
init(_ textField: UITextFieldView) {
self.parent = textField
}
func textFieldDidChangeSelection(_ textField: UITextField) {
// Without async this will modify the state during view update.
DispatchQueue.main.async {
self.parent.text = textField.text ?? ""
}
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
setFocus(tag: parent.tag)
return true
}
func setFocus(tag: Int) {
let reset = tag >= parent.isfocusAble.count || tag < 0
if reset || !parent.isfocusAble[tag] {
var newFocus = [Bool](repeatElement(false, count: parent.isfocusAble.count))
if !reset {
newFocus[tag] = true
}
// Without async this will modify the state during view update.
DispatchQueue.main.async {
self.parent.isfocusAble = newFocus
}
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
setFocus(tag: parent.tag + 1)
return true
}
}
}
struct UITextFieldView_Previews: PreviewProvider {
static var previews: some View {
UITextFieldView(contentType: .emailAddress,
returnVal: .next,
placeholder: "Email",
tag: 0,
text: .constant(""),
isfocusAble: .constant([false]))
}
}
In iOS 15 we can now use @FocusState
to control which field should be focused.
Here is an example how to add buttons above the keyboard to focus the previous/next field:
struct ContentView: View {
@State private var email: String = ""
@State private var username: String = ""
@State private var password: String = ""
@FocusState private var focusedField: Field?
var body: some View {
NavigationView {
VStack {
TextField("Email", text: $email)
.focused($focusedField, equals: .email)
TextField("Username", text: $username)
.focused($focusedField, equals: .username)
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
}
.toolbar {
ToolbarItem(placement: .keyboard) {
Button(action: focusPreviousField) {
Image(systemName: "chevron.up")
}
.disabled(!canFocusPreviousField()) // remove this to loop through fields
}
ToolbarItem(placement: .keyboard) {
Button(action: focusNextField) {
Image(systemName: "chevron.down")
}
.disabled(!canFocusNextField()) // remove this to loop through fields
}
}
}
}
}
extension ContentView {
private enum Field: Int, CaseIterable {
case email, username, password
}
private func focusPreviousField() {
focusedField = focusedField.map {
Field(rawValue: $0.rawValue - 1) ?? .password
}
}
private func focusNextField() {
focusedField = focusedField.map {
Field(rawValue: $0.rawValue + 1) ?? .email
}
}
private func canFocusPreviousField() -> Bool {
guard let currentFocusedField = focusedField else {
return false
}
return currentFocusedField.rawValue > 0
}
private func canFocusNextField() -> Bool {
guard let currentFocusedField = focusedField else {
return false
}
return currentFocusedField.rawValue < Field.allCases.count - 1
}
}
Note: As of Xcode 13 beta 1 @FocusState
doesn't work in Form
/List
. This should be fixed in the next releases.
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