How to Move to Next Textfield in Swiftui

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.

  1. Inside the customized TextFieldType, the Keyboard return type has
    been set.
  2. 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:
Sample Image

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



Leave a reply



Submit