如何通过单击键盘上的 return 按钮来浏览 SwiftUI TextFields?
How to navigate through SwiftUI TextFields by clicking on return button from keyboard?
我正在与 SwiftUI 的 TextField View
合作。我主要有2个问题:
在Swift中,我们可以将 Return Key(Text Input Traits) 设置为 Next
for TextField from这样的故事板对吗?为此,在 SwiftUI?
中使用哪个修饰符
我有两个文本字段,当我从键盘单击 return/next 按钮时如何导航到下一个文本字段?
任何人都可以使用 SwiftUI(不是 UIKit)来帮助解决这个问题吗?或者执行此功能的任何其他替代方法?
你不能,SwiftUI 中还没有响应链的概念。您不能以编程方式启动对任何 View
的关注,因为它们实际上不是视图本身,而只是描述视图应如何设置的结构。我猜它最终可能会通过 EnvironmentValues
公开(例如行截断、自动更正等),但它目前不存在。
要解决您的两个问题,您需要使用 SwiftUI 中的 UIKit。首先,您需要使用 UIViewRepresentable 自定义 TextField。这是用于测试目的的示例代码,尽管代码不是那么优雅。我敢打赌,会有更强大的解决方案。
- 在自定义的 TextFieldType 中,键盘 return 类型有
已设置。
- 通过使用对象绑定和委托方法
textFieldShouldReturn,View可以通过更新绑定变量来聚焦键盘。
示例代码如下:
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
}
}
}
输出:
基于 Razib Mollick 的回答和
https://www.hackingwithswift.com/forums/100-days-of-swiftui/jump-focus-between-a-series-of-textfields-pin-code-style-entry-widget/765
我为文本字段数组提出了以下实现。
struct NextLineTextField: UIViewRepresentable {
@Binding var text: String
@Binding var selectedField: Int
var tag: Int
var keyboardType: UIKeyboardType = .asciiCapable
var returnKey: UIReturnKeyType = .next
func makeUIView(context: UIViewRepresentableContext<NextLineTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.keyboardType = keyboardType
textField.returnKeyType = returnKey
textField.tag = tag
return textField
}
func makeCoordinator() -> NextLineTextField.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<NextLineTextField>) {
uiView.text = text
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
var newSelection: (Int) -> () = { _ in }
init(text: Binding<String>) {
_text = text
}
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
}
}
}
然后将表单元素设为
class FieldElement: ObservableObject, Identifiable {
var id = UUID()
var title = ""
@Published var value = ""
var keyboard: UIKeyboardType = .asciiCapable
var returnType: UIReturnKeyType = .next
init(title: String, value: String = "", keyboard: UIKeyboardType =
.asciiCapable, returnType: UIReturnKeyType = .next) {
self.title = title
self.value = value
self.keyboard = keyboard
self.returnType = returnType
}
}
并实施
struct FormView: View {
@State var formElements: [FieldElement] = [
FieldElement(title: "Name"),
FieldElement(title: "Address"),
FieldElement(title: "Phone Number"),
FieldElement(title: "Email Address", keyboard: .emailAddress, returnType:
.done),
]
@State var selectedField = 0
var body: some View {
VStack(alignment: .leading) {
ForEach(Array(zip(formElements.indices, formElements)), id: \.0) {
index, element in
VStack(alignment: .leading, spacing: 0) {
Text(element.title)
NextLineTextField(text: self.$formElements[index].value,
selectedField: self.$selectedField,
tag: index,
keyboardType: element.keyboard,
returnKey: element.returnType)
.frame(height: 35)
.frame(maxWidth: .infinity)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.5), lineWidth: 0.7)
)
}.padding(.bottom, 4)
}
Button(action: {
print(self.formElements.map({ [=12=].value }))
}) {
Text("Print Entered Values")
.foregroundColor(Color.white)
.font(.body)
.padding()
}.frame(height: 50)
.background(Color.green)
.cornerRadius(8)
.padding(.vertical, 10)
Spacer()
}.padding()
}
}
如果这很难导航,请随时查看
https://github.com/prakshapan/Utilities/blob/master/FormView.swift
截至 2021 年 6 月 15 日,这仍处于测试阶段。
iOS 15.0+
macOS 12.0+,
Mac 催化剂 15.0+,
tvOS 15.0+,
watchOS 8.0+
Use submitLabel(_:)
view modifier that sets the submit label for a view. It takes a predefined case specified in SubmitLabel
Use .next
. It defines a submit label with text of “Next”.
Use onFocus(_:)
to find when the modified view hierarchy, in this case the TextField
, loses focus. When it does, put the focus on the next view (SecureField
)
struct LoginForm: View {
enum Field: Hashable {
case usernameField
case passwordField
}
@State private var username = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: .usernameField)
.submitLabel(.next)
.onFocus { isFocused in
if (!isFocused) {
focusedField = .passwordField
}
}
SecureField("Password", text: $password)
.focused($focusedField, equals: .passwordField)
.submitLabel(.done)
}
}
}
更新
我在 xCode version 13.0 beta 5 (13A5212g)
中对此进行了测试。有效。
我正在与 SwiftUI 的 TextField View
合作。我主要有2个问题:
在Swift中,我们可以将 Return Key(Text Input Traits) 设置为
中使用哪个修饰符Next
for TextField from这样的故事板对吗?为此,在 SwiftUI?我有两个文本字段,当我从键盘单击 return/next 按钮时如何导航到下一个文本字段?
任何人都可以使用 SwiftUI(不是 UIKit)来帮助解决这个问题吗?或者执行此功能的任何其他替代方法?
你不能,SwiftUI 中还没有响应链的概念。您不能以编程方式启动对任何 View
的关注,因为它们实际上不是视图本身,而只是描述视图应如何设置的结构。我猜它最终可能会通过 EnvironmentValues
公开(例如行截断、自动更正等),但它目前不存在。
要解决您的两个问题,您需要使用 SwiftUI 中的 UIKit。首先,您需要使用 UIViewRepresentable 自定义 TextField。这是用于测试目的的示例代码,尽管代码不是那么优雅。我敢打赌,会有更强大的解决方案。
- 在自定义的 TextFieldType 中,键盘 return 类型有 已设置。
- 通过使用对象绑定和委托方法 textFieldShouldReturn,View可以通过更新绑定变量来聚焦键盘。
示例代码如下:
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
}
}
}
输出:
基于 Razib Mollick 的回答和 https://www.hackingwithswift.com/forums/100-days-of-swiftui/jump-focus-between-a-series-of-textfields-pin-code-style-entry-widget/765
我为文本字段数组提出了以下实现。
struct NextLineTextField: UIViewRepresentable {
@Binding var text: String
@Binding var selectedField: Int
var tag: Int
var keyboardType: UIKeyboardType = .asciiCapable
var returnKey: UIReturnKeyType = .next
func makeUIView(context: UIViewRepresentableContext<NextLineTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.delegate = context.coordinator
textField.keyboardType = keyboardType
textField.returnKeyType = returnKey
textField.tag = tag
return textField
}
func makeCoordinator() -> NextLineTextField.Coordinator {
return Coordinator(text: $text)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<NextLineTextField>) {
uiView.text = text
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
var newSelection: (Int) -> () = { _ in }
init(text: Binding<String>) {
_text = text
}
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
}
}
}
然后将表单元素设为
class FieldElement: ObservableObject, Identifiable {
var id = UUID()
var title = ""
@Published var value = ""
var keyboard: UIKeyboardType = .asciiCapable
var returnType: UIReturnKeyType = .next
init(title: String, value: String = "", keyboard: UIKeyboardType =
.asciiCapable, returnType: UIReturnKeyType = .next) {
self.title = title
self.value = value
self.keyboard = keyboard
self.returnType = returnType
}
}
并实施
struct FormView: View {
@State var formElements: [FieldElement] = [
FieldElement(title: "Name"),
FieldElement(title: "Address"),
FieldElement(title: "Phone Number"),
FieldElement(title: "Email Address", keyboard: .emailAddress, returnType:
.done),
]
@State var selectedField = 0
var body: some View {
VStack(alignment: .leading) {
ForEach(Array(zip(formElements.indices, formElements)), id: \.0) {
index, element in
VStack(alignment: .leading, spacing: 0) {
Text(element.title)
NextLineTextField(text: self.$formElements[index].value,
selectedField: self.$selectedField,
tag: index,
keyboardType: element.keyboard,
returnKey: element.returnType)
.frame(height: 35)
.frame(maxWidth: .infinity)
.overlay(
RoundedRectangle(cornerRadius: 8)
.stroke(Color.gray.opacity(0.5), lineWidth: 0.7)
)
}.padding(.bottom, 4)
}
Button(action: {
print(self.formElements.map({ [=12=].value }))
}) {
Text("Print Entered Values")
.foregroundColor(Color.white)
.font(.body)
.padding()
}.frame(height: 50)
.background(Color.green)
.cornerRadius(8)
.padding(.vertical, 10)
Spacer()
}.padding()
}
}
如果这很难导航,请随时查看 https://github.com/prakshapan/Utilities/blob/master/FormView.swift
截至 2021 年 6 月 15 日,这仍处于测试阶段。
iOS 15.0+
macOS 12.0+, Mac 催化剂 15.0+, tvOS 15.0+, watchOS 8.0+
Use
submitLabel(_:)
view modifier that sets the submit label for a view. It takes a predefined case specified inSubmitLabel
Use
.next
. It defines a submit label with text of “Next”.
Use
onFocus(_:)
to find when the modified view hierarchy, in this case theTextField
, loses focus. When it does, put the focus on the next view (SecureField
)
struct LoginForm: View {
enum Field: Hashable {
case usernameField
case passwordField
}
@State private var username = ""
@State private var password = ""
@FocusState private var focusedField: Field?
var body: some View {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: .usernameField)
.submitLabel(.next)
.onFocus { isFocused in
if (!isFocused) {
focusedField = .passwordField
}
}
SecureField("Password", text: $password)
.focused($focusedField, equals: .passwordField)
.submitLabel(.done)
}
}
}
更新
我在 xCode version 13.0 beta 5 (13A5212g)
中对此进行了测试。有效。