SwiftUI kullanırken klavye nasıl gizlenir?


96

Nasıl gizlemek için keyboardkullanan SwiftUIvakalarda altında mı?

Dava 1

Bende var TextFieldve keyboardkullanıcı returndüğmeyi tıkladığında gizlemem gerekiyor .

Durum 2

Ben var TextFieldve keyboardkullanıcı dışarıya dokunduğunda gizlemem gerekiyor .

Bunu kullanarak nasıl yapabilirim SwiftUI?

Not:

İle ilgili bir soru sormadım UITextField. Kullanarak yapmak istiyorum SwifUI.TextField.


29
@DannyBuonocore Sorumu tekrar dikkatlice okuyun!
Hitesh Surani

9
@DannyBuonocore Bu, bahsedilen sorunun kopyası değildir. Bu soru SwiftUI ile ilgili ve diğeri normal UIKit
Johnykutty

1
@DannyBuonocore baktık lütfen developer.apple.com/documentation/swiftui UIKit ve SwiftUI arasındaki farkı bulmak için. Teşekkürler
Hitesh Surani

Çözümümü buraya ekledim umarım size yardımcı olur.
Victor Kushnerov

Buradaki çoğu çözüm, diğer kontrol musluklarında istenen reaksiyonları devre dışı bıraktıkları için istenildiği gibi çalışmaz. Bir çalışma çözümü burada bulunabilir: forums.developer.apple.com/thread/127196
Hardy

Yanıtlar:


86

Paylaşılan uygulamaya bir eylem göndererek ilk yanıtlayanı istifa etmeye zorlayabilirsiniz:

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

Artık klavyeyi istediğiniz zaman kapatmak için bu yöntemi kullanabilirsiniz:

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

Klavyeyi bir dokunuşla kapatmak isterseniz, bir dokunma eylemiyle tam ekran beyaz bir görünüm oluşturabilirsiniz ve bu, şunları tetikler 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
.keyWindowartık kullanımdan kaldırıldı. Lorenzo Santini'nin cevabına bakın .
LinusGeffarth

4
Ayrıca, .tapActionadı.onTapGesture
LinusGeffarth

Alternatif bir kontrol aktif hale geldiğinde klavye kapatılabilir mi? stackoverflow.com/questions/58643512/…
Yarm

1
Bunu beyaz arka plan olmadan yapmanın bir yolu var mı, ara parçalar kullanıyorum ve ara parça üzerinde bir dokunma hareketini algılamak için buna ihtiyacım var. Ayrıca beyaz arka plan stratejisi, yukarıda fazladan ekran alanı bulunan yeni iPhone'larda bir sorun yaratıyor. Herhangi bir yardım için minnettarız!
Joseph Astrahan

2
Belki de bunun UIApplicationUIKit'in bir parçası olduğunu fark etmeye değer , bu yüzden birinin buna ihtiyacı var import UIKit.
Alienbash

62

Birçok denemeden sonra (şu anda) herhangi bir kontrolü engellemeyen bir çözüm buldum - buna hareket tanıyıcı ekledim UIWindow.

  1. Klavyeyi yalnızca Dışarıda dokunun (sürüklemeleri kullanmadan) kapatmak istiyorsanız - o zaman sadece UITapGestureRecognizerve yalnızca 3. adımı kopyalamanız yeterlidir :
  2. Herhangi bir dokunuşla çalışan özel bir hareket tanıma sınıfı oluşturun:

    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. In SceneDelegate.swiftiçinde func scene, bir sonraki kodu ekleyin:

    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. UIGestureRecognizerDelegateEşzamanlı dokunuşlara izin vermek için uygulayın .

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

Artık herhangi bir görünümdeki herhangi bir klavye dokunulduğunda veya dışarı sürüklendiğinde kapatılacaktır.

Not: Yalnızca belirli TextFields'ı kapatmak istiyorsanız - TextField geri araması çağrıldığında pencereye hareket tanıyıcı ekleyin ve kaldırın onEditingChanged


3
Bu cevap en üstte olmalı. Görünümde başka kontroller varken diğer yanıtlar başarısız olur.
Imthath

1
@RolandLariotte bu davranışı düzeltmek için yanıtı güncelledi, AnyGestureRecognizer'ın yeni uygulamasına bakın
Mikhail

2
Harika cevap. Kusursuz çalışır. @Mikhail, özellikle bazı metin alanları için jest tanıyıcıyı nasıl kaldıracağınızı bilmekle ilgileniyor (etiketlerle bir otomatik tamamlama oluşturdum, bu yüzden listedeki bir öğeye her dokunduğumda, bu belirli metin alanının odağı kaybetmesini istemiyorum)
Pasta

1
bu çözüm aslında harika, ancak 3 ay gibi bir süredir kullandıktan sonra maalesef doğrudan bu tür bir hacklenmenin neden olduğu bir hata buldum. lütfen, size de aynı
şeylerin

1
Harika cevap! Bunun scenedelegate olmadan iOS 14 ile nasıl uygulanacağını merak ediyorum?
Dom

27

@ RyanTCB'nin cevabı iyidir; Aşağıda, olası bir çökmeyi önlemeyi ve kullanımını kolaylaştıran birkaç iyileştirme verilmiştir:

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

'Hata düzeltmesi' basitçe keyWindow!.endEditing(true)gerektiği gibi olması gerektiğidir keyWindow?.endEditing(true)(evet, bunun olmayacağını iddia edebilirsiniz.)

Daha ilginç olanı, onu nasıl kullanabileceğinizdir. Örneğin, içinde birden çok düzenlenebilir alan bulunan bir formunuz olduğunu varsayalım. Sadece şu şekilde sarın:

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

Şimdi, kendisi bir klavye sunmayan herhangi bir kontrole dokunmak, uygun şekilde kapatmayı sağlayacaktır.

(Beta 7 ile test edildi)


6
Hmmm - diğer kontrollere dokunmak artık kayıt olmuyor. Olay yutulur.
Yarm

Bunu çoğaltamıyorum - 11/1 itibarıyla Apple'ın en son damlalarını kullanarak benim için hala çalışıyor. İşe yaradı mı ve sonra senin için çalışmayı bıraktı mı?
Feldur

Formda bir DatePicker'ınız varsa, DatePicker artık gösterilmeyecektir
Albert

@Albert - bu doğru; bu yaklaşımı kullanmak için, öğelerin DismissingKeyboard () ile süslendiği yerleri, DatePicker'ı atlaması gereken ve bundan kaçınan öğeler için geçerli olan daha ince bir seviyeye ayırmanız gerekir.
Feldur

Bu kodun kullanılması uyarıyı yeniden oluşturacaktırCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314

25

Bir NavigationView içinde bir TextField kullanırken bunu yaşadım. Bunun için benim çözümüm bu. Kaydırmaya başladığınızda klavyeyi kapatır.

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
Bu, onDelete'i (silmek için kaydırın) garip davranışa yönlendirir.
Tarek Hallak

Bu güzel, peki ya musluk?
DanielZanchi

21

Özelliğe erişmeyi gerektirmeyen klavyeyi kapatmanın başka bir yolunu buldum keyWindow; işin doğrusu, derleyici şunu kullanarak bir uyarı verir:

UIApplication.shared.keyWindow?.endEditing(true)

iOS 13.0'da 'keyWindow' kullanımdan kaldırıldı: Tüm bağlı sahnelerde bir anahtar pencere döndürdüğü için birden çok sahneyi destekleyen uygulamalar için kullanılmamalıdır

Bunun yerine şu kodu kullandım:

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

21

SwiftUI 2

İşte SwiftUI 2 / iOS 14 için güncellenmiş bir çözüm (ilk olarak burada Mikhail tarafından önerilmiştir ).

Bu kullanmaz AppDelegatene de SceneDelegatesen SwiftUI yaşam döngüsünü kullanırsanız eksik olan:

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

Uzun Basma hareketleri dışında eşzamanlı hareketlerin nasıl tespit edileceğine dair bir örnek:

extension UIApplication: UIGestureRecognizerDelegate {
    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return !otherGestureRecognizer.isKind(of: UILongPressGestureRecognizer.self)
    }
}

2
Bu en üstte olmalı çünkü yeni SwiftUI yaşam döngüsünü akılda tutuyor.
carlosobedgomez

Bu harika çalışıyor. Ancak, bir metin alanına iki kez dokunursam, metni seçmek yerine artık klavye kayboluyor. Seçim için çift dokunmaya nasıl izin verebileceğime dair bir fikriniz var mı?
Gary

@Gary Diğer hareketler sırasında dokunmayı algılamak istemiyorsanız , alt uzantıda yorumun yanlış olarak ayarlandığı satırı görebilirsiniz . Sadece olarak ayarlayın return false.
pawello2222

Bunu yanlış olarak ayarlamak işe yarıyor, ancak bir kişi metin alanının dışına uzun süre basarsa veya sürüklerse veya kaydırırsa klavye de kapatmaz. Bunu yalnızca çift tıklamalar için yanlış olarak ayarlamanın bir yolu var mı (tercihen metin alanı içinde çift tıklama, ancak yalnızca tüm çift tıklamalar bile işe yarar).
Gary

1
Kendi sorumu cevaplamak için, onu tekrar doğru olarak ayarladım ve ardından Mikhail'in tapGesture = UITapGestureRecognizer (...) yerine kendi cevabında oluşturduğu tapGesture = AnyGestureRecognizer (...) ayarını yaptım. Bu, çift dokunuşların metin alanı içindeki metni seçmesine izin verirken aynı zamanda çeşitli hareketlerin klavyeyi metin alanının dışında gizlemesine izin verir.
Gary

15

'SceneDelegate.swift' dosyasındaki SwiftUI şunu ekleyin: .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()
        }
    }

bu, uygulamanızda klavyeyi kullanan her Görünüm için yeterlidir ...


4
Bu başka bir sorun yaratıyor - Metin alanının yanında {} Formunda bir seçicim var, yanıt vermiyor. Bu konudaki tüm cevapları kullanarak bir çözüm bulamadım. Ancak cevabınız, klavyeyi başka bir yerde bir dokunuşla kaldırmak için iyidir - seçiciler kullanmıyorsanız.
Nalov

Merhaba. kodum `` `var body: bazı Görünüm {NavigationView {Form {Section {TextField (" typesomething ", text: $ c)} Section {Picker (" name ", selection: $ sel) {ForEach (0 .. <200 ) {Metin ("(self.array [$ 0])%")}}} `` Başka bir yere dokunulduğunda klavye kapatıldı, ancak seçici yanıt vermedi. Çalıştırmanın bir yolunu bulamadım.
Nalov

2
Tekrar merhaba, şu anda iki çözümüm var: birincisi - dönüş düğmesinde kaldırılan yerel klavyeyi kullanmak, ikincisi - hafifçe dokunma işlemini biraz değiştirmektir (aka 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (sayı: 2, gerçekleştirin: {window.endEditing (true)})) Umarım bu size yardımcı olur ...
Dim Novo

Merhaba. Teşekkür ederim. İkinci yol bunu çözdü. Kullanıcıların yalnızca sayı girebilmesi için sayısal tuş takımı kullanıyorum, dönüş tuşu yok. Aradığım şey dokunarak işten çıkmaktı.
Nalov

bu listenin gezilememesine neden olur.
Cui Mingda

11

Çözümüm, kullanıcılar dışarıya dokunduğunda yazılım klavyesini nasıl gizleyeceğimi. View kapsayıcısının tamamını algılamak için contentShapeile kullanmanız gerekir onLongPressGesture. onTapGestureodaklanmayı engellemekten kaçınmak için gereklidir TextField. Bunun onTapGestureyerine kullanabilirsiniz , onLongPressGestureancak NavigationBar öğeleri çalışmaz.

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

Bu harika çalıştı, biraz farklı kullandım ve ana iş parçacığında çağrıldığından emin olmalıydım.
keegan3d

7

bu değiştiriciyi, kullanıcı dokunmalarını algılamak istediğiniz görünüme ekleyin

.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

Bir .onLongPressGesture(minimumDuration: 0)başkası TextViewetkinleştirildiğinde klavyenin yanıp sönmesine neden olmayan (yan etkisi .onTapGesture) seçeneğini kullanmayı tercih ediyorum . Gizleme klavye kodu yeniden kullanılabilir bir işlev olabilir.

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

Bu yöntemi kullanırken hala titreme var.
Daniel Ryan

Bu harika çalıştı, biraz farklı kullandım ve ana iş parçacığında çağrıldığından emin olmalıydım.
keegan3d

6

Çünkü keyWindowkullanımdan kaldırıldı.

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

1
forceParametre kullanılmaz. O olmalı{ $0.endEditing(force)}
Davide

5

Görünüşe göre endEditingçözüm, @rraphael'in işaret ettiği gibi tek çözüm.
Şimdiye kadar gördüğüm en temiz örnek şudur:

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

ve sonra bunu kullanarak onCommit:


2
.keyWindowartık kullanımdan kaldırıldı. Lorenzo Santini'nin cevabına bakın .
LinusGeffarth

İOS

4

Cevabı @Feldur (@ RyanTCB'ye dayanıyordu) ile genişleterek, burada klavyeyi diğer hareketlerde kapatmanıza izin veren onTapGesture, işlev çağrısında hangisini istediğinizi belirtebileceğinizden daha etkileyici ve güçlü bir çözüm var .

Kullanım

// MARK: - View
extension RestoreAccountInputMnemonicScreen: View {
    var body: some View {
        List(viewModel.inputWords) { inputMnemonicWord in
            InputMnemonicCell(mnemonicInput: inputMnemonicWord)
        }
        .dismissKeyboard(on: [.tap, .drag])
    }
}

Veya kullanarak All.gestures( Gestures.allCases🍬 için sadece şeker )

.dismissKeyboard(on: All.gestures)

Kod

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

Dikkatli söz

Lütfen, tüm hareketleri kullanırsanız , çatışabileceklerini ve bunu çözmek için düzgün bir çözüm bulmadığımı unutmayın.


ne demek eraseToAny()
Ravindra_Bhati

2

Bu yöntem klavyeyi ara parçalar üzerinde gizlemenizi sağlar !

Önce bu işlevi ekleyin (Kredi Verilen: Casper Zandbergen, SwiftUI'den HStack'in Spacer'ına dokunamaz )

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

Ardından aşağıdaki 2 işlevi ekleyin (Kredi Verilen: rraphael, bu sorudan)

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

Aşağıdaki işlev, View sınıfınıza eklenecektir, daha fazla ayrıntı için rraphael'deki en üstteki yanıta başvurmanız yeterlidir.

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

Son olarak, artık basitçe arayabilirsiniz ...

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

Bu, herhangi bir boşluk alanını şimdi klavyeyi kapatacaktır. Artık büyük bir beyaz arka plan görüntüsüne gerek yok!

Bu tekniği varsayımsal olarak extension, şu anda bunu yapmayan TapGestures'ı desteklemek için ihtiyaç duyduğunuz herhangi bir kontrole uygulayabilir ve istediğiniz herhangi bir durumda klavyeyi kapatmak için onTapGestureişlevi ile birlikte çağırabilirsiniz self.endEditing().


Şimdi sorum, klavyeyi bu şekilde uzaklaştırdığınızda bir metin alanında bir işlemi nasıl tetiklersiniz? şu anda 'commit' yalnızca iOS klavyesindeki return tuşuna basarsanız tetiklenir.
Joseph Astrahan


2

@ Sajjon'un cevabına dayanarak, burada, seçtiğiniz klavyeye dokunma, uzun basma, sürükleme, büyütme ve döndürme hareketlerini kapatmanıza olanak tanıyan bir çözüm var.

Bu çözüm XCode 11.4'te çalışıyor

@IMHiteshSurani tarafından sorulan davranışı elde etmek için kullanım

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

Kod

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 ile etkileşimden tamamen kaçınabilir ve onu saf SwiftUI'de uygulayabilirsiniz . Bir .id(<your id>)değiştirici eklemeniz yeterlidir.TextFieldKlavyeyi kapatmak istediğinizde (kaydırma, görüntüleme dokunma, düğme eylemi, ..) ve değerini değiştirin.

Örnek uygulama:

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

Bunu yalnızca en son Xcode 12 beta sürümünde test ettiğimi, ancak herhangi bir sorun olmadan eski sürümlerle (hatta Xcode 11) çalışması gerektiğini unutmayın.


1

Klavyenin ReturnAnahtarı

TextField'ın dışına dokunmayla ilgili tüm yanıtlara ek olarak, kullanıcı klavyedeki dönüş tuşuna dokunduğunda klavyeyi kapatmak isteyebilirsiniz:

bu genel işlevi tanımlayın:

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

Ve onCommitargümanına kullanım ekleyin :

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

Faydaları

  • Her yerden arayabilirsin
  • UIKit veya SwiftUI'ye bağlı değildir (mac uygulamalarında kullanılabilir)
  • İOS 13'te bile çalışır

Demo

demo


0

Şimdiye kadar yukarıdaki seçenekler benim için işe yaramadı, çünkü Form ve iç düğmeler, bağlantılar, seçici ...

Yukarıdaki örneklerin yardımıyla çalışan aşağıdaki kodu oluşturuyorum.

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

Kullanım:

 var body: some View {
        NavigationView {
            Form {
                picker
                button
                textfield
                text
            }
            .dismissingKeyboard()

-2

Xcode 12 ve iOS 14 ile Haziran / 2020'de piyasaya sürülen SwiftUI, hideKeyboardOnTap () değiştiricisini ekler. Bu, 2. vaka numaranızı çözmelidir. 1 numaralı vakanızın çözümü, Xcode 12 ve iOS 14 ile ücretsiz olarak gelir: TextField için varsayılan klavye, Geri Dön düğmesine basıldığında otomatik olarak gizlenir.


1
İOS14'te hideKeyboardOnTap değiştiricisi yok
Teo Sartori

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.