Bagaimana cara menyembunyikan keyboard saat menggunakan SwiftUI?


96

Bagaimana cara menyembunyikan keyboardmenggunakan SwiftUIuntuk kasus di bawah ini?

Kasus 1

Saya punya TextFielddan saya perlu menyembunyikan keyboardsaat pengguna mengklik returntombol.

Kasus 2

Saya punya TextFielddan saya perlu menyembunyikan keyboardsaat 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.


29
@DannyBuonocore Baca pertanyaan saya dengan cermat lagi!
Hitesh Surani

9
@DannyBuonocore Ini bukan duplikat dari pertanyaan yang disebutkan. Pertanyaan ini tentang SwiftUI, dan lainnya adalah UIKit normal
Johnykutty

1
@DannyBuonocore silakan lihat developer.apple.com/documentation/swiftui untuk menemukan perbedaan antara UIKit dan SwiftUI. Terima kasih
Hitesh Surani

Saya menambahkan solusi saya di sini, saya harap ini membantu Anda.
Victor Kushnerov

Sebagian besar solusi di sini tidak berfungsi seperti yang diinginkan, karena menonaktifkan reaksi yang diinginkan pada tap kontrol lainnya. Solusi yang berfungsi dapat ditemukan di sini: forums.developer.apple.com/thread/127196
Hardy

Jawaban:


86

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

2
.keyWindowsekarang tidak digunakan lagi. Lihat jawaban Lorenzo Santini .
LinusGeffarth

4
Juga, .tapActiontelah diubah namanya menjadi.onTapGesture
LinusGeffarth

Bisakah keyboard ditutup ketika kontrol alternatif menjadi aktif? stackoverflow.com/questions/58643512/…
Yarm

1
Apakah ada cara untuk melakukan ini tanpa latar belakang putih, saya menggunakan spacer dan saya membutuhkannya untuk mendeteksi gerakan ketukan pada spacer. Juga strategi latar belakang putih menciptakan masalah pada iPhone yang lebih baru di mana ada ruang layar ekstra di atasnya sekarang. Setiap bantuan dihargai!
Joseph Astrahan

2
Mungkin juga perlu diperhatikan bahwa itu UIApplicationadalah bagian dari UIKit, jadi perlu import UIKit.
Alienbash

62

Setelah banyak upaya, saya menemukan solusi yang (saat ini) tidak memblokir kontrol apa pun - menambahkan pengenal gerakan ke UIWindow.

  1. Jika Anda ingin menutup keyboard hanya di Tap di luar (tanpa menangani seret) - cukup gunakan saja UITapGestureRecognizerdan salin saja langkah 3:
  2. 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
        }
    }
    
  3. Di SceneDelegate.swiftdalam func 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)  
    
  4. Terapkan UIGestureRecognizerDelegateuntuk 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


3
Jawaban ini harus ada di atas. Jawaban lain gagal jika ada kontrol lain dalam tampilan.
Imthath

1
@RolandLariotte memperbarui jawaban untuk memperbaiki perilaku ini, lihat implementasi baru AnyGestureRecognizer
Mikhail

2
Jawaban yang luar biasa. Bekerja dengan sempurna. @Mikhail sebenarnya tertarik untuk mengetahui bagaimana Anda menghapus pengenal isyarat khusus untuk beberapa bidang teks (Saya membuat pelengkapan otomatis dengan tag, jadi setiap kali saya mengetuk elemen dalam daftar, saya tidak ingin bidang teks khusus ini kehilangan fokus)
Pasta

1
solusi ini sebenarnya bagus, tetapi setelah menggunakannya selama 3 bulan, sayangnya saya menemukan bug, yang secara langsung disebabkan oleh peretasan semacam ini. tolong, waspadalah terhadap hal yang sama terjadi pada Anda
glassomoss

1
Jawaban yang fantastis! Saya bertanya-tanya bagaimana ini akan diimplementasikan dengan iOS 14 tanpa scenedelegate?
Dom

27

@ 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 semestinya keyWindow?.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)


6
Hmmm - mengetuk kontrol lain tidak lagi terdaftar. Peristiwa itu ditelan.
Yarm

Saya tidak dapat meniru itu - masih berfungsi untuk saya menggunakan tetes terbaru dari Apple mulai 11/1. Apakah itu berhasil dan kemudian berhenti bekerja untuk Anda, atau ??
Feldur

Jika Anda memiliki DatePicker dalam formulir, maka DatePicker tidak akan ditampilkan lagi
Albert

@Albert - itu benar; untuk menggunakan pendekatan ini, Anda harus memecah tempat item didekorasi dengan DismissingKeyboard () ke tingkat yang lebih halus yang berlaku untuk elemen yang harus menutup dan menghindari DatePicker.
Feldur

Penggunaan kode ini akan mereproduksi peringatanCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314

25

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

1
Ini akan menyebabkan onDelete (geser untuk menghapus) ke perilaku aneh.
Tarek Hallak

Ini bagus, tapi bagaimana dengan kerannya?
DanielZanchi

21

Saya menemukan cara lain untuk menutup keyboard yang tidak memerlukan akses ke keyWindowproperti; Sebenarnya kompilator memberikan kembali peringatan menggunakan

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' tidak digunakan lagi di iOS 13.0: Tidak boleh digunakan untuk aplikasi yang mendukung banyak adegan karena mengembalikan jendela kunci di semua adegan yang terhubung

Sebagai gantinya saya menggunakan kode ini:

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

21

SwiftUI 2

Berikut adalah solusi terbaru untuk SwiftUI 2 / iOS 14 (awalnya diusulkan di sini oleh Mikhail).

Itu tidak menggunakan AppDelegateatau SceneDelegateyang 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)
    }
}

2
Ini harus menjadi yang teratas karena mengingat siklus hidup SwiftUI yang baru.
carlosobedgomez

Ini bekerja dengan baik. Namun jika saya mengetuk dua kali di bidang teks, alih-alih memilih teks, keyboard sekarang menghilang. Tahu bagaimana saya dapat mengizinkan ketuk dua kali untuk seleksi?
Gary

@Gary Di ekstensi bawah Anda dapat melihat baris dengan komentar disetel ke false jika Anda tidak ingin mendeteksi ketukan selama gerakan lain . Setel saja ke return false.
pawello2222

Menyetelnya ke false berfungsi, tetapi kemudian keyboard juga tidak menutup jika seseorang menekan atau menyeret atau menggulir lama di luar area teks. Apakah ada cara untuk menyetelnya ke false hanya untuk klik ganda (sebaiknya klik dua kali di dalam bidang teks, tetapi bahkan hanya semua klik ganda sudah cukup).
Gary

1
Untuk menjawab pertanyaan saya sendiri, saya menyetelnya kembali ke true, lalu saya menyetel tapGesture = AnyGestureRecognizer (...) yang dibuat Mikhail dalam jawabannya daripada tapGesture = UITapGestureRecognizer (...). Ini memungkinkan ketukan ganda untuk memilih teks di dalam bidang teks sementara juga memungkinkan berbagai gerakan untuk menyembunyikan keyboard di luar bidang teks.
Gary

15

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


4
Ini memberikan masalah lain - saya memiliki alat pilih dalam Formulir {} di samping bidang teks, itu menjadi tidak responsif. Saya tidak menemukan solusi untuk itu menggunakan semua jawaban dalam topik ini. Tetapi jawaban Anda bagus untuk menutup keyboard dengan ketukan di tempat lain - jika Anda tidak menggunakan alat pilih.
Nalov

Halo. kode saya `` `var body: some View {NavigationView {Formulir {Bagian {TextField (" typesomething ", teks: $ c)} Bagian {Picker (" name ", selection: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` Keyboard ditutup saat mengetuk di tempat lain, tapi alat pilih menjadi tidak responsif. Saya tidak menemukan cara untuk membuatnya berhasil.
Nalov

2
Hai lagi, saat ini saya memiliki dua solusi: yang pertama - adalah menggunakan keyboard asli yang dihilangkan pada tombol kembali, yang kedua - adalah mengubah penanganan penyadapan sedikit (alias 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (count: 2, lakukan: {window.endEditing (true)})) Semoga ini bisa membantu Anda ...
Redupkan Novo

Halo. Terima kasih. Cara kedua menyelesaikannya. Saya menggunakan tombol angka, sehingga pengguna hanya dapat memasukkan angka, tidak ada tombol kembali. Mengabaikan dengan mengetuk adalah apa yang saya cari.
Nalov

ini akan menyebabkan daftar tidak dapat dinavigasi.
Cui Mingda

11

Solusi saya cara menyembunyikan keyboard perangkat lunak saat pengguna mengetuk di luar. Anda perlu menggunakan contentShapewith onLongPressGestureuntuk mendeteksi seluruh penampung View. onTapGesturediperlukan untuk menghindari pemblokiran fokus TextField. Anda dapat menggunakan onTapGesturesebagai ganti onLongPressGesturetetapi 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: {})
    }
}

Ini berfungsi dengan baik, saya menggunakannya sedikit berbeda dan harus memastikan itu dipanggil di utas utama.
keegan3d

7

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)

        }

7

Saya lebih suka menggunakan .onLongPressGesture(minimumDuration: 0), yang tidak menyebabkan keyboard berkedip ketika yang lain TextViewdiaktifkan (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)
}

Masih berkedip menggunakan metode ini.
Daniel Ryan

Ini berfungsi dengan baik, saya menggunakannya sedikit berbeda dan harus memastikan itu dipanggil di utas utama.
keegan3d

6

Karena keyWindowsudah usang.

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

1
The forceparameter tidak digunakan. Seharusnya{ $0.endEditing(force)}
Davide

5

Sepertinya endEditingsolusinya 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:


2
.keyWindowsekarang tidak digunakan lagi. Lihat jawaban Lorenzo Santini .
LinusGeffarth

Ini disusutkan di iOS 13+
Ahmadreza

4

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 untuk Gestures.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.


apa artinya eraseToAny()
Ravindra_Bhati

2

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 extensionke setiap kontrol yang Anda butuhkan untuk mendukung TapGestures yang saat ini tidak melakukannya dan memanggil onTapGesturefungsi tersebut bersama self.endEditing()untuk menutup keyboard dalam situasi apa pun yang Anda inginkan.


Pertanyaan saya sekarang adalah bagaimana Anda memicu komit pada bidang teks saat Anda mendapatkan keyboard pergi dengan cara ini? saat ini 'komit' hanya terpicu jika Anda menekan tombol kembali pada keyboard iOS.
Joseph Astrahan


2

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

2

Anda sepenuhnya dapat menghindari interaksi dengan UIKit dan menerapkannya dalam SwiftUI murni . Cukup tambahkan .id(<your id>)pengubah ke Anda TextFielddan 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.


1

ReturnKunci Keyboard

Selain 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 onCommitargumen itu:

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

Manfaat

  • Anda bisa meneleponnya dari mana saja
  • Ini tidak tergantung pada UIKit atau SwiftUI (dapat digunakan di aplikasi mac)
  • Ia bekerja bahkan di iOS 13

Demo

demo


0

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.