Hit*_*esh 29 ios swift swiftui
在以下情况下如何隐藏keyboard使用SwiftUI?
情况1
我有TextField,我需要keyboard在用户单击return按钮时隐藏。
情况二
我有TextField,keyboard当用户在外面轻按时,我需要隐藏。
我该如何使用SwiftUI呢?
注意:
我尚未提出有关的问题UITextField。我想使用SwifUI(TextField)来做。
Mik*_*ail 72
经过多次尝试,我找到了一个(当前)不阻止任何控件的解决方案 - 将手势识别器添加到UIWindow.
UITapGestureRecognizer并复制第 3 步:创建适用于任何触摸的自定义手势识别器类:
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
}
}
Run Code Online (Sandbox Code Playgroud)在SceneDelegate.swift中func scene,添加下一个代码:
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)
Run Code Online (Sandbox Code Playgroud)实施UIGestureRecognizerDelegate以允许同时触摸。
extension SceneDelegate: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
Run Code Online (Sandbox Code Playgroud)现在,任何视图上的任何键盘都将在触摸或向外拖动时关闭。
PS 如果您只想关闭特定的 TextFields - 然后在调用 TextField 回调时向窗口添加和删除手势识别器 onEditingChanged
paw*_*222 52
(键盘上方的完成按钮)
从 iOS 15 开始,我们现在可以使用@FocusState来控制应该关注哪个字段(请参阅此答案以查看更多示例)。
我们也可以ToolbarItem直接在键盘上方添加s 。
当组合在一起时,我们可以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
}
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
(点击任意位置隐藏键盘)
这是SwiftUI 2 / iOS 14的更新解决方案(最初由 Mikhail在此处提出)。
如果您使用 SwiftUI 生命周期,它不会使用缺少的AppDelegate或SceneDelegate缺少的:
@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
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想检测其他手势(不仅是点击手势),您可以使用AnyGestureRecognizerMikhail 的回答:
let tapGesture = AnyGestureRecognizer(target: window, action: #selector(UIView.endEditing))
Run Code Online (Sandbox Code Playgroud)
以下是如何检测除长按手势以外的同时手势的示例:
extension UIApplication: UIGestureRecognizerDelegate {
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
}
}
Run Code Online (Sandbox Code Playgroud)
Fel*_*dur 28
@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)
}
}
}
Run Code Online (Sandbox Code Playgroud)
'错误修复'keyWindow!.endEditing(true)应该是正确的keyWindow?.endEditing(true)(是的,你可能会争辩说它不可能发生。)
更有趣的是如何使用它。例如,假设您有一个包含多个可编辑字段的表单。像这样包装它:
Form {
.
.
.
}
.modifier(DismissingKeyboard())
Run Code Online (Sandbox Code Playgroud)
现在,点击任何本身不显示键盘的控件将进行适当的关闭。
(用 beta 7 测试)
小智 27
我在 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)})
Run Code Online (Sandbox Code Playgroud)
rra*_*ael 26
您可以通过向共享应用程序发送操作来强制第一响应者辞职:
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Run Code Online (Sandbox Code Playgroud)
现在,您可以根据需要使用此方法关闭键盘:
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()
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果您想通过点击来关闭键盘,则可以通过点击操作创建全屏白色视图,这将触发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()
}
}
Run Code Online (Sandbox Code Playgroud)
Pta*_*tah 16
从 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
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
Vic*_*rov 13
我的解决方案如何在用户点击外部时隐藏软件键盘。您需要使用contentShapewithonLongPressGesture来检测整个 View 容器。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: {})
}
}
Run Code Online (Sandbox Code Playgroud)
Joe*_*tto 13
在 iOS15 中,此功能完美运行。
VStack {
// Some content
}
.onTapGesture {
// Hide Keyboard
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
.gesture(
DragGesture(minimumDistance: 0, coordinateSpace: .local).onEnded({ gesture in
// Hide keyboard on swipe down
if gesture.translation.height > 0 {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}))
Run Code Online (Sandbox Code Playgroud)
您的文本字段上不需要任何其他内容,向下滑动和点击都可以隐藏它。我使用它的方式是在我的 master 上NavigationView添加此代码,然后它下面的所有内容都会起作用。唯一的例外是任何都Sheet需要将其附加到它后面,因为它作用于不同的状态。
Lor*_*ini 10
我找到了另一种不需要访问该keyWindow属性的方法来关闭键盘。事实上,编译器会使用
UIApplication.shared.keyWindow?.endEditing(true)
Run Code Online (Sandbox Code Playgroud)
iOS 13.0中已弃用“ keyWindow”:不应将其用于支持多个场景的应用程序,因为它会返回所有已连接场景的关键窗口
相反,我使用了以下代码:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
Run Code Online (Sandbox Code Playgroud)
我更喜欢使用.onLongPressGesture(minimumDuration: 0),它不会在TextView激活另一个键盘时导致键盘闪烁( 的副作用.onTapGesture)。隐藏键盘代码可以是可重复使用的功能。
.onTapGesture(count: 2){} // UI is unresponsive without this line. Why?
.onLongPressGesture(minimumDuration: 0, maximumDistance: 0, pressing: nil, perform: hide_keyboard)
func hide_keyboard()
{
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
Run Code Online (Sandbox Code Playgroud)
iOS 15 (Xcode 13) 中的 SwiftUI 获得了对TextField使用新@FocusState属性包装器的编程重点的原生支持。
要关闭键盘,只需将视图设置focusedField为nil。返回键将自动关闭键盘(自 iOS 14 起)。
文档: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
}
}
}
Run Code Online (Sandbox Code Playgroud)
您可以完全避免与 UIKit 交互并在纯 SwiftUI 中实现它。只需.id(<your id>)向您添加一个修饰符TextField并在您想要关闭键盘时更改其值(滑动、查看点击、按钮操作等)。
示例实现:
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 })
}
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,我仅在最新的 Xcode 12 beta 中对其进行了测试,但它应该适用于旧版本(甚至 Xcode 11)而不会出现任何问题。
将此修饰符添加到要检测用户点击的视图中
.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)
}
Run Code Online (Sandbox Code Playgroud)
只需在'SceneDelegate.swift'文件中添加SwiftUI:.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()
}
}
Run Code Online (Sandbox Code Playgroud)
这对于使用应用程序中的键盘的每个视图就足够了...
通过上面的 josefdolezal扩展答案,当用户点击文本字段之外的任何位置时,您可以隐藏键盘,如下所示:
struct SwiftUIView: View {
@State private var textFieldId: String = UUID().uuidString // To hidekeyboard when tapped outside textFields
@State var fieldValue = ""
var body: some View {
VStack {
TextField("placeholder", text: $fieldValue)
.id(textFieldId)
.onTapGesture {} // So that outer tap gesture has no effect on field
// any more views
}
.onTapGesture { // whenever tapped within VStack
textFieldId = UUID().uuidString
//^ this will remake the textfields hence loosing keyboard focus!
}
}
}
Run Code Online (Sandbox Code Playgroud)
Return按键除了有关点击文本字段外部的所有答案之外,您可能还想在用户点击键盘上的返回键时关闭键盘:
定义这个全局函数:
func resignFirstResponder() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
Run Code Online (Sandbox Code Playgroud)
并在onCommit参数 it 中添加 use :
TextField("title", text: $text, onCommit: {
resignFirstResponder()
})
Run Code Online (Sandbox Code Playgroud)

因为keyWindow已弃用。
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
Run Code Online (Sandbox Code Playgroud)
扩展@Feldur(基于@RyanTCB)的答案,这是一个更具表现力和更强大的解决方案,允许您在其他手势上关闭键盘onTapGesture,您可以在函数调用中指定您想要的手势。
// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
var body: some View {
List(viewModel.inputWords) { inputMnemonicWord in
InputMnemonicCell(mnemonicInput: inputMnemonicWord)
}
.dismissKeyboard(on: [.tap, .drag])
}
}
Run Code Online (Sandbox Code Playgroud)
或者使用All.gestures(只是糖Gestures.allCases)
.dismissKeyboard(on: All.gestures)
Run Code Online (Sandbox Code Playgroud)
enum All {
static let gestures = all(of: Gestures.self)
private static func all<CI>(of _: CI.Type) -> CI.AllCases where CI: CaseIterable {
return CI.allCases
}
}
enum Gestures: Hashable, CaseIterable {
case tap, longPress, drag, magnification, rotation
}
protocol ValueGesture: Gesture where Value: Equatable {
func onChanged(_ action: @escaping (Value) -> Void) -> _ChangedGesture<Self>
}
extension LongPressGesture: ValueGesture {}
extension DragGesture: ValueGesture {}
extension MagnificationGesture: ValueGesture {}
extension RotationGesture: ValueGesture {}
extension Gestures {
@discardableResult
func apply<V>(to view: V, perform voidAction: @escaping () -> Void) -> AnyView where V: View {
func highPrio<G>(
gesture: G
) -> AnyView where G: ValueGesture {
view.highPriorityGesture(
gesture.onChanged { value in
_ = value
voidAction()
}
).eraseToAny()
}
switch self {
case .tap:
// not `highPriorityGesture` since tapping is a common gesture, e.g. wanna allow users
// to easily tap on a TextField in another cell in the case of a list of TextFields / Form
return view.gesture(TapGesture().onEnded(voidAction)).eraseToAny()
case .longPress: return highPrio(gesture: LongPressGesture())
case .drag: return highPrio(gesture: DragGesture())
case .magnification: return highPrio(gesture: MagnificationGesture())
case .rotation: return highPrio(gesture: RotationGesture())
}
}
}
struct DismissingKeyboard: ViewModifier {
var gestures: [Gestures] = Gestures.allCases
dynamic func body(content: Content) -> some View {
let action = {
let forcing = true
let keyWindow = UIApplication.shared.connectedScenes
.filter({$0.activationState == .foregroundActive})
.map({$0 as? UIWindowScene})
.compactMap({$0})
.first?.windows
.filter({$0.isKeyWindow}).first
keyWindow?.endEditing(forcing)
}
return gestures.reduce(content.eraseToAny()) { $1.apply(to: $0, perform: action) }
}
}
extension View {
dynamic func dismissKeyboard(on gestures: [Gestures] = Gestures.allCases) -> some View {
return ModifiedContent(content: self, modifier: DismissingKeyboard(gestures: gestures))
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,如果您使用所有手势,它们可能会发生冲突,并且我没有想出任何巧妙的解决方案来解决这个问题。
我发现效果很好的是
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Run Code Online (Sandbox Code Playgroud)
然后添加到视图结构中:
private func endEditing() {
UIApplication.shared.endEditing()
}
Run Code Online (Sandbox Code Playgroud)
然后
struct YourView: View {
var body: some View {
ParentView {
//...
}.contentShape(Rectangle()) //<---- This is key!
.onTapGesture {endEditing()}
}
}
Run Code Online (Sandbox Code Playgroud)
似乎endEditing解决方案是@rraphael 指出的唯一解决方案。
到目前为止我见过的最干净的例子是这样的:
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
Run Code Online (Sandbox Code Playgroud)
然后在 onCommit:
请检查https://github.com/michaelhenry/KeyboardAvoider
只需包含KeyboardAvoider {}在您的主视图之上即可。
KeyboardAvoider {
VStack {
TextField()
TextField()
TextField()
TextField()
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
5206 次 |
| 最近记录: |