How to move to next TextField in SwiftUI?
iOS 15+
Use @FocusState
Before iOS 15
I've taken @Philip Borbon answer and cleaned it up a little bit. I've removed a lot of the customization and kept in the bare minimum to make it easier to see what's required.
struct CustomTextfield: UIViewRepresentable {
let label: String
@Binding var text: String
var focusable: Binding<[Bool]>? = nil
var returnKeyType: UIReturnKeyType = .default
var tag: Int? = nil
var onCommit: (() -> Void)? = nil
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.placeholder = label
textField.delegate = context.coordinator
textField.returnKeyType = returnKeyType
if let tag = tag {
textField.tag = tag
}
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
if let focusable = focusable?.wrappedValue {
var resignResponder = true
for (index, focused) in focusable.enumerated() {
if uiView.tag == index && focused {
uiView.becomeFirstResponder()
resignResponder = false
break
}
}
if resignResponder {
uiView.resignFirstResponder()
}
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
final class Coordinator: NSObject, UITextFieldDelegate {
let parent: CustomTextfield
init(_ parent: CustomTextfield) {
self.parent = parent
}
func textFieldDidBeginEditing(_ textField: UITextField) {
guard var focusable = parent.focusable?.wrappedValue else { return }
for i in 0...(focusable.count - 1) {
focusable[i] = (textField.tag == i)
}
parent.focusable?.wrappedValue = focusable
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard var focusable = parent.focusable?.wrappedValue else {
textField.resignFirstResponder()
return true
}
for i in 0...(focusable.count - 1) {
focusable[i] = (textField.tag + 1 == i)
}
parent.focusable?.wrappedValue = focusable
if textField.tag == focusable.count - 1 {
textField.resignFirstResponder()
}
return true
}
@objc func textFieldDidChange(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
}
}
SwiftUI: How to move/jump to next TextField without hiding keyboard?
Here is a functional example to understand what I meant by my question.
struct ContentView: View {
@FocusState private var focusedField: Int?
var body: some View {
HStack(spacing: 3) {
ForEach(0..<10, id: \.self) { index in
Stone(focusedField: _focusedField, index: index)
}
}
.padding(.horizontal)
}
}
struct Stone: View {
@FocusState var focusedField: Int?
@State var index: Int
@State private var text: String = ""
var body: some View {
TextField("", text: $text)
.focused($focusedField, equals: index)
.textFieldStyle(.roundedBorder)
.onChange(of: text) { newValue in
setFocus(index: index)
}
}
func setFocus(index: Int) {
/// Default to the first box when focus is not set or the user reaches the last box
if focusedField == nil || focusedField == 9 {
focusedField = 0
} else {
/// Another safety check for the index
if index == 9 {
focusedField = 0
} else {
focusedField = index + 1
}
}
}
}
Thanks to all!
How to navigate through SwiftUI TextFields by clicking on return button from keyboard?
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.
- Inside the customized TextFieldType, the Keyboard return type has
been set. - By using object binding and delegate methods
textFieldShouldReturn, View can focus the keyboard by updating the binding variables.
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
}
}
}
Output:
Focus on the next TextField/SecureField in SwiftUI
When using UIKit, one would accomplish this by setting up the responder chain. This isn't available in SwiftUI, so until there is a more sophisticated focus and responder system, you can make use of the onEditingChanged
changed of TextField
You will then need to manage the state of each field based on stored State variables. It may end up being more work than you want to do.
Fortunately, you can fall back to UIKit in SwiftUI by using UIViewRepresentable.
Here is some code that manages the focus of text fields using the UIKit responder system:
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
}
}
}
You can refer to this question to get more information about this particular approach.
Hope this helps!
How to move to next field programmatically in nested custom form with custom fields in SwiftUI
I found my issue, I was using @State
to track the current focused field, which is getting reset every time the view changes.
So I had to us @ObservableObject
with @Published
property.
Here is my final working code.
Custom field
enum InputFieldType {
case text, number, dropdown
}
struct CustomField: View {
let tag: Int
let type: InputFieldType
let title: String
var dropdownItems: Array<String> = []
var placeholder: String = ""
@State var text: String = ""
@State var enabled: Bool = true
@Binding var focusTag: Int
@FocusState private var focusField: Bool
private let dropdownImage = Image(systemName: "chevron.down")
@State private var showDropdown: Bool = false
var body: some View {
VStack(alignment: .leading, spacing: 8.0) {
Text(title)
.foregroundColor(.gray)
.frame(alignment: .leading)
ZStack(alignment: .leading) {
// The placeholder view
Text(placeholder).foregroundColor( enabled ? .gray.opacity(0.3) : .gray.opacity(0.5))
.opacity(text.isEmpty ? 1 : 0)
.padding(.horizontal, 8)
// Text field
TextField("", text: $text)
.disabled(!enabled)
.frame(height: 44)
.textInputAutocapitalization(.sentences)
.foregroundColor(.white)
.padding(.horizontal, 8)
.keyboardType( type == .number ? .decimalPad : .default)
.onChange(of: focusTag, perform: { newValue in
focusField = newValue == tag
})
.focused($focusField)
.onChange(of: focusField, perform: { newValue in
if type != .dropdown, newValue, focusTag != tag {
focusTag = tag
}
})
}.background(Color.red.opacity(0.1))
.cornerRadius(5)
}
}
}
Form view
fileprivate class FocusStateObserver: ObservableObject {
@Published var focusFieldTag: Int = -1
}
struct FormView: View {
@ObservedObject private var focusStateObserver = FocusStateObserver()
func moveToNextField() -> Bool {
if focusStateObserver.focusFieldTag < 4 {
switch focusStateObserver.focusFieldTag {
case 0:
focusStateObserver.focusFieldTag = 1
case 1:
focusStateObserver.focusFieldTag = 2
case 2:
focusStateObserver.focusFieldTag = 4
default:
break
}
return true
}
return false
}
var body: some View {
VStack {
ScrollView(.vertical) {
VStack(spacing: 24) {
CustomField(tag: 0, type: .text, title: "First name", placeholder: "John", text: "", enabled: false, focusTag: $focusStateObserver.focusFieldTag)
CustomField(tag: 1, type: .text, title: "Surname", placeholder: "Mike", text: "", focusTag: $focusStateObserver.focusFieldTag)
CustomField(tag: 2, type: .text, title: "Gender (Optional)", placeholder: "Optional", text: "", focusTag: $focusStateObserver.focusFieldTag)
CustomField(tag: 3, type: .dropdown, title: "Body type", dropdownItems: ["1", "2", "3"], placeholder: "Skinny", text: "", focusTag: $focusStateObserver.focusFieldTag)
CustomField(tag: 4, type: .number, title: "Year of birth", placeholder: "2000", text: "", focusTag: $focusStateObserver.focusFieldTag)
Spacer()
}
}
}.onTapGesture {
endEditing()
}
.background(Color.clear)
.padding(.horizontal, 16)
}
}
extension View {
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Content view
struct ContentView: View {
let formView = FormView()
var body: some View {
VStack {
Spacer(minLength: 30)
formView
.padding(.vertical)
Button("Next") {
if formView.moveToNextField() {
return
}
endEditing()
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 44, maxHeight: 44, alignment: .center)
.background(Color.secondary)
.cornerRadius(5)
.padding(.horizontal, 16)
Spacer(minLength: 20)
}.background(Color.primary)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().preferredColorScheme(.dark)
}
}
How can one change the FocusState of a SwiftUI app with TextFields in different child views without having View refresh which causes a bounce effect?
After going through this a bunch of times, it dawned on me that when using FocusState
you really should be in a ScrollView
, Form
or some other type of greedy view. Even a GeometryReader
will work. Any of these will remove the bounce.
struct MyObject: Identifiable, Equatable {
public let id = UUID()
public var name: String
public var value: String
}
class MyObjViewModel: ObservableObject {
@Published var myObjects: [MyObject]
init(_ objects: [MyObject]) {
myObjects = objects
}
}
struct ContentView: View {
@StateObject var viewModel = MyObjViewModel([
MyObject(name: "aa", value: "1"),
MyObject(name: "bb", value: "2"),
MyObject(name: "cc", value: "3"),
MyObject(name: "dd", value: "4")
])
@State var focus: UUID?
var body: some View {
VStack {
Form {
Text("Header")
ForEach($viewModel.myObjects) { $obj in
FocusField(object: $obj, focus: $focus, nextFocus: {
guard let index = viewModel.myObjects.map( { $0.id }).firstIndex(of: obj.id) else {
return
}
focus = viewModel.myObjects.indices.contains(index + 1) ? viewModel.myObjects[index + 1].id : viewModel.myObjects[0].id
})
}
Text("Footer")
}
}
}
}
struct FocusField: View {
@Binding var object: MyObject
@Binding var focus: UUID?
var nextFocus: () -> Void
@FocusState var isFocused: UUID?
var body: some View {
TextField("Test", text: $object.value)
.onChange(of: focus, perform: { newValue in
self.isFocused = newValue
})
.focused(self.$isFocused, equals: object.id)
.onSubmit {
self.nextFocus()
}
}
}
edit:
Also, it is a really bad idea to set id
in the struct the way you did. An id should be unique. It works here, but best practice is a UUID
.
Second edit: tightened up the code.
How to move to the next UITextField automatically in Swift
In viewDidLoad :-
ourTextField?.addTarget(self, action: #selector(CalculatorViewController.textFieldDidChange(_:)), for: UIControlEvents.editingChanged)
forThemTextField?.addTarget(self, action: #selector(CalculatorViewController.textFieldDidChange(_:)), for: UIControlEvents.editingChanged)
//create function
func textFieldDidChange(_ textField: UITextField) {
if textField == ourTextField {
if (textField.text.count)! >= 2 {
forThemTextField?.becomeFirstResponder()
}
}
else if textField == forThemTextField {
if (textField.text?.count)! >= 2 {
forThemTextField.resignFirstResponder()
}
}
}
Related Topics
Using Combine's Future to Replicate Async Await in Swift
How to Make an Array of the Current Week Dates Swift
How to Generate Large, Ranged Random Numbers in Swift
Swift Performsegue Going to Xcode
Swift Struct Type Recursive Value
How to Clear the Terminal Screen in Swift
What Is the Swift Syntax " .Bar" Called
Swift Date(Byadding:To:) Returns Nil for Trivial Calculation in Repl
Initialize Class-Instance and Access Variables in Swift
Swiftui iOS 14 Widget Countdown
Simple Swift Fibonacci Program Crashing (Project Euler 2)
Order of Modifiers in Swiftui View Impacts View Appearance
Rounding in Swift with Round()
How to Reason When I Have to Choose Between a Class, Struct and Enum in Swift
How to Replicate Promisekit-Style Chained Async Flow Using Combine + Swift