我正在使用模式将名称添加到列表中。当显示模式时,我想自动聚焦 TextField,如下所示:
我还没有找到合适的解决方案。
为了做到这一点,SwiftUI 中是否已经实现了任何东西?
感谢您的帮助。
var modal: some View {
NavigationView{
VStack{
HStack{
Spacer()
TextField("Name", text: $inputText) // autofocus this!
.textFieldStyle(DefaultTextFieldStyle())
.padding()
.font(.system(size: 25))
// something like .focus() ??
Spacer()
}
Button(action: {
if self.inputText != ""{
self.players.append(Player(name: self.inputText))
self.inputText = ""
self.isModal = false
}
}, label: {
HStack{
Text("Add \(inputText)")
Image(systemName: "plus")
}
.font(.system(size: 20))
})
.padding()
.foregroundColor(.white)
.background(Color.blue)
.cornerRadius(10)
Spacer()
}
.navigationBarTitle("New Player")
.navigationBarItems(trailing: Button(action: {self.isModal=false}, label: {Text("Cancel").font(.system(size: 20))}))
.padding()
}
}
有一个名为
@FocusState
的新包装器,它控制键盘和聚焦键盘(又名“firstResponder”)的状态。
⚠️注意,如果你想让它在初始时间集中,你必须应用延迟。这是 SwiftUI 的一个已知错误。
如果在文本字段上使用
focused
修饰符,则可以使它们成为焦点,例如,您可以在代码中设置 focusedField
属性来使绑定的文本字段变为活动状态:
或通过将变量设置为
nil
来关闭键盘:
不要忘记观看 WWDC2021 的 SwiftUI 中的直接和反映焦点会议
老但工作:
请注意根据评论中的要求添加了文本绑定支持
struct LegacyTextField: UIViewRepresentable {
@Binding public var isFirstResponder: Bool
@Binding public var text: String
public var configuration = { (view: UITextField) in }
public init(text: Binding<String>, isFirstResponder: Binding<Bool>, configuration: @escaping (UITextField) -> () = { _ in }) {
self.configuration = configuration
self._text = text
self._isFirstResponder = isFirstResponder
}
public func makeUIView(context: Context) -> UITextField {
let view = UITextField()
view.addTarget(context.coordinator, action: #selector(Coordinator.textViewDidChange), for: .editingChanged)
view.delegate = context.coordinator
return view
}
public func updateUIView(_ uiView: UITextField, context: Context) {
uiView.text = text
switch isFirstResponder {
case true: uiView.becomeFirstResponder()
case false: uiView.resignFirstResponder()
}
}
public func makeCoordinator() -> Coordinator {
Coordinator($text, isFirstResponder: $isFirstResponder)
}
public class Coordinator: NSObject, UITextFieldDelegate {
var text: Binding<String>
var isFirstResponder: Binding<Bool>
init(_ text: Binding<String>, isFirstResponder: Binding<Bool>) {
self.text = text
self.isFirstResponder = isFirstResponder
}
@objc public func textViewDidChange(_ textField: UITextField) {
self.text.wrappedValue = textField.text ?? ""
}
public func textFieldDidBeginEditing(_ textField: UITextField) {
self.isFirstResponder.wrappedValue = true
}
public func textFieldDidEndEditing(_ textField: UITextField) {
self.isFirstResponder.wrappedValue = false
}
}
}
struct ContentView: View {
@State var text = ""
@State var isFirstResponder = false
var body: some View {
LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)
}
}
LegacyTextField(text: $text, isFirstResponder: $isFirstResponder) {
$0.textColor = .red
$0.tintColor = .blue
}
由于 Responder Chain 不提供通过 SwiftUI 来使用,因此我们必须使用 UIViewRepresentable 来使用它。 我已经制定了一种解决方法,其工作方式与我们使用 UIKit 的方式类似。
struct CustomTextField: UIViewRepresentable {
class Coordinator: NSObject, UITextFieldDelegate {
@Binding var text: String
@Binding var nextResponder : Bool?
@Binding var isResponder : Bool?
init(text: Binding<String>,nextResponder : Binding<Bool?> , isResponder : Binding<Bool?>) {
_text = text
_isResponder = isResponder
_nextResponder = nextResponder
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
func textFieldDidBeginEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.isResponder = true
}
}
func textFieldDidEndEditing(_ textField: UITextField) {
DispatchQueue.main.async {
self.isResponder = false
if self.nextResponder != nil {
self.nextResponder = true
}
}
}
}
@Binding var text: String
@Binding var nextResponder : Bool?
@Binding var isResponder : Bool?
var isSecured : Bool = false
var keyboard : UIKeyboardType
func makeUIView(context: UIViewRepresentableContext<CustomTextField>) -> UITextField {
let textField = UITextField(frame: .zero)
textField.isSecureTextEntry = isSecured
textField.autocapitalizationType = .none
textField.autocorrectionType = .no
textField.keyboardType = keyboard
textField.delegate = context.coordinator
return textField
}
func makeCoordinator() -> CustomTextField.Coordinator {
return Coordinator(text: $text, nextResponder: $nextResponder, isResponder: $isResponder)
}
func updateUIView(_ uiView: UITextField, context: UIViewRepresentableContext<CustomTextField>) {
uiView.text = text
if isResponder ?? false {
uiView.becomeFirstResponder()
}
}
}
你可以像这样使用这个组件...
struct ContentView : View {
@State private var username = ""
@State private var password = ""
// set true , if you want to focus it initially, and set false if you want to focus it by tapping on it.
@State private var isUsernameFirstResponder : Bool? = true
@State private var isPasswordFirstResponder : Bool? = false
var body : some View {
VStack(alignment: .center) {
CustomTextField(text: $username,
nextResponder: $isPasswordFirstResponder,
isResponder: $isUsernameFirstResponder,
isSecured: false,
keyboard: .default)
// assigning the next responder to nil , as this will be last textfield on the view.
CustomTextField(text: $password,
nextResponder: .constant(nil),
isResponder: $isPasswordFirstResponder,
isSecured: true,
keyboard: .default)
}
.padding(.horizontal, 50)
}
}
这里的isResponder是给当前textfield分配响应者,nextResponder是做第一个响应,因为当前textfield放弃了它。
使用
SwiftUIX
非常简单,我很惊讶更多的人不知道这一点。
import SwiftUIX
。CocoaTextField
代替 TextField
来使用功能 .isFirstResponder(true)
。CocoaTextField("Confirmation Code", text: $confirmationCode)
.isFirstResponder(true)
我认为 SwiftUIX 有很多方便的东西,但这仍然是你控制范围之外的代码,谁知道当 SwiftUI 3.0 出现时,糖魔法会发生什么。 请允许我介绍一下无聊的 UIKit 解决方案,经过合理的检查和升级的时间安排,略有升级
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
// AutoFocusTextField.swift
struct AutoFocusTextField: UIViewRepresentable {
private let placeholder: String
@Binding private var text: String
private let onEditingChanged: ((_ focused: Bool) -> Void)?
private let onCommit: (() -> Void)?
init(_ placeholder: String, text: Binding<String>, onEditingChanged: ((_ focused: Bool) -> Void)? = nil, onCommit: (() -> Void)? = nil) {
self.placeholder = placeholder
_text = text
self.onEditingChanged = onEditingChanged
self.onCommit = onCommit
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: UIViewRepresentableContext<AutoFocusTextField>) -> UITextField {
let textField = UITextField()
textField.delegate = context.coordinator
textField.placeholder = placeholder
return textField
}
func updateUIView(_ uiView: UITextField, context:
UIViewRepresentableContext<AutoFocusTextField>) {
uiView.text = text
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { // needed for modal view to show completely before aufo-focus to avoid crashes
if uiView.window != nil, !uiView.isFirstResponder {
uiView.becomeFirstResponder()
}
}
}
class Coordinator: NSObject, UITextFieldDelegate {
var parent: AutoFocusTextField
init(_ autoFocusTextField: AutoFocusTextField) {
self.parent = autoFocusTextField
}
func textFieldDidChangeSelection(_ textField: UITextField) {
parent.text = textField.text ?? ""
}
func textFieldDidEndEditing(_ textField: UITextField) {
parent.onEditingChanged?(false)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
parent.onEditingChanged?(true)
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
parent.onCommit?()
return true
}
}
}
// SearchBarView.swift
struct SearchBarView: View {
@Binding private var searchText: String
@State private var showCancelButton = false
private var shouldShowOwnCancelButton = true
private let onEditingChanged: ((Bool) -> Void)?
private let onCommit: (() -> Void)?
@Binding private var shouldAutoFocus: Bool
init(searchText: Binding<String>,
shouldShowOwnCancelButton: Bool = true,
shouldAutofocus: Binding<Bool> = .constant(false),
onEditingChanged: ((Bool) -> Void)? = nil,
onCommit: (() -> Void)? = nil) {
_searchText = searchText
self.shouldShowOwnCancelButton = shouldShowOwnCancelButton
self.onEditingChanged = onEditingChanged
_shouldAutoFocus = shouldAutofocus
self.onCommit = onCommit
}
var body: some View {
HStack {
HStack(spacing: 6) {
Image(systemName: "magnifyingglass")
.foregroundColor(.gray500)
.font(Font.subHeadline)
.opacity(1)
if shouldAutoFocus {
AutoFocusTextField("Search", text: $searchText) { focused in
self.onEditingChanged?(focused)
self.showCancelButton.toggle()
}
.foregroundColor(.gray600)
.font(Font.body)
} else {
TextField("Search", text: $searchText, onEditingChanged: { focused in
self.onEditingChanged?(focused)
self.showCancelButton.toggle()
}, onCommit: {
print("onCommit")
}).foregroundColor(.gray600)
.font(Font.body)
}
Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill")
.foregroundColor(.gray500)
.opacity(searchText == "" ? 0 : 1)
}.padding(4)
}.padding([.leading, .trailing], 8)
.frame(height: 36)
.background(Color.gray300.opacity(0.6))
.cornerRadius(5)
if shouldShowOwnCancelButton && showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
}
}
#if DEBUG
struct SearchBarView_Previews: PreviewProvider {
static var previews: some View {
Group {
SearchBarView(searchText: .constant("Art"))
.environment(\.colorScheme, .light)
SearchBarView(searchText: .constant("Test"))
.environment(\.colorScheme, .dark)
}
}
}
#endif
// MARK: Helpers
extension UIApplication {
func endEditing(_ force: Bool) {
self.windows
.filter{$0.isKeyWindow}
.first?
.endEditing(force)
}
}
// ContentView.swift
class SearchVM: ObservableObject {
@Published var searchQuery: String = ""
...
}
struct ContentView: View {
@State private var shouldAutofocus = true
@StateObject private var viewModel = SearchVM()
var body: some View {
VStack {
SearchBarView(searchText: $query, shouldShowOwnCancelButton: false, shouldAutofocus: $shouldAutofocus)
}
}
}
我试图根据之前的答案使其变得简单,这使得视图出现时键盘出现,没有其他。 刚刚在iOS 16上测试,确实自动出现,无需设置延迟。
struct MyView: View {
@State private var answer = ""
@FocusState private var focused: Bool // 1. create a @FocusState here
var body: some View {
VStack {
TextField("", text: $answer)
.focused($focused) // 2. set the binding here
}
.onAppear {
focused = true // 3. pop the keyboard on appear
}
}
}
对于 macOS 13,有一个不需要延迟的新修改器。目前,不适用于 iOS 16。
VStack {
TextField(...)
.focused($focusedField, equals: .firstField)
TextField(...)
.focused($focusedField, equals: .secondField)
}.defaultFocus($focusedField, .secondField) // <== Here
要做的一件事是,重点关注支持的操作系统版本。
@available(iOS 15.0, *)
struct focusTextField: ViewModifier {
@FocusState var textFieldFocused: Bool
init(focused: Bool) {
self.textFieldFocused = focused
}
func body(content: Content) -> some View {
content
.focused($textFieldFocused)
.onTapGesture {
textFieldFocused = true
}
}
}
struct nonfocusTextField: ViewModifier {
func body(content: Content) -> some View {
content
}
}
extension View {
func addFocus(textFieldFocused: Bool) -> some View {
if #available(iOS 15.0, *) {
return modifier(focusTextField(focused: textFieldFocused))
} else {
return modifier(nonfocusTextField())
}
}
}
这可以像
一样使用TextField("Name", text: $name)
.addFocus(textFieldFocused: nameFocused)
这可能并不适合所有情况,但非常适合我的用例