Reputation: 423
I'm making a sign-in interface for iOS in SwiftUI. The user should be able to easily switch from the username text field to the password text field by tapping the "next" button on the software keyboard. It's working well but the keyboard always bounces a little when switching between the two text fields for some reason.
Edit: As suggested in this answer I've added a Spacer into the VStack to make it fill the available space. The text fields aren't bouncing anymore but the keyboard unfortunately still is. I've updated the code and the GIF to reflect my changes.
After googling a little it seemed like this wasn't a very common issue. This question seemed to be similar to what happens to me but following the answer and wrapping the text fields in a ScrollView or a GeometryReader did not change anything at all. This is my code:
struct AuthenticationView: View {
@State var userName: String = ""
@State var userAuth: String = ""
@FocusState var currentFocus: FocusObject?
enum FocusObject: Hashable { case name, auth }
var body: some View {
VStack(spacing: 8) {
TextField("Username", text: $userName)
.focused($currentFocus, equals: .name)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 8)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = .auth }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.username)
.submitLabel(.next)
SecureField("Password", text: $userAuth)
.focused($currentFocus, equals: .auth)
.padding(8).background(Color.lightGray)
.cornerRadius(8).padding(.bottom, 16)
.textInputAutocapitalization(.never)
.onSubmit { currentFocus = nil }
.autocorrectionDisabled(true)
.keyboardType(.asciiCapable)
.textContentType(.password)
.submitLabel(.done)
Spacer() // This fixes the text fields
// But it does not fix the keyboard
}.padding(32)
}
}
Upvotes: 28
Views: 2569
Reputation: 120113
The (ugly / but working) workaround for this ridicules bug with pure SwiftUI is to use .vertical
axes:
TextField("First", text: $firstText, axis: .vertical) // 👈 Vertical axis here
.onChange(of: firstText) {
guard firstText.hasSuffix("\n") else { return } // 👈 Watch for the new line
firstText = firstText.replacingOccurrences(of: "\n", with: "") // 👈 Cleanup unintended new lines
focusedField = .second // 👈 Change the focus here
}
⚠️ Note that this method is just a workaround and may have some side-effects. Use with caution.
Upvotes: 2
Reputation: 278
check this below code
import SwiftUI
import UIKit
struct ContentView: View {
@State private var firstName: String = ""
@State private var password: String = ""
@State private var activeField: Field?
enum Field {
case firstName, password
}
var body: some View {
VStack(spacing: 50) {
Spacer()
.frame(height: 50)
CustomTextField(placeholder: "First Name", text: $firstName, field: .firstName, activeField: $activeField, isSecure: false)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.submitLabel(.next)
.onSubmit {
activeField = .password
}
.background(Color.gray)
.frame(height: 30)
CustomTextField(placeholder: "Password", text: $password, field: .password, activeField: $activeField, isSecure: true)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
.submitLabel(.done)
.onSubmit {
activeField = nil // Done editing, dismiss keyboard
}
.background(Color.gray)
.frame(height: 30)
Spacer()
}
.padding()
}
}
struct CustomTextField: UIViewRepresentable {
var placeholder: String
@Binding var text: String
var field: ContentView.Field
@Binding var activeField: ContentView.Field?
var isSecure: Bool
func makeUIView(context: Context) -> UITextField {
let textField: UITextField
if isSecure {
textField = UITextField()
textField.isSecureTextEntry = true
} else {
textField = UITextField()
}
textField.placeholder = placeholder
textField.delegate = context.coordinator
textField.returnKeyType = (field == .password) ? .done : .next
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
// Check focus
if activeField == field {
uiView.becomeFirstResponder() // Show keyboard if this field is active
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomTextField
init(_ parent: CustomTextField) {
self.parent = parent
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if parent.field == .firstName {
// Move focus to the next field
if parent.activeField != nil {
parent.activeField = nil
}
parent.activeField = .password
} else {
// Done pressed, dismiss the keyboard
textField.resignFirstResponder()
parent.activeField = nil // Clear active field to dismiss keyboard
}
return true
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Upvotes: -2
Reputation: 19174
Your current layout says:
Put the edit fields into a VStack. Layout the VStack in the parent view by centering it in the available space. Note, that the VStack only uses a minimum size.
Now, when the keyboard appears, the available space of the parent view, i.e. its height, will be reduced accordingly.
Because the VStack is layout in the center, the text fields bounce up and down.
There are a couple of options:
Ensure the VStack extends its height and the text fields are aligned at the top. For example using a Spacer
:
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
Spacer()
}.padding(32)
Using a ScrollView:
ScrollView {
Spacer(minLength: 80) // give some space at the top
VStack(spacing: 8) {
TextField("Username", text: $userName)
...
SecureField("Password", text: $userAuth)
...
}.padding(32)
}
It may not look pretty, but it should give you an idea, where to work on this issue (you may want to use a GeometryReader and a possibly a ScrollView to perfect your layout).
Another option is to use a Form
. Put your fields into there, and with a Form
you get also a head start which looks pretty nice. The reason why a Form
works is because the same reasons why it works with a Spacer
(aligns fields on top) and because of a ScrollView.
The fact that the keyboard disappears temporarily when you tap "Next" is unfortunate. I have no solution for this, so far.
Upvotes: 0