Как скрыть keyboard
использование SwiftUI
для следующих случаев?
Случай 1
У меня есть, TextField
и мне нужно скрыть, keyboard
когда пользователь нажимает return
кнопку.
Случай 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(_:)
: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() } }
источник
.keyWindow
теперь устарела. См . Ответ Лоренцо Сантини ..tapAction
он был переименован в.onTapGesture
После множества попыток я нашел решение, которое (в настоящее время) не блокирует никакие элементы управления - добавление распознавателя жестов в
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 } }
В
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)
Реализуйте
UIGestureRecognizerDelegate
возможность одновременного касания.extension SceneDelegate: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
Теперь любая клавиатура в любом представлении будет закрыта при касании или перетаскивании наружу.
PS Если вы хотите закрыть только определенные текстовые поля - добавьте и удалите распознаватель жестов в окно при каждом вызове обратного вызова TextField
onEditingChanged
источник
@ 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())
Теперь нажатие на любой элемент управления, который сам по себе не имеет клавиатуры, приведет к соответствующему закрытию.
(Проверено с бета-версией 7)
источник
Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
Я испытал это при использовании TextField внутри NavigationView. Это мое решение. Он закроет клавиатуру, когда вы начнете прокрутку.
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)})
источник
Я нашел другой способ отключить клавиатуру, который не требует доступа к
keyWindow
свойству; на самом деле компилятор выдает предупреждение, используяUIApplication.shared.keyWindow?.endEditing(true)
Вместо этого я использовал этот код:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
источник
SwiftUI в файле 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() } }
этого достаточно для каждого просмотра с помощью клавиатуры в вашем приложении ...
источник
SwiftUI 2
Вот обновленное решение для SwiftUI 2 / iOS 14 (первоначально предложенное здесь Михаилом).
Он не использует
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 } }
Вот пример того, как распознавать одновременные жесты, кроме жестов длительного нажатия:
extension UIApplication: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) } }
источник
return false
.Мое решение, как скрыть программную клавиатуру, когда пользователи нажимают снаружи. Вам нужно использовать
contentShape
with,onLongPressGesture
чтобы обнаружить весь контейнер 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: {}) } }
источник
добавьте этот модификатор в представление, которое вы хотите обнаруживать касания пользователя
.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) }
источник
Я предпочитаю использовать
.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) }
источник
Потому
keyWindow
что устарело.extension View { func endEditing(_ force: Bool) { UIApplication.shared.windows.forEach { $0.endEditing(force)} } }
источник
force
Параметр не используется. Должно быть{ $0.endEditing(force)}
Похоже,
endEditing
это единственное решение, на которое указал @rraphael.Самый чистый пример, который я видел до сих пор:
extension View { func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(force) } }
а затем использовать его в
onCommit:
источник
.keyWindow
теперь устарела. См . Ответ Лоренцо Сантини .Расширяя ответ @Feldur (который был основан на @RyanTCB), вот еще более выразительное и мощное решение, позволяющее отключать клавиатуру с помощью других жестов, чем
onTapGesture
вы можете указать, что вы хотите, в вызове функции.Применение
// MARK: - View extension RestoreAccountInputMnemonicScreen: View { var body: some View { List(viewModel.inputWords) { inputMnemonicWord in InputMnemonicCell(mnemonicInput: inputMnemonicWord) } .dismissKeyboard(on: [.tap, .drag]) } }
Или используя
All.gestures
(просто сахар дляGestures.allCases
🍬).dismissKeyboard(on: All.gestures)
Код
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)) } }
Слово предостережения
Обратите внимание, что если вы используете все жесты, они могут конфликтовать, и я не придумал какого-либо изящного решения для этого.
источник
eraseToAny()
Этот способ позволяет скрыть клавиатуру на проставках!
Сначала добавьте эту функцию (Кредит предоставлен: Каспер Зандберген, из SwiftUI не может нажать в Spacer of HStack )
extension Spacer { public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View { ZStack { Color.black.opacity(0.001).onTapGesture(count: count, perform: action) self } } }
Затем добавьте следующие 2 функции (Credit Given To: rraphael, из этого вопроса)
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Приведенная ниже функция будет добавлена в ваш класс View, просто обратитесь к верхнему ответу здесь от rraphael для получения более подробной информации.
private func endEditing() { UIApplication.shared.endEditing() }
Наконец, теперь вы можете просто позвонить ...
Spacer().onTapGesture { self.endEditing() }
Теперь любая разделительная область закроет клавиатуру. Больше нет необходимости в большом белом фоне!
Вы можете гипотетически применить эту технику
extension
к любым элементам управления, которые вам нужны для поддержки TapGestures, которые в настоящее время этого не делают, и вызыватьonTapGesture
функцию в сочетании сself.endEditing()
закрытием клавиатуры в любой ситуации, в которой вы хотите.источник
Пожалуйста, проверьте https://github.com/michaelhenry/KeyboardAvoider
Просто включите его
KeyboardAvoider {}
поверх основного экрана и все.KeyboardAvoider { VStack { TextField() TextField() TextField() TextField() } }
источник
Основываясь на ответе @ Sajjon, вот решение, позволяющее отключать клавиатуру при нажатии, длительном нажатии, перетаскивании, увеличении и повороте жестов по вашему выбору.
Это решение работает в XCode 11.4
Использование для получения поведения, заданного @IMHiteshSurani
struct MyView: View { @State var myText = "" var body: some View { VStack { DismissingKeyboardSpacer() HStack { TextField("My Text", text: $myText) Button("Return", action: {}) .dismissKeyboard(on: [.longPress]) } DismissingKeyboardSpacer() } } } struct DismissingKeyboardSpacer: View { var body: some View { ZStack { Color.black.opacity(0.0001) Spacer() } .dismissKeyboard(on: Gestures.allCases) } }
Код
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 { AnyView(view.highPriorityGesture( gesture.onChanged { _ in voidAction() } )) } switch self { case .tap: return AnyView(view.gesture(TapGesture().onEnded(voidAction))) 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(AnyView(content)) { $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)) } }
источник
Вы можете полностью избежать взаимодействия с 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 }) } } }
Обратите внимание, что я тестировал его только в последней бета-версии Xcode 12, но он должен работать со старыми версиями (даже Xcode 11) без каких-либо проблем.
источник
Return
Клавиша клавиатурыВ дополнение ко всем ответам о касании вне текстового поля, вы можете захотеть закрыть клавиатуру, когда пользователь нажимает клавишу возврата на клавиатуре:
определите эту глобальную функцию:
func resignFirstResponder() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }
И добавьте в
onCommit
аргумент его:TextField("title", text: $text, onCommit: { resignFirstResponder() })
Льготы
Демо
источник
До сих пор вышеуказанные параметры не работали для меня, потому что у меня есть форма и внутренние кнопки, ссылки, средство выбора ...
Я создаю работающий ниже код с помощью приведенных выше примеров.
import Combine import SwiftUI private class KeyboardListener: ObservableObject { @Published var keyabordIsShowing: Bool = false var cancellable = Set<AnyCancellable>() init() { NotificationCenter.default .publisher(for: UIResponder.keyboardWillShowNotification) .sink { [weak self ] _ in self?.keyabordIsShowing = true } .store(in: &cancellable) NotificationCenter.default .publisher(for: UIResponder.keyboardWillHideNotification) .sink { [weak self ] _ in self?.keyabordIsShowing = false } .store(in: &cancellable) } } private struct DismissingKeyboard: ViewModifier { @ObservedObject var keyboardListener = KeyboardListener() fileprivate func body(content: Content) -> some View { ZStack { content Rectangle() .background(Color.clear) .opacity(keyboardListener.keyabordIsShowing ? 0.01 : 0) .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height) .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) } } } } extension View { func dismissingKeyboard() -> some View { ModifiedContent(content: self, modifier: DismissingKeyboard()) } }
Применение:
var body: some View { NavigationView { Form { picker button textfield text } .dismissingKeyboard()
источник
SwiftUI, выпущенный в июне 2020 года с Xcode 12 и iOS 14, добавляет модификатор hideKeyboardOnTap (). Это должно решить ваш случай номер 2. Решение для вашего случая номер 1 предоставляется бесплатно с Xcode 12 и iOS 14: клавиатура по умолчанию для TextField автоматически скрывается при нажатии кнопки Return.
источник