2

Imagine that I have 6 TextFields arranged in a grid with 3 columns and 2 rows. We'll refer to them by their X,Y position in this grid, starting with 1,1 in the upper left TextField, and 3,2 in the lower right corner.

When I run this program, I put the cursor in TextField 1,1 and enter a value. I hit tab and the cursor goes to 2,1; then to 3,1; then to 1,2; then 2,2; and finally to 3,2.

The cursor is marching horizontally across the first row, then the second, and so on.

I need to change that order, though.

I need it to march down column 1, before moving to column 2, and then on to column 3.

In Cocoa programming, you can specify the order using nextKeyView.

Is there anything similar in SwiftUI?

I was hoping I could group them in VStacks like below and get the behavior I wanted, but it doesn't work. I also tried putting the column-pairs in Group{...} but it doesn't work.

struct ContentView: View {
@State private var oneone = ""
@State private var onetwo = ""
@State private var twoone = ""
@State private var twotwo = ""
@State private var threeone = ""
@State private var threetwo = ""

var body: some View {
    HStack {
        VStack {
            TextField("1,1", text: $oneone)

            TextField("1,2", text: $onetwo)
        }
        
        VStack {
            TextField("2,1", text: $twoone)

            TextField("2,2", text: $twotwo)
        }

        VStack {
            TextField("3,1", text: $threeone)

            TextField("3,2", text: $threetwo)

        }
    }
}

}

The Cappy
  • 623
  • 4
  • 8

1 Answers1

3

SwiftUI doesn't have this built in yet, but Swift does. To do it in SwiftUI, create a custom text field that conforms to UIViewRepresentable to use the becomeFirstResponder functionality in a text field. Here's how I did it in my project. This example has several optional variables you may not need. Feel free to tweak it to work for you.

struct CustomTextField: UIViewRepresentable
{
  @Binding var text: String
  @Binding var selectedField: Int
  @Binding var secure: Bool // Optional
  var placeholder: String = ""
  
  var tag: Int
  var keyboardType: UIKeyboardType = .asciiCapable // Optional
  var returnKey: UIReturnKeyType = .next // Optional
  var correctionType: UITextAutocorrectionType = .default // Optional
  var capitalizationType: UITextAutocapitalizationType = .sentences // Optional
  
  func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField
  {
    let textField = UITextField(frame: .zero)
    textField.delegate = context.coordinator
    textField.keyboardType = keyboardType
    textField.returnKeyType = returnKey
    textField.autocorrectionType = correctionType
    textField.autocapitalizationType = capitalizationType
    textField.tag = tag
    textField.isSecureTextEntry = secure
    textField.placeholder = placeholder
    return textField
  }
  
  func makeCoordinator() -> CustomTextField.Coordinator
  {
    return Coordinator(text: $text, secure: $secure)
  }
  
  func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>)
  {
    uiView.text = text
    uiView.isSecureTextEntry = secure
    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
    @Binding var secure: Bool
    var newSelection: (Int) -> () = { _ in }
    
    init(text: Binding<String>, secure: Binding<Bool>)
    {
      _text = text
      _secure = secure
    }
    
    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, in the view with the text fields, assign each one a different tag, indicating the desired order.

struct ContentView: View {
    @State private var oneOne = ""
    @State private var oneTwo = ""
    @State private var twoOne = ""
    @State private var twoTwo = ""
    @State private var threeOne = ""
    @State private var threeTwo = ""
    @State private var selectedField: Int = 0
    @State private var fieldsSecure: Bool = false

var body: some View {
    HStack {
        VStack {
            CustomTextField(
             text: $oneOne, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "1, 1", 
             tag: 1, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)

            CustomTextField(
             text: $oneTwo, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "1, 2", 
             tag: 4, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)
        }
        
        VStack {
            CustomTextField(
             text: $twoOne, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "2, 1", 
             tag: 2, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)

            CustomTextField(
             text: $twoTwo, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "2, 2", 
             tag: 5, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)
        }

        VStack {
            CustomTextField(
             text: $threeOne, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "3, 1", 
             tag: 3, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)

            CustomTextField(
             text: $threeTwo, 
             selectedField: $selectedField, 
             secure: $fieldsSecure, 
             placeholder: "3, 2", 
             tag: 6, 
             keyboardType: .asciiCapable, 
             returnKey: .done, 
             correctionType: .yes, 
             capitalizationType: .words)
        }
    }
}
Crystal
  • 46
  • 7