SwiftUI:如何让TextField成为第一响应者?
这是我的SwiftUI:如何让TextField成为第一响应者?,swift,textfield,swiftui,first-responder,Swift,Textfield,Swiftui,First Responder,这是我的SwiftUI代码: struct ContentView : View { @State var showingTextField = false @State var text = "" var body: some View { return VStack { if showingTextField { TextField($text) }
SwiftUI
代码:
struct ContentView : View {
@State var showingTextField = false
@State var text = ""
var body: some View {
return VStack {
if showingTextField {
TextField($text)
}
Button(action: { self.showingTextField.toggle() }) {
Text ("Show")
}
}
}
}
我想要的是当文本字段变为可见时,使文本字段成为第一响应者(即接收焦点并弹出键盘)。Swift UI 3 从Xcode 13开始,您可以使用
focused
修改器使视图成为第一响应者
Swift UI 1/2 目前似乎不可能,但您可以自己实现类似的功能 您可以创建一个自定义文本字段并添加一个值,使其成为第一响应者
struct CustomTextField:UIViewRepresentable{
类协调器:NSObject,UITextFieldDelegate{
@绑定变量文本:字符串
var didBecomeFirstResponder=false
init(文本:绑定){
_文本=文本
}
func textField didchangeSelection(textField:UITextField){
text=textField.text??“”
}
}
@绑定变量文本:字符串
var isFirstResponder:Bool=false
func makeUIView(上下文:UIViewRepresentableContext)->UITextField{
设textField=UITextField(帧:.0)
textField.delegate=context.coordinator
返回文本字段
}
func makeCoordinator()->CustomTextField.Coordinator{
退货协调员(文本:$text)
}
func updateUIView(uiView:UITextField,context:UIViewRepresentableContext){
uiView.text=文本
如果是第一响应者&&!context.coordinator.didBecomeFirstResponder{
uiView.becomeFirstResponder()
context.coordinator.didBecomeFirstResponder=true
}
}
}
注意:didBecomeFirstResponder
用于确保文本字段仅成为firstresponder一次,而不是每次由SwiftUI刷新时都成为firstresponder
你会像这样使用它
struct ContentView:View{
@状态变量文本:String=“”
var body:一些观点{
CustomTextField(text:$text,isFirstResponder:true)
.框架(宽:300,高:50)
.背景(颜色.红色)
}
}
另外,我添加了一个框架
,因为它不像股票文本字段
,这意味着幕后会有更多的事情发生
有关协调人的更多信息,请参见本次精彩的WWDC 19演讲:
在Xcode 11.4上测试对于任何最终来到这里但使用@Matteo Pacini的答案面临崩溃的人,请注意beta 4中的此更改:关于此块:
init(文本:绑定){
$text=text
}
应使用:
init(文本:绑定){
_文本=文本
}
如果您想让文本字段成为工作表中的第一响应者
,请注意,在显示文本字段之前,您不能调用becomeFirstResponder
。换句话说,将@Matteo Pacini的文本字段直接放在工作表中会导致崩溃
要解决此问题,请添加一个附加检查uiView.window!=零
表示文本字段的可见性。仅在其位于视图层次结构中后才聚焦:
struct AutoFocusTextField:UIViewRepresentable{
@绑定变量文本:字符串
func makeCoordinator()->Coordinator{
协调员(自我)
}
func makeUIView(上下文:UIViewRepresentableContext)->UITextField{
设textField=UITextField()
textField.delegate=context.coordinator
返回文本字段
}
func updateUIView(uiView:UITextField,context:
UIViewRepresentableContext){
uiView.text=文本
如果uiView.window!=nil,!uiView.isFirstResponder{
uiView.becomeFirstResponder()
}
}
类协调器:NSObject,UITextFieldDelegate{
变量父项:AutoFocusTextField
init(u0; autoFocusTextField:autoFocusTextField){
self.parent=autoFocusTextField
}
func textField didchangeSelection(textField:UITextField){
parent.text=textField.text??“”
}
}
}
iOS 15
有一个名为@FocusState
的新包装器,它控制键盘和聚焦键盘的状态(“aka”firstResponder)
Becaome第一响应者(专注)
如果对文本字段使用focused
修饰符,可以使其成为焦点:
辞职第一响应者(解雇键盘)
或者通过将变量设置为nil
,关闭键盘:
老而活:
简单包装结构-与本机结构类似:
注意根据评论中的要求添加了文本绑定支持
使用,您可以执行以下操作:
TextField(“,text:$value)
.introspectTextField{textField in
textField.becomeFirstResponder()
}
选择的答案会导致AppKit出现一些无限循环问题。我不知道UIKit的案子
为了避免这个问题,我建议直接共享NSTextField
实例
import AppKit
import SwiftUI
struct Sample1: NSViewRepresentable {
var textField: NSTextField
func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField }
func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {}
}
这打破了纯值语义,但依赖AppKit意味着您部分放弃了纯值语义,并将承受一些肮脏。这是一个魔术洞,我们现在需要处理缺乏第一反应控制在SwiftUI
由于我们直接访问NSTextField
,设置第一响应程序是简单的AppKit方式,因此没有可见的故障源
您可以下载可用的源代码。正如其他人所指出的(例如),我也没有找到任何可用于macOS的公认解决方案。不过,我还是有点运气,使用它可以在快捷界面视图中以第一响应者的身份出现:
导入可可粉
导入快捷键
类FirstResponderNSSearchFieldController:NSViewController{
@绑定变量文本:字符串
init(文本:绑定){
self.\u text=文本
super.init(nibName:nil,bundle:nil)
}
必需初始化?(编码器:NSCoder){
fatalError(“init(coder:)尚未实现
struct ContentView: View {
@State var text = ""
@State var isFirstResponder = false
var body: some View {
LegacyTextField(text: $text, isFirstResponder: $isFirstResponder)
}
}
import AppKit
import SwiftUI
struct Sample1: NSViewRepresentable {
var textField: NSTextField
func makeNSView(context:NSViewRepresentableContext<Sample1>) -> NSView { textField }
func updateNSView(_ x:NSView, context:NSViewRepresentableContext<Sample1>) {}
}
let win = NSWindow()
let txt = NSTextField()
win.setIsVisible(true)
win.setContentSize(NSSize(width: 256, height: 256))
win.center()
win.contentView = NSHostingView(rootView: Sample1(textField: txt))
win.makeFirstResponder(txt)
let app = NSApplication.shared
app.setActivationPolicy(.regular)
app.run()
struct ResponderView<View: UIView>: UIViewRepresentable {
@Binding var isFirstResponder: Bool
var configuration = { (view: View) in }
func makeUIView(context: UIViewRepresentableContext<Self>) -> View { View() }
func makeCoordinator() -> Coordinator {
Coordinator($isFirstResponder)
}
func updateUIView(_ uiView: View, context: UIViewRepresentableContext<Self>) {
context.coordinator.view = uiView
_ = isFirstResponder ? uiView.becomeFirstResponder() : uiView.resignFirstResponder()
configuration(uiView)
}
}
// MARK: - Coordinator
extension ResponderView {
final class Coordinator {
@Binding private var isFirstResponder: Bool
private var anyCancellable: AnyCancellable?
fileprivate weak var view: UIView?
init(_ isFirstResponder: Binding<Bool>) {
_isFirstResponder = isFirstResponder
self.anyCancellable = Publishers.keyboardHeight.sink(receiveValue: { [weak self] keyboardHeight in
guard let view = self?.view else { return }
DispatchQueue.main.async { self?.isFirstResponder = view.isFirstResponder }
})
}
}
}
// MARK: - keyboardHeight
extension Publishers {
static var keyboardHeight: AnyPublisher<CGFloat, Never> {
let willShow = NotificationCenter.default.publisher(for: UIApplication.keyboardWillShowNotification)
.map { ($0.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect)?.height ?? 0 }
let willHide = NotificationCenter.default.publisher(for: UIApplication.keyboardWillHideNotification)
.map { _ in CGFloat(0) }
return MergeMany(willShow, willHide)
.eraseToAnyPublisher()
}
}
struct ResponderView_Previews: PreviewProvider {
static var previews: some View {
ResponderView<UITextField>.init(isFirstResponder: .constant(false)) {
$0.placeholder = "Placeholder"
}.previewLayout(.fixed(width: 300, height: 40))
}
}
struct ResponderTextField: View {
var placeholder: String
@Binding var text: String
@Binding var isFirstResponder: Bool
private var textFieldDelegate: TextFieldDelegate
init(_ placeholder: String, text: Binding<String>, isFirstResponder: Binding<Bool>) {
self.placeholder = placeholder
self._text = text
self._isFirstResponder = isFirstResponder
self.textFieldDelegate = .init(text: text)
}
var body: some View {
ResponderView<UITextField>(isFirstResponder: $isFirstResponder) {
$0.text = self.text
$0.placeholder = self.placeholder
$0.delegate = self.textFieldDelegate
}
}
}
// MARK: - TextFieldDelegate
private extension ResponderTextField {
final class TextFieldDelegate: NSObject, UITextFieldDelegate {
@Binding private(set) var text: String
init(text: Binding<String>) {
_text = text
}
func textFieldDidChangeSelection(_ textField: UITextField) {
text = textField.text ?? ""
}
}
}
struct ResponderTextField_Previews: PreviewProvider {
static var previews: some View {
ResponderTextField("Placeholder",
text: .constant(""),
isFirstResponder: .constant(false))
.previewLayout(.fixed(width: 300, height: 40))
}
}
struct SomeView: View {
@State private var login: String = ""
@State private var password: String = ""
@State private var isLoginFocused = false
@State private var isPasswordFocused = false
var body: some View {
VStack {
ResponderTextField("Login", text: $login, isFirstResponder: $isLoginFocused)
ResponderTextField("Password", text: $password, isFirstResponder: $isPasswordFocused)
}
}
}
struct SetFirstResponderTextField: ViewModifier {
@State var isFirstResponderSet = false
func body(content: Content) -> some View {
content
.introspectTextField { textField in
if self.isFirstResponderSet == false {
textField.becomeFirstResponder()
self.isFirstResponderSet = true
}
}
}
}
import SwiftUI
struct MyView: View {
@Binding var text: String
var body: some View {
TextField("Hello world", text: $text)
.onAppear {
UIApplication.shared.windows.first?.rootViewController?.view.textField?.becomeFirstResponder()
}
}
}
private extension UIView {
var textField: UITextField? {
subviews.compactMap { $0 as? UITextField }.first ??
subviews.compactMap { $0.textField }.first
}
}
import SwiftUI
import SwiftUIX
struct ContentView : View {
@State var showingTextField = false
@State var text = ""
var body: some View {
return VStack {
if showingTextField {
CocoaTextField("Placeholder text", text: $text)
.isFirstResponder(true)
.frame(width: 300, height: 48, alignment: .center)
}
Button(action: { self.showingTextField.toggle() }) {
Text ("Show")
}
}
}
}
TextField("Name", text: $name)
.firstResponder(id: FirstResponders.name, firstResponder: $firstResponder, resignableUserOperations: .all)
TextEditor(text: $notes)
.firstResponder(id: FirstResponders.notes, firstResponder: $firstResponder, resignableUserOperations: .all)
struct StartInput: ViewModifier {
@EnvironmentObject var chain: ResponderChain
private let tag: String
init(tag: String) {
self.tag = tag
}
func body(content: Content) -> some View {
content.responderTag(tag).onAppear() {
DispatchQueue.main.async {
chain.firstResponder = tag
}
}
}
}
extension TextField {
func startInput(_ tag: String = "field") -> ModifiedContent<TextField<Label>, StartInput> {
self.modifier(StartInput(tag: tag))
}
}
TextField("Enter value:", text: $quantity)
.startInput()
struct NameForm: View {
@FocusState private var isFocued: Bool
@State private var name = ""
var body: some View {
TextField("Name", text: $name)
.focused($isFocued)
Button("Submit") {
if name.isEmpty {
isFocued = true
}
}
}
}
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)
SecureField("Password", text: $password)
.focused($focusedField, equals: .passwordField)
Button("Sign In") {
if username.isEmpty {
focusedField = .usernameField
} else if password.isEmpty {
focusedField = .passwordField
} else {
handleLogin(username, password)
}
}
}
}
}