Как скрыть клавиатуру при использовании SwiftUI?
Как спрятаться keyboard
с помощью SwiftUI
для нижеуказанных случаев?
Дело 1
у меня есть TextField
и мне нужно скрыть keyboard
когда пользователь нажимает return
кнопка.
Дело 2
у меня есть TextField
и мне нужно скрыть keyboard
когда пользователь нажимает снаружи.
Как я могу сделать это, используя SwiftUI
?
Примечание:
Я не задавал вопрос относительно UITextField
, Я хочу сделать это с помощью SwifUI
(TextField
).
37 ответов
Единственное, что я нашел до сих пор, чтобы закрыть клавиатуру, это позвонить endEditing(_:)
на keyWindow
:
struct ContentView : View {
@State private var name: String = ""
var body: some View {
VStack {
Text("Hello \(name)")
TextField($name, placeholder: Text("Name...")) {
// Called when the user tap the return button
// see `onCommit` on TextField initializer.
UIApplication.shared.keyWindow?.endEditing(true)
}
}
}
}
Если вы хотите закрыть клавиатуру касанием, вы можете создать полноэкранный белый вид с действием касания, которое вызовет 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(self.$name, placeholder: Text("Name...")) {
self.endEditing(true)
}
}
}.tapAction {
self.endEditing(true)
}
}
private func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
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)
}
}
После множества попыток я нашел решение, которое (в настоящее время) не блокирует никакие элементы управления - добавление распознавателя жестов в UIWindow
.
- Если вы хотите закрывать клавиатуру только на Tap снаружи (без обработки перетаскивания) - тогда достаточно просто
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
Я испытал это при использовании 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)})
@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)
Я нашел другой способ отклонить клавиатуру, которая не требует доступа к keyWindow
свойство; на самом деле компилятор возвращает предупреждение, используя
UIApplication.shared.keyWindow?.endEditing(true)
"keyWindow" устарело в iOS 13.0: его не следует использовать для приложений, которые поддерживают несколько сцен, поскольку оно возвращает ключевое окно для всех связанных сцен
Вместо этого я использовал этот код:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
Вы можете полностью избежать взаимодействия с 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) без каких-либо проблем.
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()
}
}
этого достаточно для каждого просмотра с помощью клавиатуры в вашем приложении...
Начиная с 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
}
}
}
}
Мое решение как скрыть программную клавиатуру, когда пользователи нажимают снаружи. Вам нужно использоватьcontentShape
с 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: {})
}
}
В 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)
}
}))
В вашем TextField больше ничего не требуется, и оба смахивания вниз вместе с нажатием будут работать, чтобы скрыть его. То, как я использую это, это то, что на моем мастере
Я предпочитаю использовать .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)
}
Добавьте этот модификатор в представление, которое вы хотите обнаружить касания пользователя
.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
не рекомендуется.
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.windows.forEach { $0.endEditing(force)}
}
}
Похоже на endEditing
Решение - единственное, на которое указал @rraphael.
Самый чистый пример, который я видел до сих пор, таков:
extension View {
func endEditing(_ force: Bool) {
UIApplication.shared.keyWindow?.endEditing(force)
}
}
а затем использовать его в onCommit:
Расширяя ответ с помощью 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!
}
}
}
Клавиатуры
Return
Ключ
В дополнение ко всем ответам о касании вне текстового поля, вы можете захотеть закрыть клавиатуру, когда пользователь нажимает клавишу возврата на клавиатуре:
определите эту глобальную функцию:
func resignFirstResponder() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
И добавить пользы в
onCommit
аргументируйте это:
TextField("title", text: $text, onCommit: {
resignFirstResponder()
})
Льготы
- Вы можете позвонить из любого места
- Это не зависит от UIKit или SwiftUI (может использоваться в приложениях для Mac)
- Работает даже в iOS 13
Демо
Расширяя ответ @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))
}
}
Слово предостережения
Обратите внимание, что если вы используете все жесты, они могут конфликтовать, и я не придумал какого-либо изящного решения для этого.
Я обнаружил, что очень хорошо работает:
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
Затем добавьте в структуру представления:
private func endEditing() {
UIApplication.shared.endEditing()
}
потом
struct YourView: View {
var body: some View {
ParentView {
//...
}.contentShape(Rectangle()) //<---- This is key!
.onTapGesture {endEditing()}
}
}
Пожалуйста, проверьте https://github.com/michaelhenry/KeyboardAvoider
Просто включите KeyboardAvoider {}
поверх основного экрана и все.
KeyboardAvoider {
VStack {
TextField()
TextField()
TextField()
TextField()
}
}
Что ж, самое простое решение для меня - просто использовать библиотеку https://github.com/hackiftekhar/IQKeyboardManager
Поддержка SwiftUI несколько ограничена, я использую ее, помещая этот код в структуру @main:
import IQKeyboardManagerSwift
@main
struct MyApp: App {
init(){
IQKeyboardManager.shared.enable = true
IQKeyboardManager.shared.shouldResignOnTouchOutside = true
}
...
}
Сначала добавьте расширение в свой код
extension UIApplication {
func dismissKeyboard() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
} }
Используйте его как модификатор, чтобы закрыть клавиатуру при нажатии
Done
кнопка.
.toolbar{
ToolbarItemGroup(placement: .keyboard){
Spacer()
Button("Done"){
UIApplication.shared.dismissKeyboard()
}
}
}
Этот способ позволяет скрыть клавиатуру на проставках!
Сначала добавьте эту функцию (Кредит предоставлен: Casper Zandbergen, из 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()
чтобы закрыть клавиатуру в любой ситуации.
Основываясь на ответе @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))
}
}
Простое решение для нажатия кнопки «снаружи», которое сработало для меня:
Сначала укажите ZStack перед всеми представлениями. В нем поместите фон (с выбранным вами цветом) и укажите Жест касания. В вызове жеста вызовите sendAction, который мы видели выше:
import SwiftUI
struct MyView: View {
private var myBackgroundColor = Color.red
@State var text = "text..."
var body: some View {
ZStack {
self.myBackgroundColor.edgesIgnoringSafeArea(.all)
.onTapGesture(count: 1) {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
TextField("", text: $text)
.textFieldStyle(RoundedBorderTextFieldStyle())
.padding()
}
}
}
extension UIApplication {
func endEditing() {
sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
с помощью и на + iOS 15
используя и@FocusState
вы можете отклонить клавиатуру при любом нажатии возврата, или вы можете выбрать другое, чтобы затем получить фокус:
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)
}
.onSubmit {
fieldInFocus = nil
}
}
}
}
Или, если вы хотите использовать.onSubmit
чтобы сосредоточить внимание на другомTextField
:
.onSubmit {
} if fieldInFocus == .email {
fieldInFocus = .password
} else if fieldInFocus == .password {
fieldInFocus = nil
}
}
До сих пор вышеуказанные параметры не работали для меня, потому что у меня есть форма и внутренние кнопки, ссылки, средство выбора...
Я создаю работающий ниже код с помощью приведенных выше примеров.
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 способ отключить клавиатуру с помощью касания без блокировки каких-либо сложных форм или чего-то еще ... кредит @user3441734 за то, что он пометил GestureMask как чистый подход.
Монитор UIWindow.keyboardWillShowNotification / willHide
Передайте текущее состояние клавиатуры через EnvironmentKey, установленный в корневом представлении /
Протестировано на iOS 14.5.
Прикрепите жест увольнения к форме
Form { }
.dismissKeyboardOnTap()
Настроить монитор в корневом представлении
// Root view
.environment(\.keyboardIsShown, keyboardIsShown)
.onDisappear { dismantleKeyboarMonitors() }
.onAppear { setupKeyboardMonitors() }
// Monitors
@State private var keyboardIsShown = false
@State private var keyboardHideMonitor: AnyCancellable? = nil
@State private var keyboardShownMonitor: AnyCancellable? = nil
func setupKeyboardMonitors() {
keyboardShownMonitor = NotificationCenter.default
.publisher(for: UIWindow.keyboardWillShowNotification)
.sink { _ in if !keyboardIsShown { keyboardIsShown = true } }
keyboardHideMonitor = NotificationCenter.default
.publisher(for: UIWindow.keyboardWillHideNotification)
.sink { _ in if keyboardIsShown { keyboardIsShown = false } }
}
func dismantleKeyboarMonitors() {
keyboardHideMonitor?.cancel()
keyboardShownMonitor?.cancel()
}
SwiftUI Gesture + Sugar
struct HideKeyboardGestureModifier: ViewModifier {
@Environment(\.keyboardIsShown) var keyboardIsShown
func body(content: Content) -> some View {
content
.gesture(TapGesture().onEnded {
UIApplication.shared.resignCurrentResponder()
}, including: keyboardIsShown ? .all : .none)
}
}
extension UIApplication {
func resignCurrentResponder() {
sendAction(#selector(UIResponder.resignFirstResponder),
to: nil, from: nil, for: nil)
}
}
extension View {
/// Assigns a tap gesture that dismisses the first responder only when the keyboard is visible to the KeyboardIsShown EnvironmentKey
func dismissKeyboardOnTap() -> some View {
modifier(HideKeyboardGestureModifier())
}
/// Shortcut to close in a function call
func resignCurrentResponder() {
UIApplication.shared.resignCurrentResponder()
}
}
EnvironmentKey
extension EnvironmentValues {
var keyboardIsShown: Bool {
get { return self[KeyboardIsShownEVK] }
set { self[KeyboardIsShownEVK] = newValue }
}
}
private struct KeyboardIsShownEVK: EnvironmentKey {
static let defaultValue: Bool = false
}
расширение UIView{
переопределить open func touchesBegan(_ touches: Set, with event: UIEvent?) {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}}
iOS 13+
Один простой хак для iOS13+ — установить переменную состояния для «отключено» для каждого отдельного текстового поля. Очевидно, что это не идеально, но в некоторых случаях может помочь.
Как только вы установитеdisabled = True
, то все связанные респонденты автоматически уходят в отставку.
@State var isEditing: Bool
@State var text: String
....
TextField("Text", text: self.$text).disabled(!self.isEditing)