使用Swift5.1.2,iOS13.2,Xcode-11.2和
Stackview中有几个TextField,我希望在用户在第一个TextField中输入x个字符后立即移到下一个TextField。
使用this link,我可以识别出TextField条目何时达到x个字符。但是,我不知道如何使firstResponder跳转到StackView中的第二个TextField。
SwiftUI是否可以解决此问题?
答案 0 :(得分:2)
我为此专门使用UITextField
和UIViewRepresentable
编写了一个类。
您需要定义每个文本字段的tag
并声明所有布尔值的列表,这些布尔值的返回键将要关注的可用文本字段的数量相同,fieldFocus
将跟踪接下来要关注的文本字段基于当前索引/标签。
用法:
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 ?? ""
}
}
}
答案 1 :(得分:1)
今年,Apple 推出了一个新的修饰符以及一个名为 @FocusState
的新包装器,用于控制键盘和聚焦键盘(“又名”firstResponder)的状态。
以下是如何迭代 textFields 的示例:
此外,您可以查看 this answer 以了解如何制作 textField 第一响应者或辞职以隐藏键盘并了解有关如何将此枚举绑定到的更多信息文本字段。
答案 2 :(得分:0)
尝试一下:
import SwiftUI
struct ResponderTextField: UIViewRepresentable {
typealias TheUIView = UITextField
var isFirstResponder: Bool
var configuration = { (view: TheUIView) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> TheUIView { TheUIView() }
func updateUIView(_ uiView: TheUIView, context: UIViewRepresentableContext<Self>) {
_ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
configuration(uiView)
}
}
struct ContentView: View {
@State private var entry = ""
@State private var entry2 = ""
let characterLimit = 6
var body: some View {
VStack {
TextField("hallo", text: $entry)
.disabled(entry.count > (characterLimit - 1))
ResponderTextField(isFirstResponder: entry.count > (characterLimit - 1)) { uiView in
uiView.placeholder = "2nd textField"
}
}
}
}
答案 3 :(得分:0)
我能够使用Introspect库完成此任务。 https://github.com/siteline/SwiftUI-Introspect:
@State private var passcode = ""
HStack {
TextField("", text: self.$passcode)
.introspectTextField { textField in
if self.passcode.count >= 1 {
textField.resignFirstResponder()
} else if self.passcode.count < 1 {
textField.becomeFirstResponder()
}
}
TextField("", text: self.$passcode)
.introspectTextField { textField in
if self.passcode.count >= 2
textField.resignFirstResponder()
} else if self.passcode.count < 2 {
textField.becomeFirstResponder()
}
}
}
我可能从尝试复制和粘贴我的代码中弄乱了实现,但是您了解了它的工作原理。
答案 4 :(得分:0)
我已经接受了@Philip Borbon 的回答并对其进行了一些清理。我删除了很多自定义内容,并保留在最低限度,以便更容易地了解需要什么。
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 ?? ""
}
}
}
答案 5 :(得分:-1)
可以按照 here 给出的答案进行操作。
很可能以其他方式做到这一点,但这是一个简单案例的解决方案,例如 OP 的问题。
答案 6 :(得分:-1)
在 iOS 15 中,我们现在可以使用 @FocusState
来控制应该关注哪个字段。
这是一个演示:
struct ContentView: View {
@State private var street: String = ""
@State private var city: String = ""
@State private var country: String = ""
@FocusState private var focusedField: Field?
var body: some View {
NavigationView {
VStack {
TextField("Street", text: $street)
.focused($focusedField, equals: .street)
TextField("City", text: $city)
.focused($focusedField, equals: .city)
TextField("Country", text: $country)
.focused($focusedField, equals: .country)
}
.toolbar {
ToolbarItem(placement: .keyboard) {
Button(action: focusPreviousField) {
Image(systemName: "chevron.up")
}
.disabled(!canFocusPreviousField()) // remove this to loop through fields
}
ToolbarItem(placement: .keyboard) {
Button(action: focusNextField) {
Image(systemName: "chevron.down")
}
.disabled(!canFocusNextField()) // remove this to loop through fields
}
}
}
}
}
extension ContentView {
private enum Field: Int, CaseIterable {
case street, city, country
}
private func focusPreviousField() {
focusedField = focusedField.map {
Field(rawValue: $0.rawValue - 1) ?? .country
}
}
private func focusNextField() {
focusedField = focusedField.map {
Field(rawValue: $0.rawValue + 1) ?? .street
}
}
private func canFocusPreviousField() -> Bool {
guard let currentFocusedField = focusedField else {
return false
}
return currentFocusedField.rawValue > 0
}
private func canFocusNextField() -> Bool {
guard let currentFocusedField = focusedField else {
return false
}
return currentFocusedField.rawValue < Field.allCases.count - 1
}
}
注意:从 Xcode 13 beta 1 开始,@FocusState
在 Form
/List
中不起作用。这应该在下一个版本中修复。