วิธีซ่อนแป้นพิมพ์เมื่อใช้ SwiftUI


96

จะซ่อนkeyboardโดยใช้SwiftUIเคสด้านล่างได้อย่างไร?

กรณีที่ 1

ฉันมีTextFieldและต้องการซ่อนkeyboardเมื่อผู้ใช้คลิกreturnปุ่ม

กรณีที่ 2

ฉันมีTextFieldและจำเป็นต้องซ่อนkeyboardเมื่อผู้ใช้แตะข้างนอก

ฉันจะทำสิ่งนี้ได้SwiftUIอย่างไร?

บันทึก:

UITextFieldฉันไม่ได้ถามคำถามเกี่ยวกับ ฉันต้องการที่จะทำโดยใช้SwifUI.TextField.


29
@DannyBuonocore อ่านคำถามของฉันอย่างละเอียดอีกครั้ง!
Hitesh Surani

9
@DannyBuonocore นี่ไม่ใช่คำถามที่กล่าวถึงซ้ำ คำถามนี้เกี่ยวกับ SwiftUI และอื่น ๆ เป็นเรื่องปกติ UIKit
Johnykutty

1
@DannyBuonocore โปรดไปที่developer.apple.com/documentation/swiftuiเพื่อค้นหาความแตกต่างระหว่าง UIKit และ SwiftUI ขอบคุณ
Hitesh Surani

ฉันเพิ่มโซลูชันที่นี่ฉันหวังว่ามันจะช่วยคุณได้
Victor Kushnerov

วิธีแก้ปัญหาส่วนใหญ่ไม่ได้ผลตามที่ต้องการเนื่องจากปิดใช้งานปฏิกิริยาที่ต้องการบนก๊อกควบคุมอื่น ๆ สามารถดูวิธีการทำงานได้ที่นี่: forums.developer.apple.com/thread/127196
Hardy

คำตอบ:


86

คุณบังคับให้ผู้ตอบกลับคนแรกลาออกได้โดยส่งการดำเนินการไปยังแอปพลิเคชันที่แชร์:

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

2
.keyWindowเลิกใช้งานแล้ว ดูคำตอบของ Lorenzo Santini
LinusGeffarth

4
นอกจากนี้.tapActionยังเปลี่ยนชื่อเป็น.onTapGesture
LinusGeffarth

แป้นพิมพ์สามารถปิดได้เมื่อมีการใช้งานการควบคุมอื่นหรือไม่ stackoverflow.com/questions/58643512/…
Yarm

1
มีวิธีการทำเช่นนี้โดยไม่มีพื้นหลังสีขาวหรือไม่ฉันใช้ตัวเว้นวรรคและฉันต้องการให้มันตรวจจับท่าทางการแตะบนตัวเว้นวรรค นอกจากนี้กลยุทธ์พื้นหลังสีขาวยังสร้างปัญหาให้กับ iPhone รุ่นใหม่ที่ตอนนี้มีพื้นที่หน้าจอเพิ่มเติม ความช่วยเหลือใด ๆ ที่ชื่นชม!
Joseph Astrahan

2
บางทีมันอาจจะยังมีมูลค่าการสังเกตเห็นว่าUIApplicationเป็นส่วนหนึ่งของ UIKit import UIKitดังนั้นหนึ่งความต้องการ
Alienbash

63

หลังจากที่จำนวนมากของความพยายามที่ผมพบวิธีแก้ปัญหาที่ (ปัจจุบัน) จะไม่ปิดกั้นการควบคุมใด ๆ - UIWindowเพิ่มจดจำท่าทาง

  1. หากคุณต้องการปิดแป้นพิมพ์เฉพาะใน Tap outside (โดยไม่ต้องจัดการการลาก) - ก็เพียงพอที่จะใช้เพียง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
        }
    }
    

ตอนนี้แป้นพิมพ์ใด ๆ ในทุกมุมมองจะปิดเมื่อสัมผัสหรือลากออกไป

ป.ล. หากคุณต้องการปิดเฉพาะ TextFields เฉพาะให้เพิ่มและลบตัวจดจำท่าทางในหน้าต่างเมื่อใดก็ตามที่เรียกกลับของ TextField onEditingChanged


3
คำตอบนี้ควรอยู่ด้านบนสุด คำตอบอื่นล้มเหลวเมื่อมีตัวควบคุมอื่นในมุมมอง
Imthath

1
@RolandLariotte อัปเดตคำตอบเพื่อแก้ไขพฤติกรรมนี้ดูการใช้งาน AnyGestureRecognizer ใหม่
Mikhail

2
คำตอบที่ยอดเยี่ยม ทำงานได้อย่างไม่มีที่ติ @ Mikhail สนใจที่จะรู้ว่าคุณจะลบตัวจดจำท่าทางโดยเฉพาะสำหรับช่องข้อความบางช่องได้อย่างไร (ฉันสร้างการเติมข้อความอัตโนมัติด้วยแท็กดังนั้นทุกครั้งที่ฉันแตะองค์ประกอบในรายการฉันไม่ต้องการให้ช่องข้อความเฉพาะนี้สูญเสียโฟกัส)
Pasta

1
โซลูชันนี้ยอดเยี่ยมจริง ๆ แต่หลังจากใช้งานมา 3 เดือนแล้วน่าเสียดายที่ฉันพบข้อผิดพลาดซึ่งเกิดจากการแฮ็กประเภทนี้โดยตรง โปรดระวังสิ่งเดียวกันที่เกิดขึ้นกับคุณ
glassomoss

1
คำตอบสุดวิเศษ! ฉันสงสัยว่าสิ่งนี้จะนำไปใช้กับ iOS 14 โดยไม่มีผู้ร่วมประชุมได้อย่างไร?
Dom

27

คำตอบของ @ 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
อืม - การแตะที่การควบคุมอื่น ๆ จะไม่ลงทะเบียนอีกต่อไป เหตุการณ์จะกลืนกิน
Yarm

ฉันไม่สามารถทำซ้ำได้ - มันยังใช้ได้สำหรับฉันโดยใช้หยดล่าสุดจาก Apple เมื่อวันที่ 11/1 มันได้ผลแล้วก็หยุดทำงานให้คุณหรือ ??
Feldur

หากคุณมี DatePicker ในรูปแบบ DatePicker จะไม่แสดงอีกต่อไป
Albert

@ อัลเบิร์ต - นั่นคือเรื่องจริง ในการใช้แนวทางนี้คุณจะต้องแยกย่อยส่วนที่ตกแต่งด้วย DismissingKeyboard () ให้เป็นระดับที่ละเอียดกว่าซึ่งใช้กับองค์ประกอบที่ควรปิดและหลีกเลี่ยง DatePicker
Feldur

การใช้รหัสนี้จะทำให้คำเตือนเกิดซ้ำCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314

25

ฉันพบสิ่งนี้ขณะใช้ 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)})

1
สิ่งนี้จะทำให้ onDelete (ปัดเพื่อลบ) ไปสู่พฤติกรรมแปลก ๆ
Tarek Hallak

นี่เป็นสิ่งที่ดี แต่แล้วการแตะล่ะ?
DanielZanchi

22

SwiftUI 2

นี่คือโซลูชันที่อัปเดตสำหรับSwiftUI 2 / iOS 14 (เดิมเสนอโดย Mikhail ที่นี่ )

มันไม่ได้ใช้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
สิ่งนี้ควรอยู่ด้านบนเนื่องจากคำนึงถึงวงจรชีวิตของ SwiftUI ใหม่
carlosobedgomez

ใช้งานได้ดี อย่างไรก็ตามหากฉันแตะสองครั้งในช่องข้อความแทนที่จะเลือกข้อความแป้นพิมพ์จะหายไป ฉันจะอนุญาตให้แตะสองครั้งเพื่อเลือกได้อย่างไร
Gary

@Gary ในส่วนขยายด้านล่างคุณสามารถดูบรรทัดที่มีความคิดเห็นตั้งค่าเป็นเท็จถ้าคุณไม่ต้องการที่จะตรวจสอบในระหว่างการประปาท่าทางอื่นreturn falseเพียงแค่ตั้งค่าให้
pawello2222

การตั้งค่าเป็นเท็จใช้งานได้ แต่แป้นพิมพ์จะไม่ปิดหากมีคนกดหรือลากหรือเลื่อนออกนอกพื้นที่ข้อความเป็นเวลานาน มีวิธีใดบ้างในการตั้งค่าเป็นเท็จเฉพาะสำหรับการคลิกสองครั้ง (โดยเฉพาะอย่างยิ่งการคลิกสองครั้งในช่องข้อความ แต่จะทำได้เพียงแค่คลิกสองครั้งทั้งหมด)
Gary

2
เพื่อตอบคำถามของตัวเองฉันตั้งค่ากลับเป็นจริงจากนั้นตั้งค่า tapGesture = AnyGestureRecognizer (... ) ที่ Mikhail สร้างในคำตอบของเขาแทนที่จะเป็น tapGesture = UITapGestureRecognizer (... ) ซึ่งช่วยให้สามารถแตะสองครั้งเพื่อเลือกข้อความภายในช่องข้อความในขณะเดียวกันก็อนุญาตให้ใช้ท่าทางต่างๆเพื่อซ่อนแป้นพิมพ์นอกช่องข้อความ
Gary

21

ฉันพบวิธีอื่นในการปิดแป้นพิมพ์ที่ไม่ต้องการเข้าถึง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
สิ่งนี้ทำให้เกิดปัญหาอีกอย่างหนึ่ง - ฉันมีตัวเลือกในแบบฟอร์ม {} ข้างฟิลด์ข้อความมันไม่ตอบสนอง ฉันไม่พบวิธีแก้ปัญหาโดยใช้คำตอบทั้งหมดในหัวข้อนี้ แต่คำตอบของคุณดีสำหรับการปิดแป้นพิมพ์ด้วยการแตะที่อื่น - หากคุณไม่ใช้ตัวเลือก
Nalov

สวัสดี. รหัสของฉัน `` var body: some View {NavigationView {Form {Section {TextField ("typesomething", text: $ c)} Section {Picker ("name", selection: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}} `` แป้นพิมพ์ถูกปิดเมื่อแตะที่อื่น แต่ตัวเลือกไม่ตอบสนอง ฉันไม่พบวิธีที่จะทำให้มันใช้งานได้
Nalov

2
สวัสดีอีกครั้งในขณะนี้ฉันมีสองวิธีแก้ปัญหา: อย่างแรก - คือการใช้แป้นพิมพ์เนทีฟที่ปิดบนปุ่มย้อนกลับประการที่สอง - คือการเปลี่ยนการจัดการการแตะเล็กน้อย (aka 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (จำนวน: 2, ดำเนินการ: {window.endEditing (true)})) หวังว่านี่จะช่วยคุณได้ ...
Dim Novo

สวัสดี. ขอขอบคุณ. วิธีที่สองแก้ไขได้ ฉันใช้แป้นตัวเลขดังนั้นผู้ใช้จึงสามารถป้อนตัวเลขได้เท่านั้น แต่ไม่มีปุ่มย้อนกลับ การปิดการแตะคือสิ่งที่ฉันกำลังค้นหา
Nalov

สิ่งนี้จะทำให้รายการไม่สามารถนำทางได้
Cui Mingda

11

วิธีแก้ปัญหาของฉันวิธีซ่อนแป้นพิมพ์ซอฟต์แวร์เมื่อผู้ใช้แตะด้านนอก คุณจำเป็นต้องใช้contentShapeกับonLongPressGestureการตรวจสอบตู้คอนเทนเนอร์ดูทั้งหมด จำเป็นที่จะหลีกเลี่ยงการปิดกั้นการมุ่งเน้นไปที่onTapGesture TextFieldคุณสามารถใช้onTapGestureแทนได้onLongPressGestureแต่รายการ NavigationBar จะไม่ทำงาน

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

struct KeyboardAvoiderDemo: View {
    @State var text = ""
    var body: some View {
        VStack {
            TextField("Demo", text: self.$text)
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .contentShape(Rectangle())
        .onTapGesture {}
        .onLongPressGesture(
            pressing: { isPressed in if isPressed { self.endEditing() } },
            perform: {})
    }
}

มันใช้งานได้ดีฉันใช้มันแตกต่างกันเล็กน้อยและต้องแน่ใจว่ามันถูกเรียกในเธรดหลัก
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)
}

ยังคงสั่นไหวโดยใช้วิธีนี้
Daniel Ryan

มันใช้งานได้ดีฉันใช้มันแตกต่างกันเล็กน้อยและต้องแน่ใจว่ามันถูกเรียกในเธรดหลัก
keegan3d

6

เนื่องจากkeyWindowเลิกใช้งานแล้ว

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

1
forceพารามิเตอร์ที่ไม่ได้ใช้ มันควรจะเป็น{ $0.endEditing(force)}
Davide

5

ดูเหมือนendEditingวิธีแก้ปัญหาจะมีเพียงวิธีเดียวเช่นที่ @rraphael ชี้ให้เห็น
ตัวอย่างที่สะอาดที่สุดที่ฉันเคยเห็นมาคือ:

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

แล้วใช้ในไฟล์ onCommit:


2
.keyWindowเลิกใช้งานแล้ว ดูคำตอบของ Lorenzo Santini
LinusGeffarth

คิดค่าเสื่อมราคาใน iOS 13+
Ahmadreza

4

ขยายคำตอบโดย @Feldur (ซึ่งอ้างอิงจาก @ RyanTCB's) นี่คือโซลูชันที่แสดงออกและมีประสิทธิภาพยิ่งขึ้นซึ่งช่วยให้คุณสามารถปิดแป้นพิมพ์บนท่าทางสัมผัสอื่น ๆ นอกเหนือจากที่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

วิธีนี้ช่วยให้คุณซ่อนแป้นพิมพ์บนตัวเว้นวรรค!

ก่อนอื่นให้เพิ่มฟังก์ชั่นนี้ (เครดิตมอบให้: Casper Zandbergen จากSwiftUI ไม่สามารถแตะใน Spacer of HStack )

extension Spacer {
    public func onTapGesture(count: Int = 1, perform action: @escaping () -> Void) -> some View {
        ZStack {
            Color.black.opacity(0.001).onTapGesture(count: count, perform: action)
            self
        }
    }
}

จากนั้นเพิ่ม 2 ฟังก์ชั่นต่อไปนี้ (เครดิตมอบให้: rraphael จากคำถามนี้)

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

ฟังก์ชันด้านล่างนี้จะถูกเพิ่มลงในคลาส View ของคุณเพียงอ้างอิงคำตอบด้านบนจาก rraphael สำหรับรายละเอียดเพิ่มเติม

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

ในที่สุดคุณก็สามารถโทร ...

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

สิ่งนี้จะทำให้พื้นที่เว้นวรรคปิดแป้นพิมพ์ทันที ไม่จำเป็นต้องมีพื้นหลังสีขาวขนาดใหญ่อีกต่อไป!

คุณสามารถใช้เทคนิคนี้โดยสมมุติฐานในการextensionควบคุมใด ๆ ที่คุณต้องการเพื่อรองรับ TapGestures ที่ยังไม่ทำเช่นนั้นและเรียกใช้onTapGestureฟังก์ชันนี้ร่วมกับself.endEditing()เพื่อปิดแป้นพิมพ์ในทุกสถานการณ์ที่คุณต้องการ


คำถามของฉันตอนนี้คือคุณจะกระตุ้นการกระทำบนช่องข้อความได้อย่างไรเมื่อคุณทำให้แป้นพิมพ์หายไปด้วยวิธีนี้ ปัจจุบัน 'กระทำ' จะทริกเกอร์เฉพาะเมื่อคุณกดปุ่มย้อนกลับบนแป้นพิมพ์ iOS
Joseph Astrahan

2

โปรดตรวจสอบhttps://github.com/michaelhenry/KeyboardAvoider

เพียงรวมKeyboardAvoider {}ไว้ที่ด้านบนของมุมมองหลักของคุณเท่านี้ก็คือทั้งหมด

KeyboardAvoider {
    VStack { 
        TextField()
        TextField()
        TextField()
        TextField()
    }

}

สิ่งนี้ใช้ไม่ได้กับมุมมองฟอร์มที่มี Textfields แบบฟอร์มไม่ปรากฏขึ้น
Nils

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) โดยไม่มีปัญหาใด ๆ


1

แป้นพิมพ์Returnคีย์

นอกเหนือจากคำตอบทั้งหมดเกี่ยวกับการแตะภายนอก textField แล้วคุณอาจต้องการปิดแป้นพิมพ์เมื่อผู้ใช้แตะปุ่มย้อนกลับบนแป้นพิมพ์:

กำหนดฟังก์ชันส่วนกลางนี้:

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

และเพิ่มการใช้ในการonCommitโต้แย้ง:

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

สิทธิประโยชน์

  • คุณสามารถโทรได้จากทุกที่
  • ไม่ขึ้นอยู่กับ UIKit หรือ SwiftUI (สามารถใช้ในแอพ mac)
  • ใช้งานได้แม้ใน iOS 13

การสาธิต

การสาธิต


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 จะซ่อนโดยอัตโนมัติเมื่อกดปุ่ม Return


1
ไม่มีตัวแก้ไข hideKeyboardOnTap ใน iOS14
Teo Sartori

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.