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?
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
}
}
}
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)
}
}
}
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.
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
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.