Reputation: 3897
I'm working with SwiftUI's TextField View
. Mainly I have 2 questions:
In Swift, we can set Return Key(Text Input Traits) to Next
for TextField from storyboard like this right? For this which modifier to use in SwiftUI?
I have two Textfields, how to navigate to the next TextField when I click return/next button from keyboard?
Can anyone help with this using SwiftUI (not UIKit)? Or any other alternative to perform this feature?
Upvotes: 16
Views: 18569
Reputation: 652
From iOS 15+, we can handle keyboard action using @FocusState
and onSubmit()
,
struct TextFieldFocus: View {
enum Field {
case userName, password
}
@State var field1 = ""
@State var field2 = ""
@FocusState var focusedField: Field?
var body: some View {
VStack(spacing: 20.0) {
TextField("User name", text: $field1)
.focused($focusedField, equals: .userName)
.submitLabel(.next)
.padding()
.onSubmit {
focusedField = .password
}
SecureField("Password", text: $field2)
.focused($focusedField, equals: .password)
.submitLabel(.continue)
.padding()
.onSubmit {
focusedField = .userName
}
}
.textFieldStyle(.roundedBorder)
}
}
Upvotes: 2
Reputation: 1
I have written a small library that enables automatic navigation between TextFields upon submission: FormView. It also has useful features such as:
Upvotes: 0
Reputation: 14935
macOS 12.0+, Mac Catalyst 15.0+, tvOS 15.0+, watchOS 8.0+
Use
submitLabel(_:)
view modifier that sets the submit label for a view. It takes a predefined case specified inSubmitLabel
Use
.next
. It defines a submit label with text of “Next”.
Use
onFocus(_:)
to find when the modified view hierarchy, in this case theTextField
, loses focus. When it does, put the focus on the next view (SecureField
)
struct LoginForm: View {
enum Field: Hashable {
case usernameField
case passwordField
}
@State private var username = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: .usernameField)
.submitLabel(.next)
.onFocus { isFocused in
if (!isFocused) {
focusedField = .passwordField
}
}
SecureField("Password", text: $password)
.focused($focusedField, equals: .passwordField)
.submitLabel(.done)
}
}
}
Upvotes: 6
Reputation: 438
Based on Razib Mollick's answer and https://www.hackingwithswift.com/forums/100-days-of-swiftui/jump-focus-between-a-series-of-textfields-pin-code-style-entry-widget/765
I've come up with the following implementation for array of textfields.
struct NextLineTextField: UIViewRepresentable {
@Binding var text: String
@Binding var selectedField: Int
var tag: Int
var keyboardType: UIKeyboardType = .asciiCapable
var returnKey: UIReturnKeyType = .next
func makeUIView(context: UIViewRepresentableContext<NextLineTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.keyboardType = keyboardType
textField.returnKeyType = returnKey
textField.tag = tag
return textField
}
func makeCoordinator() -> NextLineTextField.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<NextLineTextField>) {
uiView.text = text
context.coordinator.newSelection = { newSelection in
DispatchQueue.main.async {
self.selectedField = newSelection
}
}
if uiView.tag == self.selectedField {
uiView.becomeFirstResponder()
}
}
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
var newSelection: (Int) -> () = { _ in }
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
DispatchQueue.main.async {
self.text = textField.text ?? ""
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
self.newSelection(textField.tag)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField.returnKeyType == .done {
textField.resignFirstResponder()
} else {
self.newSelection(textField.tag + 1)
}
return true
}
}
}
Then make form element as
class FieldElement: ObservableObject, Identifiable {
var id = UUID()
var title = ""
@Published var value = ""
var keyboard: UIKeyboardType = .asciiCapable
var returnType: UIReturnKeyType = .next
init(title: String, value: String = "", keyboard: UIKeyboardType =
.asciiCapable, returnType: UIReturnKeyType = .next) {
self.title = title
self.value = value
self.keyboard = keyboard
self.returnType = returnType
}
}
And for implementation
struct FormView: View {
@State var formElements: [FieldElement] = [
FieldElement(title: "Name"),
FieldElement(title: "Address"),
FieldElement(title: "Phone Number"),
FieldElement(title: "Email Address", keyboard: .emailAddress, returnType:
.done),
]
@State var selectedField = 0
var body: some View {
VStack(alignment: .leading) {
ForEach(Array(zip(formElements.indices, formElements)), id: \.0) {
index, element in
VStack(alignment: .leading, spacing: 0) {
Text(element.title)
NextLineTextField(text: self.$formElements[index].value,
selectedField: self.$selectedField,
tag: index,
keyboardType: element.keyboard,
returnKey: element.returnType)
.frame(height: 35)
.frame(maxWidth: .infinity)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.5), lineWidth: 0.7)
)
}.padding(.bottom, 4)
}
Button(action: {
print(self.formElements.map({ $0.value }))
}) {
Text("Print Entered Values")
.foregroundColor(Color.white)
.font(.body)
.padding()
}.frame(height: 50)
.background(Color.green)
.cornerRadius(8)
.padding(.vertical, 10)
Spacer()
}.padding()
}
}
If this is hard to navigate, feel free to look into https://github.com/prakshapan/Utilities/blob/master/FormView.swift
Upvotes: 1
Reputation: 5042
To resolve your two problems, you need to work with UIKit from SwiftUI. First, you need to customized TextField using UIViewRepresentable. Here is the sample code for test purposes though the code is not so elegance. I bet, there will be having a more robust solution.
Here is the sample code:
import SwiftUI
struct KeyboardTypeView: View {
@State var firstName = ""
@State var lastName = ""
@State var focused: [Bool] = [true, false]
var body: some View {
Form {
Section(header: Text("Your Info")) {
TextFieldTyped(keyboardType: .default, returnVal: .next, tag: 0, text: self.$firstName, isfocusAble: self.$focused)
TextFieldTyped(keyboardType: .default, returnVal: .done, tag: 1, text: self.$lastName, isfocusAble: self.$focused)
Text("Full Name :" + self.firstName + " " + self.lastName)
}
}
}
}
struct TextFieldTyped: UIViewRepresentable {
let keyboardType: UIKeyboardType
let returnVal: UIReturnKeyType
let tag: Int
@Binding var text: String
@Binding var isfocusAble: [Bool]
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.keyboardType = self.keyboardType
textField.returnKeyType = self.returnVal
textField.tag = self.tag
textField.delegate = context.coordinator
textField.autocorrectionType = .no
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
if isfocusAble[tag] {
uiView.becomeFirstResponder()
} else {
uiView.resignFirstResponder()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: TextFieldTyped
init(_ textField: TextFieldTyped) {
self.parent = textField
}
func updatefocus(textfield: UITextField) {
textfield.becomeFirstResponder()
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if parent.tag == 0 {
parent.isfocusAble = [false, true]
parent.text = textField.text ?? ""
} else if parent.tag == 1 {
parent.isfocusAble = [false, false]
parent.text = textField.text ?? ""
}
return true
}
}
}
Upvotes: 20
Reputation: 4503
You can't, there is no concept of a responder chain in SwiftUI yet. You can't programmatically initiate focus on any View
because they aren't actually the views themselves, merely structs that describe how the views should be set up. My guess it may eventually be exposed via EnvironmentValues
(like line truncation, autocorrection, etc.) but it doesn't currently exist.
Upvotes: 1