Як приховати клавіатуру під час використання SwiftUI?


88

Як приховати keyboardвикористання SwiftUIдля нижченаведених випадків?

Випадок 1

У мене є, TextFieldі мені потрібно приховати, keyboardколи користувач натискає returnкнопку.

Випадок 2

У мене є, TextFieldі мені потрібно приховати, keyboardколи користувач натискає назовні.

Як я можу це зробити за допомогою SwiftUI?

Примітка:

Я не задавав питання щодо UITextField. Я хочу зробити це за допомогою SwifUI.TextField.


29
@DannyBuonocore Ще раз уважно прочитайте моє запитання!
Hitesh Сурани

9
@DannyBuonocore Це не дублікат згаданого питання. Це питання стосується SwiftUI, а інший звичайний UIKit
Johnykutty

1
@DannyBuonocore, будь ласка, звернувся до developer.apple.com/documentation/swiftui, щоб знайти різницю між UIKit та SwiftUI. Спасибі
Hitesh Сурани

Я додав тут своє рішення, сподіваюся, воно вам допоможе.
Віктор Кушньоров

Більшість рішень тут не працюють за бажанням, оскільки вони відключають бажані реакції на інших кранах управління. Робоче рішення можна знайти тут: forums.developer.apple.com/thread/127196
Харді

Відповіді:


79

Ви можете змусити першого відповідача подати у відставку, надіславши дію до спільної програми:

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()
    }
}

1
.keyWindowзараз застаріла. Дивіться відповідь Лоренцо Сантіні .
LinusGeffarth

3
Крім того, .tapActionбуло перейменовано на.onTapGesture
LinusGeffarth

Чи можна клавіатуру відхилити, коли альтернативне управління стає активним? stackoverflow.com/questions/58643512 / ...
ярма

1
Чи є спосіб зробити це без білого фону, я використовую розпірки, і мені він потрібен, щоб виявити жест натискання на розпір. Також стратегія на білому фоні створює проблему на нових iPhone, де зараз є додатковий простір на екрані. Будь-яка допомога оцінена!
Джозеф

Я розмістив відповідь, яка покращує ваш дизайн. Не соромтеся редагувати свою відповідь, якщо хочете, я не дбаю про кредит.
Джозеф

61

Після багатьох спроб я знайшов рішення, яке (на даний момент) не блокує жодного елемента керування - додавши в нього розпізнавач жестів UIWindow.

  1. Якщо ви хочете закрити клавіатуру лише на Торкніться зовні (без обробки перетягувань) - тоді досить скористатися просто UITapGestureRecognizerта просто скопіювати крок 3:
  2. Створіть власний клас розпізнавача жестів, який працює з будь-якими дотиками:

    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
        }
    }
    
  3. У 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)  
    
  4. Реалізуйте, UIGestureRecognizerDelegateщоб дозволити одночасні дотики.

    extension SceneDelegate: UIGestureRecognizerDelegate {
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
            return true
        }
    }
    

Тепер будь-яка клавіатура на будь-якому поданні буде закрита на дотик або перетягнута назовні.

PS Якщо ви хочете закрити лише певні поля TextField - додайте та видаліть розпізнавач жестів у вікно, коли буде викликаний зворотний виклик TextField onEditingChanged


3
Ця відповідь повинна бути вгорі. Інші відповіді не вдаються, коли у поданні є інші елементи управління.
Імта

1
@RolandLariotte оновила відповідь, щоб виправити цю поведінку, подивіться на нову реалізацію AnyGestureRecognizer
Михайло

1
Чудова відповідь. Працює бездоганно. @Mikhail насправді цікавиться, як ви видаляєте розпізнавач жестів спеціально для деяких текстових полів (я створив автозаповнення з тегами, тому кожного разу, коли я натискаю елемент у списку, я не хочу, щоб це конкретне текстове поле втратило фокус)
Pasta

1
це рішення насправді чудове, але після його використання приблизно 3 місяці, на жаль, я виявив помилку, безпосередньо спричинену подібним зломкою. будь ласка, пам’ятайте, що те саме відбувається з вами
glassomoss

1
Фантастична відповідь! Цікаво, як це буде реалізовано в iOS 14 без делегування сцени?
Дом

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)                    
        }
    }
}

"Виправлення помилки" - це просто те, що keyWindow!.endEditing(true)належним чином повинно бути keyWindow?.endEditing(true)(так, ви можете заперечити, що це не може статися).

Більш цікавим є те, як ви можете ним користуватися. Наприклад, припустимо, у вас є форма з кількома полями для редагування. Просто оберніть його так:

Form {
    .
    .
    .
}
.modifier(DismissingKeyboard())

Тепер, натиснувши будь-який елемент управління, який сам по собі не має клавіатури, буде виконано відповідне відхилення.

(Перевірено бета-версією 7)


6
Хммм - натискання на інші елементи керування більше не реєструється. Подія проковтнута.
ЯРМ

Я не можу повторити це - це все ще працює для мене, використовуючи останні краплі від Apple станом на 11/1. Це спрацювало, а потім просто перестало працювати для вас, чи ??
Фелдур

Якщо у вас є DatePicker у формі, то DatePicker більше не відображатиметься
Альберт,

@Albert - це правда; щоб скористатися цим підходом, вам доведеться розбити, де елементи прикрашені DismissingKeyboard (), до більш тонкого зернистого рівня, який застосовується до елементів, які слід відхилити, і уникає DatePicker.
Фелдур

Використання цього коду відтворить попередженняCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314

23

Я відчув це під час використання 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)})

Це призведе onDelete (проведіть пальцем, щоб видалити) до дивної поведінки.
Тарек Халлак,

Це приємно, а як щодо крана?
Danny182

20

Я знайшов інший спосіб відхилити клавіатуру, яка не вимагає доступу до keyWindowвластивості; по суті, компілятор видає попередження за допомогою

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' застаріло в iOS 13.0: Не слід використовувати для програм, що підтримують кілька сцен, оскільки повертає ключове вікно для всіх підключених сцен

Натомість я використав цей код:

UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)

15

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()
        }
    }

цього достатньо для кожного перегляду за допомогою клавіатури у вашому додатку ...


4
Це створює ще одну проблему - у мене є форма вибору у формі {} поряд із текстовим полем, вона не відповідає. Я не знайшов рішення, використовуючи всі відповіді в цій темі. Але ваша відповідь хороша для відхилення клавіатури натисканням деінде - якщо ви не використовуєте інструменти вибору.
Налов

Привіт. мій код `` `var body: деякий Перегляд {NavigationView {Форма {Розділ {Розділ {TextField (" typesomething ", текст: $ c)} Розділ {Вибір (" ім'я ", виділення: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` `Клавіатура відхиляється при натисканні в іншому місці, але інструмент вибору не відповідає. Я не знайшов способу змусити це працювати.
Налов

2
Знову привіт, на даний момент у мене є два рішення: перше - це використання рідної клавіатури, відхиленої на кнопці повернення, друге - це незначна зміна режиму натискання (він же „костиль“) - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (кол: 2, виконати: {window.endEditing (true)})) Сподіваюсь, це вам допоможе ...
Dim Novo,

Привіт. Дякую. Другий спосіб вирішив. Я використовую цифрову клавіатуру, тому користувачі можуть вводити лише цифри, у неї немає ключа повернення. Звільнення за допомогою постукування було те, що я шукав.
Налов

це призведе до того, що в списку не можна переміщатися.
Cui Mingda

14

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)
    }
}

2
Це чудово працює! Дякуємо за рішення
NotAPhoenix

2
Це повинно бути на висоті, оскільки пам’ятає про новий життєвий цикл SwiftUI.
carlosobedgomez

Це чудово працює. Однак якщо я двічі натискаю в текстовому полі, замість виділення тексту клавіатура тепер зникає. Будь-яка ідея, як я можу дозволити подвійне натискання для вибору?
Гері

@Gary У нижньому розширенні ви можете побачити рядок із коментарем, встановленим на false, якщо ви не хочете виявляти натискання під час інших жестів . Просто встановіть це return false.
pawello2222

Якщо встановити значення false, це працює, але тоді клавіатура також не відпускає, якщо хтось довго натискає, перетягує або прокручує за межі текстової області. Чи є спосіб встановити значення false лише для подвійних клацань (бажано подвійних клацань всередині текстового поля, але навіть для всіх подвійних клацань це потрібно).
Гері,

11

Моє рішення, як приховати програмну клавіатуру, коли користувачі натискають зовні. Вам потрібно використовувати за contentShapeдопомогою, onLongPressGestureщоб виявити весь контейнер перегляду. onTapGestureпотрібно, щоб уникнути блокування фокусу на TextField. Ви можете використовувати onTapGestureзамість цього, onLongPressGestureале елементи Навігаційної панелі не працюватимуть.

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: {})
    }
}

Це чудово працювало, я використовував його дещо по-іншому і мав бути впевненим, що це було викликано в основній темі.
keegan3d

7

додайте цей модифікатор до подання, яке ви хочете виявити, натискання користувача

.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)

        }

7

Я вважаю за краще використовувати .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)
}

Все одно мерехтіння за допомогою цього методу.
Даніель Райан,

Це чудово працювало, я використовував його дещо по-іншому і мав бути впевненим, що це було викликано в основній темі.
keegan3d

6

Тому що keyWindowє застарілим.

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.windows.forEach { $0.endEditing(force)}
    }
}

1
forceПараметр не використовується. Це повинно бути{ $0.endEditing(force)}
Давіде

5

Здається, endEditingрішення є єдиним, як @rraphael вказав.
Найчистіший приклад, який я бачив дотепер, такий:

extension View {
    func endEditing(_ force: Bool) {
        UIApplication.shared.keyWindow?.endEditing(force)
    }
}

а потім використовувати його в onCommit:


2
.keyWindowзараз застаріла. Дивіться відповідь Лоренцо Сантіні .
LinusGeffarth

Це знецінено на iOS 13+
Ahmadreza

4

Розширюючи відповідь @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()
Ravindra_Bhati

2

Цей метод дозволяє приховати клавіатуру на розпірних проставках!

Спочатку додайте цю функцію (Кредит надано : Каспер Зандберген, з SwiftUI не може натиснути в Spacer 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 функції (Кредит, наданий: rraphael, з цього питання)

extension UIApplication {
    func endEditing() {
        sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
    }
}

Функція нижче буде додана до вашого класу Перегляду, просто зверніться до верхньої відповіді тут від rraphael для отримання більш докладної інформації.

private func endEditing() {
   UIApplication.shared.endEditing()
}

Нарешті, тепер ви можете просто зателефонувати ...

Spacer().onTapGesture {
    self.endEditing()
}

Це змусить будь-яку область розпірки закрити клавіатуру зараз. Більше не потрібно переглядати великий білий фон!

Ви можете гіпотетично застосувати цю техніку extensionдо будь-яких елементів керування, які вам потрібні для підтримки TapGestures, які в даний час цього не роблять, і викликати onTapGestureфункцію в поєднанні із self.endEditing()закриттям клавіатури в будь-якій ситуації, яку ви хочете.


Моє питання зараз полягає в тому, як викликати коміт у текстовому полі, коли клавіатура відходить таким чином? в даний час `` коміт '' спрацьовує, лише якщо ви натиснете клавішу повернення на клавіатурі iOS.
Джозеф


2

На основі відповіді @ 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))
    }
}

2

Ви можете повністю уникнути взаємодії з 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) без будь-яких проблем.


0

клавіатури ReturnKey

На додаток до всіх відповідей щодо натискання поза текстовим полем, можливо, ви захочете відхилити клавіатуру, коли користувач натискає клавішу повернення на клавіатурі:

визначте цю глобальну функцію:

func resignFirstResponder() {
    UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}

І додайте використання в onCommitаргументі it:

TextField("title", text: $text, onCommit:  {
    resignFirstResponder()
})

Переваги

  • Ви можете зателефонувати йому з будь-якого місця
  • Це не залежить від UIKit або SwiftUI (може використовуватися в програмах Mac)
  • Це працює навіть в iOS 13

Демо

демо


0

Поки що вищезазначені варіанти для мене не працювали, тому що у мене є форма та внутрішні кнопки, посилання, інструмент вибору ...

Я створюю нижчий код, який працює, за допомогою наведених вище прикладів.

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()

-2

SwiftUI, випущений в червні / 2020 р. З Xcode 12 та iOS 14, додає модифікатор hideKeyboardOnTap (). Це має вирішити вашу справу №2. Рішення для вашої справи №1 надається безкоштовно з Xcode 12 та iOS 14: клавіатура за замовчуванням для TextField автоматично ховається, коли натискається кнопка повернення.


1
У iOS14 немає модифікатора hideKeyboardOnTap
Тео Сарторі,

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.