UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

SOLVED: Jump focus between a series of TextFields (pin code style entry widget)

Forums > 100 Days of SwiftUI

I am attempting to construct a pin code / two factor auth sytle widget, my approach si encapsulating a TextView as a comopnent that keeps track of each digit/input and present a series of inputs along side each other.

The pin code component looks like:

import SwiftUI

struct SectionedTextField: View {

    @State private var numberOfCells: Int = 8

    var body: some View {
        HStack {
            ForEach(0 ..< self.numberOfCells) { index in 
                CharacterInputCell()
            }
        }
    }
}

struct SectionedTextField_Previews: PreviewProvider {
    static var previews: some View {
        SectionedTextField()
    }
}

and the Component that encapsulates the TextField:

import SwiftUI

struct CharacterInputCell: View {

    @State private var textValue: String = ""

    var body: some View {
        TextField("", text: $textValue, onEditingChanged: onEditingChanged(_:), onCommit: onCommit)
        .frame(maxWidth: .infinity, alignment: .center)
        .padding([.trailing, .leading], 10)
        .padding([.vertical], 15)
        .lineLimit(1)
        .multilineTextAlignment(.center)
        .overlay(
            RoundedRectangle(cornerRadius: 6)
                .stroke(Color.red.opacity(0.5), lineWidth: 2)
        )
    }

    func onCommit() {
        print("commit")
    }

    func onEditingChanged(_ changed: Bool) {
        print(changed)
    }
}

struct CharacterInputCell_Previews: PreviewProvider {
    static var previews: some View {
        CharacterInputCell()
    }
}

Questions:

  • How can I go about moving the focus from one TextView to another as the user enters a digit per text box
  • How can I update a combined of the TextView objects into a single State or Binding property
  • OR what can I do to raise events from the CharacterInputCell to propagate events

I am mostly struggling with converting my UIKit way of thinking to SwiftUI and would appreciate a nudge in the right edrection.

Appreciate you sparing your time to read/respond.

4      

I did play around with this, and got some solution, far from ideal as there are in my opinion many little things to implement more, but at it's basic when user types a digit the UITextField firstResponder is moved to next one. Couldn't yet implement that when user presses clear or x button on keyboard that firsResponder will move backwards to previous cell. This was quite fun to practice with integrating UIKit in SwiftUI using coordinators etc.

struct SectionedTextField: View {
    @State private var numberOfCells: Int = 8
    @State private var currentlySelectedCell = 0

    var body: some View {
        HStack {
            ForEach(0 ..< self.numberOfCells) { index in
                CharacterInputCell(currentlySelectedCell: self.$currentlySelectedCell, index: index)
            }
        }
    }
}
struct CharacterInputCell: View {
    @State private var textValue: String = ""
    @Binding var currentlySelectedCell: Int

    var index: Int

    var responder: Bool {
        return index == currentlySelectedCell
    }

    var body: some View {
        CustomTextField(text: $textValue, currentlySelectedCell: $currentlySelectedCell, isFirstResponder: responder)
            .frame(height: 20)
            .frame(maxWidth: .infinity, alignment: .center)
            .padding([.trailing, .leading], 10)
            .padding([.vertical], 15)
            .lineLimit(1)
            .multilineTextAlignment(.center)
            .overlay(
                RoundedRectangle(cornerRadius: 6)
                    .stroke(Color.red.opacity(0.5), lineWidth: 2)
            )
    }
}
struct CustomTextField: UIViewRepresentable {

    class Coordinator: NSObject, UITextFieldDelegate {

        @Binding var text: String
        @Binding var currentlySelectedCell: Int

        var didBecomeFirstResponder = false

        init(text: Binding<String>, currentlySelectedCell: Binding<Int>) {
            _text = text
            _currentlySelectedCell = currentlySelectedCell
        }

        func textFieldDidChangeSelection(_ textField: UITextField) {
            DispatchQueue.main.async {
                self.text = textField.text ?? ""
            }
        }

        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            let currentText = textField.text ?? ""

            guard let stringRange = Range(range, in: currentText) else { return false }

            let updatedText = currentText.replacingCharacters(in: stringRange, with: string)

            if updatedText.count <= 1 {
                self.currentlySelectedCell += 1
            }

            return updatedText.count <= 1
        }
    }

    @Binding var text: String
    @Binding var currentlySelectedCell: Int
    var isFirstResponder: Bool = false

    func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
        let textField = UITextField(frame: .zero)
        textField.delegate = context.coordinator
        textField.textAlignment = .center
        textField.keyboardType = .decimalPad
        return textField
    }

    func makeCoordinator() -> CustomTextField.Coordinator {
        return Coordinator(text: $text, currentlySelectedCell: $currentlySelectedCell)
    }

    func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
        uiView.text = text
        if isFirstResponder && !context.coordinator.didBecomeFirstResponder  {
            uiView.becomeFirstResponder()
            context.coordinator.didBecomeFirstResponder = true
        }
    }
}

See if that works for you!

UITextField

6      

First of all a big thank you to @Sangsom for posting his reply. I have been working on formalising the solution to a Pass Code Input like widget. The soruce can be found on Github

Thoughts, comments and revisions are welcome.

4      

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

Archived topic

This topic has been closed due to inactivity, so you can't reply. Please create a new topic if you need to.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.