I'm using UITextField
and UIViewRepresentable
to achieve this.
Define tag
of each text field and declare a list of booleans with same count of available text fields to be focused of return key, fieldFocus
, that will keep track of which textfield to focus next base on the current index/tag.
Usage:
import SwiftUI
struct Sample: View {
@State var firstName: String = ""
@State var lastName: String = ""
@State var fieldFocus = [false, false]
var body: some View {
VStack {
KitTextField (
label: "First name",
text: $firstName,
focusable: $fieldFocus,
returnKeyType: .next,
tag: 0
)
.padding()
.frame(height: 48)
KitTextField (
label: "Last name",
text: $lastName,
focusable: $fieldFocus,
returnKeyType: .done,
tag: 1
)
.padding()
.frame(height: 48)
}
}
}
UITextField
in UIViewRepresentable
:
import SwiftUI
struct KitTextField: UIViewRepresentable {
let label: String
@Binding var text: String
var focusable: Binding<[Bool]>? = nil
var isSecureTextEntry: Binding<Bool>? = nil
var returnKeyType: UIReturnKeyType = .default
var autocapitalizationType: UITextAutocapitalizationType = .none
var keyboardType: UIKeyboardType = .default
var textContentType: UITextContentType? = nil
var tag: Int? = nil
var inputAccessoryView: UIToolbar? = nil
var onCommit: (() -> Void)? = nil
func makeUIView(context: Context) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.placeholder = label
textField.returnKeyType = returnKeyType
textField.autocapitalizationType = autocapitalizationType
textField.keyboardType = keyboardType
textField.isSecureTextEntry = isSecureTextEntry?.wrappedValue ?? false
textField.textContentType = textContentType
textField.textAlignment = .left
if let tag = tag {
textField.tag = tag
}
textField.inputAccessoryView = inputAccessoryView
textField.addTarget(context.coordinator, action: #selector(Coordinator.textFieldDidChange(_:)), for: .editingChanged)
textField.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
uiView.isSecureTextEntry = isSecureTextEntry?.wrappedValue ?? false
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 control: KitTextField
init(_ control: KitTextField) {
self.control = control
}
func textFieldDidBeginEditing(_ textField: UITextField) {
guard var focusable = control.focusable?.wrappedValue else { return }
for i in 0...(focusable.count - 1) {
focusable[i] = (textField.tag == i)
}
control.focusable?.wrappedValue = focusable
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard var focusable = control.focusable?.wrappedValue else {
textField.resignFirstResponder()
return true
}
for i in 0...(focusable.count - 1) {
focusable[i] = (textField.tag + 1 == i)
}
control.focusable?.wrappedValue = focusable
if textField.tag == focusable.count - 1 {
textField.resignFirstResponder()
}
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
control.onCommit?()
}
@objc func textFieldDidChange(_ textField: UITextField) {
control.text = textField.text ?? ""
}
}
}
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…