Bagaimana cara menyembunyikan keyboard
menggunakan SwiftUI
untuk kasus di bawah ini?
Kasus 1
Saya punya TextField
dan saya perlu menyembunyikan keyboard
saat pengguna mengklik return
tombol.
Kasus 2
Saya punya TextField
dan saya perlu menyembunyikan keyboard
saat pengguna mengetuk di luar.
Bagaimana saya bisa melakukan ini dengan menggunakan SwiftUI
?
catatan:
Saya belum mengajukan pertanyaan tentang UITextField
. Saya ingin melakukannya dengan menggunakan SwifUI.TextField
.
Jawaban:
Anda dapat memaksa responden pertama untuk mengundurkan diri dengan mengirimkan tindakan ke aplikasi bersama:
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Sekarang Anda dapat menggunakan metode ini untuk menutup keyboard kapan pun Anda mau:
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() } } } }
Jika Anda ingin menutup keyboard dengan tap out, Anda dapat membuat tampilan putih layar penuh dengan tap action, yang akan memicu
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() } }
sumber
.keyWindow
sekarang tidak digunakan lagi. Lihat jawaban Lorenzo Santini ..tapAction
telah diubah namanya menjadi.onTapGesture
UIApplication
adalah bagian dari UIKit, jadi perluimport UIKit
.Setelah banyak upaya, saya menemukan solusi yang (saat ini) tidak memblokir kontrol apa pun - menambahkan pengenal gerakan ke
UIWindow
.UITapGestureRecognizer
dan salin saja langkah 3:Buat kelas pengenal isyarat khusus yang berfungsi dengan sentuhan apa pun:
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 } }
Di
SceneDelegate.swift
dalamfunc scene
, tambahkan kode berikutnya: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)
Terapkan
UIGestureRecognizerDelegate
untuk memungkinkan sentuhan simultan.extension SceneDelegate: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
Sekarang keyboard apa pun di tampilan mana pun akan ditutup saat disentuh atau diseret ke luar.
PS Jika Anda ingin menutup hanya BidangTeks tertentu - lalu tambahkan dan hapus pengenal gerakan ke jendela setiap kali disebut panggilan balik BidangTeks
onEditingChanged
sumber
@ Jawaban RyanTCB bagus; berikut adalah beberapa penyempurnaan yang membuatnya lebih mudah digunakan dan menghindari potensi kerusakan:
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) } } }
'Perbaikan bug'
keyWindow!.endEditing(true)
seharusnya dilakukan dengan semestinyakeyWindow?.endEditing(true)
(ya, Anda mungkin berpendapat itu tidak dapat terjadi.)Yang lebih menarik adalah bagaimana Anda bisa menggunakannya. Misalnya, Anda memiliki formulir dengan beberapa bidang yang dapat diedit di dalamnya. Bungkus saja seperti ini:
Form { . . . } .modifier(DismissingKeyboard())
Sekarang, mengetuk kontrol apa pun yang tidak menampilkan keyboard akan melakukan penutupan yang sesuai.
(Diuji dengan beta 7)
sumber
Can't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
Saya mengalami ini saat menggunakan TextField di dalam NavigationView. Ini adalah solusi saya untuk itu. Ini akan menutup keyboard saat Anda mulai menggulir.
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)})
sumber
Saya menemukan cara lain untuk menutup keyboard yang tidak memerlukan akses ke
keyWindow
properti; Sebenarnya kompilator memberikan kembali peringatan menggunakanUIApplication.shared.keyWindow?.endEditing(true)
Sebagai gantinya saya menggunakan kode ini:
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
sumber
SwiftUI 2
Berikut adalah solusi terbaru untuk SwiftUI 2 / iOS 14 (awalnya diusulkan di sini oleh Mikhail).
Itu tidak menggunakan
AppDelegate
atauSceneDelegate
yang hilang jika Anda menggunakan siklus hidup 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 } }
Berikut adalah contoh cara mendeteksi gerakan simultan kecuali gerakan Tekan Lama:
extension UIApplication: UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self) } }
sumber
return false
.SwiftUI di file 'SceneDelegate.swift' tambahkan saja: .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() } }
ini cukup untuk setiap Tampilan menggunakan keyboard di aplikasi Anda ...
sumber
Solusi saya cara menyembunyikan keyboard perangkat lunak saat pengguna mengetuk di luar. Anda perlu menggunakan
contentShape
withonLongPressGesture
untuk mendeteksi seluruh penampung View.onTapGesture
diperlukan untuk menghindari pemblokiran fokusTextField
. Anda dapat menggunakanonTapGesture
sebagai gantionLongPressGesture
tetapi item NavigationBar tidak akan berfungsi.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: {}) } }
sumber
tambahkan pengubah ini ke tampilan yang ingin Anda deteksi ketukan pengguna
.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) }
sumber
Saya lebih suka menggunakan
.onLongPressGesture(minimumDuration: 0)
, yang tidak menyebabkan keyboard berkedip ketika yang lainTextView
diaktifkan (efek samping.onTapGesture
). Kode sembunyikan keyboard dapat menjadi fungsi yang dapat digunakan kembali..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) }
sumber
Karena
keyWindow
sudah usang.extension View { func endEditing(_ force: Bool) { UIApplication.shared.windows.forEach { $0.endEditing(force)} } }
sumber
force
parameter tidak digunakan. Seharusnya{ $0.endEditing(force)}
Sepertinya
endEditing
solusinya adalah satu-satunya yang seperti yang ditunjukkan @rraphael.Contoh terbersih yang pernah saya lihat sejauh ini adalah:
extension View { func endEditing(_ force: Bool) { UIApplication.shared.keyWindow?.endEditing(force) } }
dan kemudian menggunakannya di
onCommit:
sumber
.keyWindow
sekarang tidak digunakan lagi. Lihat jawaban Lorenzo Santini .Memperluas jawaban oleh @Feldur (yang didasarkan pada @ RyanTCB's), berikut adalah solusi yang bahkan lebih ekspresif dan kuat yang memungkinkan Anda untuk menutup keyboard pada gerakan lain daripada
onTapGesture
, Anda dapat menentukan yang Anda inginkan dalam panggilan fungsi.Pemakaian
// MARK: - View extension RestoreAccountInputMnemonicScreen: View { var body: some View { List(viewModel.inputWords) { inputMnemonicWord in InputMnemonicCell(mnemonicInput: inputMnemonicWord) } .dismissKeyboard(on: [.tap, .drag]) } }
Atau menggunakan
All.gestures
(hanya gula untukGestures.allCases
🍬).dismissKeyboard(on: All.gestures)
Kode
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)) } }
Kata hati-hati
Harap perhatikan bahwa jika Anda menggunakan semua gerakan, mereka mungkin bertentangan dan saya tidak menemukan solusi yang rapi untuk menyelesaikannya.
sumber
eraseToAny()
Metode ini memungkinkan Anda menyembunyikan keyboard di spacer!
Pertama tambahkan fungsi ini (Kredit Diberikan Kepada: Casper Zandbergen, dari SwiftUI tidak dapat mengetuk di 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 } } }
Selanjutnya tambahkan 2 fungsi berikut (Kredit Diberikan Kepada: rraphael, dari pertanyaan ini)
extension UIApplication { func endEditing() { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } }
Fungsi di bawah ini akan ditambahkan ke kelas View Anda, cukup lihat jawaban teratas di sini dari rraphael untuk lebih jelasnya.
private func endEditing() { UIApplication.shared.endEditing() }
Akhirnya, Anda sekarang dapat memanggil ...
Spacer().onTapGesture { self.endEditing() }
Ini akan membuat area spacer menutup keyboard sekarang. Tidak perlu lagi tampilan latar belakang putih besar!
Anda dapat secara hipotetis menerapkan teknik ini
extension
ke setiap kontrol yang Anda butuhkan untuk mendukung TapGestures yang saat ini tidak melakukannya dan memanggilonTapGesture
fungsi tersebut bersamaself.endEditing()
untuk menutup keyboard dalam situasi apa pun yang Anda inginkan.sumber
Silakan periksa https://github.com/michaelhenry/KeyboardAvoider
Cukup sertakan
KeyboardAvoider {}
di atas tampilan utama Anda dan itu saja.KeyboardAvoider { VStack { TextField() TextField() TextField() TextField() } }
sumber
Berdasarkan jawaban @ Sajjon, berikut adalah solusi yang memungkinkan Anda untuk menutup keyboard saat ketuk, tekan lama, seret, perbesaran, dan gerakan rotasi sesuai pilihan Anda.
Solusi ini bekerja di XCode 11.4
Penggunaan untuk mendapatkan perilaku yang diminta oleh @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) } }
Kode
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)) } }
sumber
Anda sepenuhnya dapat menghindari interaksi dengan UIKit dan menerapkannya dalam SwiftUI murni . Cukup tambahkan
.id(<your id>)
pengubah ke AndaTextField
dan ubah nilainya setiap kali Anda ingin menutup keyboard (saat menggesek, melihat ketukan, tombol tindakan, ..).Implementasi sampel:
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 }) } } }
Perhatikan bahwa saya hanya mengujinya di Xcode 12 beta terbaru, tetapi seharusnya berfungsi dengan versi yang lebih lama (bahkan Xcode 11) tanpa masalah apa pun.
sumber
Return
Kunci KeyboardSelain semua jawaban tentang mengetuk di luar bidang teks, Anda mungkin ingin menutup keyboard saat pengguna mengetuk tombol kembali di keyboard:
tentukan fungsi global ini:
func resignFirstResponder() { UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) }
Dan tambahkan gunakan dalam
onCommit
argumen itu:TextField("title", text: $text, onCommit: { resignFirstResponder() })
Manfaat
Demo
sumber
Sejauh ini opsi di atas tidak berfungsi untuk saya, karena saya memiliki tombol Formulir dan di dalam, tautan, alat pilih ...
Saya membuat kode di bawah ini yang berfungsi, dengan bantuan dari contoh di atas.
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()) } }
Pemakaian:
var body: some View { NavigationView { Form { picker button textfield text } .dismissingKeyboard()
sumber
SwiftUI dirilis pada Juni / 2020 dengan Xcode 12 dan iOS 14 menambahkan pengubah hideKeyboardOnTap (). Ini akan menyelesaikan kasus nomor 2. Solusi untuk kasus nomor 1 Anda hadir secara gratis dengan Xcode 12 dan iOS 14: keyboard default untuk TextField bersembunyi secara otomatis saat tombol Kembali ditekan.
sumber