如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
如何使用 SwiftUI
针对以下情况隐藏 keyboard
?
情况 1
我有一个TextField
,需要在用户点击“返回”按钮时隐藏 keyboard
。
情况 2
我有一个TextField
,需要在用户点击外部区域时隐藏 keyboard
。
我可以通过使用 SwiftUI
来实现这个功能吗?
注意:
我没有问关于 UITextField
的问题。 我想通过使用 SwifUI.TextField
来完成此操作。
您可以通过向共享应用程序发送动作来强制第一响应者辞职:
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct ContentView : View {
@State private var name: String = ""
var body: some View {
VStack {
Text("Hello \(name)")
TextField("Name...", text: self.$name) {
// Called when the user tap the return button
// see `onCommit` on TextField initializer.
UIApplication.shared.endEditing()
}
}
}
}
如果您想通过点击来关闭键盘,您可以创建一个全屏的白色视图并添加一个触发endEditing(_:)
方法的点击操作:
endEditing(_:)
方法的点击操作:
```struct Background<Content: View>: View {
private var content: Content
init(@ViewBuilder content: @escaping () -> Content) {
self.content = content()
}
var body: some View {
Color.white
.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
.overlay(content)
}
}
struct ContentView : View {
@State private var name: String = ""
var body: some View {
Background {
VStack {
Text("Hello \(self.name)")
TextField("Name...", text: self.$name) {
self.endEditing()
}
}
}.onTapGesture {
self.endEditing()
}
}
private func endEditing() {
UIApplication.shared.endEditing()
}
}
.tapAction
已更名为 .onTapGesture
。 - LinusGeffarthUIApplication
是 UIKit 的一部分,因此需要 import UIKit
。 - Alienbash(键盘上方的“完成”按钮)
从iOS 15开始,我们现在可以使用@FocusState
来控制应该聚焦哪个字段(请参见此答案以查看更多示例)。
我们还可以直接在键盘上方添加ToolbarItem
。
当两者结合在一起时,我们可以在键盘正上方添加一个Done
按钮。这是一个简单的演示:
struct ContentView: View {
private enum Field: Int, CaseIterable {
case username, password
}
@State private var username: String = ""
@State private var password: String = ""
@FocusState private var focusedField: Field?
var body: some View {
NavigationView {
Form {
TextField("Username", text: $username)
.focused($focusedField, equals: .username)
SecureField("Password", text: $password)
.focused($focusedField, equals: .password)
}
.toolbar {
ToolbarItem(placement: .keyboard) {
Button("Done") {
focusedField = nil
}
}
}
}
}
}
(点击任意位置隐藏键盘)
这里提供了一个更新的解决方案,适用于SwiftUI 2 / iOS 14(最初由Mikhail在这里提出)。
它不使用AppDelegate
或者SceneDelegate
,因为如果你使用SwiftUI生命周期,这些内容是缺失的:
@main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onAppear(perform: UIApplication.shared.addTapGestureRecognizer)
}
}
}
extension UIApplication {
func addTapGestureRecognizer() {
guard let window = windows.first else { return }
let tapGesture = UITapGestureRecognizer(target: window, action: #selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self
window.addGestureRecognizer(tapGesture)
}
}
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true // set to `false` if you don't want to detect tap during other gestures
}
}
AnyGestureRecognizer
:let tapGesture = AnyGestureRecognizer(target: window, action: #selector(UIView.endEditing))
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
}
}
guard let window = (connectedScenes.first as? UIWindowScene)?.windows.first else { return }
来消除警告。这样的话,它与原解决方案的行为完全相同。 - pawello2222经过多次尝试,我找到了一个解决方案,目前不会阻止任何控件 - 将手势识别器添加到 UIWindow
。
UITapGestureRecognizer
and just copy step 3:Create custom gesture recognizer class that works with any touches:
class AnyGestureRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
if let touchedView = touches.first?.view, touchedView is UIControl {
state = .cancelled
} else if let touchedView = touches.first?.view as? UITextView, touchedView.isEditable {
state = .cancelled
} else {
state = .began
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
state = .ended
}
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
state = .cancelled
}
}
In SceneDelegate.swift
in the func scene
, add next code:
let tapGesture = AnyGestureRecognizer(target: window, action:#selector(UIView.endEditing))
tapGesture.requiresExclusiveTouchType = false
tapGesture.cancelsTouchesInView = false
tapGesture.delegate = self //I don't use window as delegate to minimize possible side effects
window?.addGestureRecognizer(tapGesture)
Implement UIGestureRecognizerDelegate
to allow simultaneous touches.
extension SceneDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
在使用NavigationView中的TextField时,我遇到了这个问题。以下是我的解决方案。它将在开始滚动时关闭键盘。
NavigationView {
Form {
Section {
TextField("Receipt amount", text: $receiptAmount)
.keyboardType(.decimalPad)
}
}
}
.gesture(DragGesture().onChanged{_ in UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)})
@RyanTCB的回答很好; 这里有一些改进措施,使其更加易于使用并避免潜在崩溃:
struct DismissingKeyboard: ViewModifier {
func body(content: Content) -> some View {
content
.onTapGesture {
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
keyWindow?.endEditing(true)
}
}
}
“修复”只是将keyWindow!.endEditing(true)
正确替换为keyWindow?.endEditing(true)
(是的,你可能会争辩这不可能发生)。
更有趣的是如何使用它。例如,假设您有一个包含多个可编辑字段的表单。只需像这样包装:
Form {
.
.
.
}
.modifier(DismissingKeyboard())
现在,轻触任何不显示键盘的控件将执行相应的关闭操作。
(已在 beta 7 中测试)
cancelsTouchesInView
设为 true
。这样,您就无法点击视图上的按钮了。 - Roland Lariotte我找到了一种不需要访问keyWindow
属性就可以取消键盘的方法;实际上,使用这种方法编译器会返回一个警告。
UIApplication.shared.keyWindow?.endEditing(true)
'keyWindow'在iOS 13.0已被弃用:不应该在支持多个场景的应用程序中使用,因为它返回所有连接场景中的关键窗口。
相反,我使用了这段代码:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
.onTapGesture{}
修饰符中,问题就解决了。谢谢。 - NSSpeedForceiOS 15(Xcode 13)中的SwiftUI已经原生支持使用新的@FocusState
属性包装器以编程方式聚焦TextField
。
要消除键盘,只需将视图的focusedField
设置为nil
即可。自iOS14以来,返回键会自动关闭键盘。
文档: https://developer.apple.com/documentation/swiftui/focusstate/
struct MyView: View {
enum Field: Hashable {
case myField
}
@State private var text: String = ""
@FocusState private var focusedField: Field?
var body: some View {
TextField("Type here", text: $text)
.focused($focusedField, equals: .myField)
Button("Dismiss") {
focusedField = nil
}
}
}
您可以完全避免与UIKit的交互,并在纯SwiftUI中实现它。只需为您的TextField
添加一个.id(<your id>)
修饰符,并在需要关闭键盘时更改其值(例如在滑动、视图点击、按钮操作等情况下)。
示例实现:
struct MyView: View {
@State private var text: String = ""
@State private var textFieldId: String = UUID().uuidString
var body: some View {
VStack {
TextField("Type here", text: $text)
.id(textFieldId)
Spacer()
Button("Dismiss", action: { textFieldId = UUID().uuidString })
}
}
}
请注意,我只在最新的Xcode 12 beta中进行了测试,但它应该可以在旧版本(甚至是Xcode 11)中正常工作,没有任何问题。
自iOS 15起,您可以使用@FocusState
struct ContentView: View {
@Binding var text: String
private enum Field: Int {
case yourTextEdit
}
@FocusState private var focusedField: Field?
var body: some View {
VStack {
TextEditor(text: $speech.text.bound)
.padding(Edge.Set.horizontal, 18)
.focused($focusedField, equals: .yourTextEdit)
}.onTapGesture {
if (focusedField != nil) {
focusedField = nil
}
}
}
}
在“SceneDelegate.swift”文件中,只需添加 .onTapGesture { window.endEditing(true)} 即可。
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
// This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
// Create the SwiftUI view that provides the window contents.
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(
rootView: contentView.onTapGesture { window.endEditing(true)}
)
self.window = window
window.makeKeyAndVisible()
}
}
这对于使用键盘的每个视图在您的应用程序中来说已经足够了...
var body: some View {
NavigationView {
Form {
Section {
TextField("输入", text: $c)
}
Section {
Picker("名称", selection: $sel) {
ForEach(0..<200) {
Text("\(self.array[$0])%")
}
}
.onTapGesture {
UIApplication.shared.endEditing()
}
}
}
}
}
当点击其他区域时,键盘会消失,但是选择器无响应。我没有找到解决方法。 - Nalovwindow.rootViewController = UIHostingController(rootView: contentView.onTapGesture { UIApplication.shared.sendAction(#selector(UIView.resignFirstResponder), to: nil, from: nil, for: nil) })
- Cinn我的解决方案是如何在用户点击外部时隐藏软件键盘。
您需要使用contentShape
与 onLongPressGesture
来检测整个视图容器。使用onTapGesture
来避免阻塞TextField
上的焦点。您可以使用onTapGesture
而不是onLongPressGesture
,但NavigationBar项目将无法使用。
extension View {
func endEditing() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct KeyboardAvoiderDemo: View {
@State var text = ""
var body: some View {
VStack {
TextField("Demo", text: self.$text)
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
.contentShape(Rectangle())
.onTapGesture {}
.onLongPressGesture(
pressing: { isPressed in if isPressed { self.endEditing() } },
perform: {})
}
}