Làm thế nào để ẩn bàn phím khi sử dụng SwiftUI?


88

Làm thế nào để ẩn keyboardbằng cách sử dụng SwiftUIcho các trường hợp dưới đây?

Trường hợp 1

Tôi có TextFieldvà tôi cần ẩn keyboardkhi người dùng nhấp vào returnnút.

Trường hợp 2

Tôi có TextFieldvà tôi cần ẩn thời keyboardđiểm người dùng nhấn bên ngoài.

Làm thế nào tôi có thể làm điều này bằng cách sử dụng SwiftUI?

Ghi chú:

Tôi đã không hỏi một câu hỏi liên quan đến UITextField. Tôi muốn làm điều đó bằng cách sử dụng SwifUI.TextField.


29
@DannyBuonocore Hãy đọc kỹ lại câu hỏi của tôi!
Hitesh Surani

9
@DannyBuonocore Đây không phải là bản sao của câu hỏi đã đề cập. Câu hỏi này là về SwiftUI, và khác là UIKit bình thường
Johnykutty

1
@DannyBuonocore vui lòng xem developer.apple.com/documentation/swiftui để tìm sự khác biệt giữa UIKit và SwiftUI. Cảm ơn
Hitesh Surani

Tôi đã thêm giải pháp của mình ở đây, tôi hy vọng nó sẽ giúp bạn.
Victor Kushnerov

Hầu hết các giải pháp ở đây không hoạt động như mong muốn, vì chúng vô hiệu hóa các phản ứng mong muốn trên các vòi điều khiển khác. Giải pháp hoạt động có thể được tìm thấy tại đây: forum.developer.apple.com/thread/127196
Hardy

Câu trả lời:


79

Bạn có thể buộc người phản hồi đầu tiên từ chức bằng cách gửi một hành động đến ứng dụng được chia sẻ:

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

Bây giờ bạn có thể sử dụng phương pháp này để đóng bàn phím bất cứ khi nào bạn muốn:

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

Nếu bạn muốn đóng bàn phím bằng một lần chạm, bạn có thể tạo chế độ xem toàn màn hình màu trắng bằng thao tác chạm, thao tác này sẽ kích hoạt endEditing(_:):

struct Background<Content: View>: View {
    private var content: Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content()
    }

    var body: some View {
        Color.white
        .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
        .overlay(content)
    }
}

struct ContentView : View {
    @State private var name: String = ""

    var body: some View {
        Background {
            VStack {
                Text("Hello \(self.name)")
                TextField("Name...", text: self.$name) {
                    self.endEditing()
                }
            }
        }.onTapGesture {
            self.endEditing()
        }
    }

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

1
.keyWindowhiện không được dùng nữa. Hãy xem câu trả lời của Lorenzo Santini .
LinusGeffarth

3
Ngoài ra, .tapActionđã được đổi tên.onTapGesture
LinusGeffarth

Bàn phím có thể bị loại bỏ khi một điều khiển thay thế hoạt động không? stackoverflow.com/questions/58643512/…
Yarm

1
Có cách nào để thực hiện việc này mà không có nền trắng không, tôi đang sử dụng bộ đệm và tôi cần nó để phát hiện cử chỉ chạm trên miếng đệm. Ngoài ra, chiến lược nền trắng cũng tạo ra một vấn đề trên iPhone mới hơn khi có thêm không gian màn hình phía trên. Bất kỳ sự giúp đỡ nào được đánh giá cao!
Joseph Astrahan

Tôi đã đăng một câu trả lời giúp cải thiện thiết kế của bạn. Vui lòng chỉnh sửa câu trả lời của bạn nếu bạn muốn Tôi không quan tâm đến tín dụng.
Joseph Astrahan

61

Sau nhiều lần thử, tôi đã tìm ra giải pháp (hiện tại) không chặn bất kỳ điều khiển nào - thêm trình nhận dạng cử chỉ vào UIWindow.

  1. Nếu bạn chỉ muốn đóng bàn phím trên Chạm bên ngoài (không xử lý kéo) - thì chỉ cần sử dụng UITapGestureRecognizervà chỉ cần sao chép bước 3 là đủ:
  2. Tạo lớp nhận dạng cử chỉ tùy chỉnh hoạt động với bất kỳ thao tác chạm nào:

    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. Trong SceneDelegate.swifttrong func scene, thêm mã tiếp theo:

    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. Triển khai UIGestureRecognizerDelegateđể cho phép chạm đồng thời.

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

Giờ đây, mọi bàn phím trên bất kỳ chế độ xem nào sẽ bị đóng khi chạm hoặc kéo ra bên ngoài.

Tái bút Nếu bạn chỉ muốn đóng các Trường văn bản cụ thể - thì hãy thêm và xóa trình nhận dạng cử chỉ vào cửa sổ bất cứ khi nào được gọi lại của Trường văn bản onEditingChanged


3
Câu trả lời này nên ở trên cùng. Các câu trả lời khác không thành công khi có các điều khiển khác trong dạng xem.
Imthath

1
@RolandLariotte đã cập nhật câu trả lời để khắc phục hiện tượng này, hãy xem triển khai mới của AnyGestureRecognizer
Mikhail

1
Câu trả lời tuyệt vời. Hoạt động hoàn hảo. @Mikhail thực sự muốn biết làm cách nào để xóa trình nhận dạng cử chỉ cụ thể cho một số trường văn bản (Tôi đã tạo tự động hoàn thành với các thẻ, vì vậy mỗi khi tôi nhấn vào một phần tử trong danh sách, tôi không muốn trường văn bản cụ thể này mất tiêu điểm)
Pasta

1
giải pháp này thực sự tuyệt vời, nhưng sau khi sử dụng nó trong 3 tháng, thật không may, tôi đã tìm thấy một lỗi, trực tiếp gây ra bởi loại hack này. làm ơn, lưu ý điều tương tự đang xảy ra với bạn
glassomoss

1
Câu trả lời tuyệt vời! Tôi tự hỏi điều này sẽ được thực hiện như thế nào với iOS 14 mà không có cổng quyền?
Dom

28

@ RyanTCB của câu trả lời là tốt; đây là một số cải tiến giúp việc sử dụng đơn giản hơn và tránh sự cố tiềm ẩn:

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

Việc 'sửa lỗi' chỉ đơn giản là keyWindow!.endEditing(true)đúng cách keyWindow?.endEditing(true)(vâng, bạn có thể cho rằng nó không thể xảy ra.)

Thú vị hơn là cách bạn có thể sử dụng nó. Ví dụ: giả sử bạn có một biểu mẫu có nhiều trường có thể chỉnh sửa trong đó. Chỉ cần quấn nó như thế này:

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

Bây giờ, nhấn vào bất kỳ điều khiển nào mà bản thân nó không hiển thị bàn phím sẽ thực hiện loại bỏ thích hợp.

(Đã thử nghiệm với bản beta 7)


6
Hmmm - việc nhấn vào các điều khiển khác không còn đăng ký nữa. Sự kiện bị nuốt chửng.
Yarm,

Tôi không thể lặp lại điều đó - nó vẫn hoạt động với tôi bằng cách sử dụng các giọt mới nhất từ ​​Apple kể từ ngày 11/1. Nó đã hoạt động và sau đó chỉ ngừng hoạt động cho bạn, hoặc ??
Feldur

Nếu bạn có DatePicker trong biểu mẫu, thì DatePicker sẽ không được hiển thị nữa
Albert

@Albert - đó là sự thật; để sử dụng phương pháp này, bạn sẽ phải chia nhỏ vị trí các mục được trang trí bằng DismissingKeyboard () thành cấp độ chi tiết hơn áp dụng cho các phần tử nên loại bỏ và tránh DatePicker.
Feldur

Việc sử dụng mã này sẽ tạo lại cảnh báoCan't find keyplane that supports type 4 for keyboard iPhone-PortraitChoco-NumberPad; using 25686_PortraitChoco_iPhone-Simple-Pad_Default
np2314,

23

Tôi đã gặp phải trường hợp này khi sử dụng TextField bên trong NavigationView. Đây là giải pháp của tôi cho điều đó. Nó sẽ loại bỏ bàn phím khi bạn bắt đầu cuộn.

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

Điều này sẽ dẫn đến onDelete (vuốt để xóa) hành vi lạ.
Tarek Hallak

Điều này thật hay, nhưng còn vòi thì sao?
Danny182

20

Tôi đã tìm thấy một cách khác để loại bỏ bàn phím không yêu cầu truy cập thuộc keyWindowtính; thực tế là trình biên dịch đưa ra cảnh báo bằng cách sử dụng

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow' không được dùng nữa trong iOS 13.0: Không nên sử dụng cho các ứng dụng hỗ trợ nhiều cảnh vì nó trả về một cửa sổ chính trên tất cả các cảnh được kết nối

Thay vào đó, tôi sử dụng mã này:

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

15

SwiftUI trong tệp 'SceneDelegate.swift' chỉ cần thêm: .onTapGesture {window.endE Chỉnh sửa (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()
        }
    }

điều này là đủ cho mỗi Chế độ xem bằng bàn phím trong ứng dụng của bạn ...


4
Điều này gây ra một vấn đề khác - tôi có một bộ chọn trong Biểu mẫu {} cùng với trường văn bản, nó không phản hồi. Tôi đã không tìm thấy giải pháp cho nó bằng cách sử dụng tất cả các câu trả lời trong chủ đề này. Nhưng câu trả lời của bạn rất phù hợp để loại bỏ bàn phím bằng một lần nhấn ở nơi khác - nếu bạn không sử dụng bộ chọn.
Nalov

xin chào. mã của tôi `` `var body: some View {NavigationView {Form {Section {TextField (" stylesomething ", text: $ c)} Section {Picker (" name ", selection: $ sel) {ForEach (0 .. <200 ) {Text ("(self.array [$ 0])%")}}} `` Bàn phím bị loại bỏ khi chạm vào nơi khác, nhưng bộ chọn không phản hồi. Tôi đã không tìm ra cách để làm cho nó hoạt động.
Nalov

2
Xin chào một lần nữa, hiện tại tôi có hai giải pháp: thứ nhất - là sử dụng bàn phím gốc bị loại bỏ trên nút quay lại, thứ hai - là thay đổi một chút xử lý khai thác (hay còn gọi là 'костыль') - window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (count: 2, thực hiện: {window.endE Chỉnh sửa (true)})) Hy vọng điều này sẽ giúp bạn ...
Dim Novo

Xin chào. Cảm ơn bạn. Cách thứ hai đã giải quyết nó. Tôi đang sử dụng bàn phím số, vì vậy người dùng chỉ có thể nhập số, nó không có phím quay lại. Loại bỏ bằng cách khai thác là những gì tôi đang tìm kiếm.
Nalov

điều này sẽ gây ra danh sách không thể được điều hướng.
Cui Mingda

14

SwiftUI 2

Đây là giải pháp cập nhật cho SwiftUI 2 / iOS 14 (ban đầu được đề xuất bởi Mikhail ở đây ).

Nó không sử dụng cái AppDelegatecũng như SceneDelegatecái bị thiếu nếu bạn sử dụng vòng đời 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
    }
}

Dưới đây là một ví dụ về cách phát hiện các cử chỉ đồng thời ngoại trừ cử chỉ Nhấn và giữ:

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

2
Điều này hoạt động hoàn hảo! Cảm ơn vì giải pháp
NotAPhoenix

2
Điều này cần được quan tâm hàng đầu vì hãy ghi nhớ vòng đời SwiftUI mới.
carlosobedgomez

Điều này hoạt động tuyệt vời. Tuy nhiên, nếu tôi nhấn đúp vào trường văn bản, thay vì chọn văn bản, bàn phím giờ sẽ biến mất. Bất kỳ ý tưởng nào về cách tôi có thể cho phép nhấn đúp để lựa chọn?
Gary

@Gary Trong tiện ích mở rộng phía dưới, bạn có thể thấy dòng có chú thích được đặt thành sai nếu bạn không muốn phát hiện chạm trong các cử chỉ khác . Chỉ cần đặt nó thành return false.
pawello2222

Đặt nó thành false hoạt động, nhưng sau đó bàn phím cũng không loại bỏ nếu ai đó nhấn và kéo hoặc cuộn bên ngoài vùng văn bản. Có cách nào để đặt nó thành false chỉ cho các lần nhấp đúp (tốt nhất là nhấp đúp bên trong trường văn bản, nhưng thậm chí chỉ tất cả các nhấp đúp sẽ làm được).
Gary

11

Giải pháp của tôi làm thế nào để ẩn bàn phím phần mềm khi người dùng gõ bên ngoài. Bạn cần sử dụng contentShapewith onLongPressGestuređể phát hiện toàn bộ vùng chứa View. onTapGesturebắt buộc để tránh chặn tiêu điểm TextField. Bạn có thể sử dụng onTapGesturethay thế onLongPressGesturenhưng các mục NavigationBar sẽ không hoạt động.

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

Điều này hoạt động tuyệt vời, tôi sử dụng nó hơi khác một chút và phải chắc chắn rằng nó được gọi trên luồng chính.
keegan3d

7

thêm công cụ sửa đổi này vào dạng xem bạn muốn phát hiện các lần nhấn của người dùng

.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

Tôi thích sử dụng .onLongPressGesture(minimumDuration: 0), điều này không làm cho bàn phím nhấp nháy khi một bàn phím khác TextViewđược kích hoạt (tác dụng phụ của .onTapGesture). Mã bàn phím ẩn có thể là một chức năng có thể sử dụng lại.

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

Vẫn nhấp nháy khi sử dụng phương pháp này.
Daniel Ryan

Điều này hoạt động tuyệt vời, tôi sử dụng nó hơi khác một chút và phải chắc chắn rằng nó được gọi trên luồng chính.
keegan3d

6

keyWindowkhông được dùng nữa.

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

1
Các forcetham số không được sử dụng. Nó nên được{ $0.endEditing(force)}
Davide

5

Có vẻ như endEditinggiải pháp là duy nhất như @rraphael đã chỉ ra.
Ví dụ rõ ràng nhất mà tôi đã thấy cho đến nay là:

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

và sau đó sử dụng nó trong onCommit:


2
.keyWindowhiện không được dùng nữa. Hãy xem câu trả lời của Lorenzo Santini .
LinusGeffarth

Nó bị mất giá trên iOS 13+
Ahmadreza

4

Mở rộng câu trả lời của @Feldur (dựa trên @ RyanTCB's), đây là một giải pháp thậm chí còn mạnh mẽ và rõ ràng hơn cho phép bạn loại bỏ bàn phím trên các cử chỉ khác ngoài ra onTapGesture, bạn có thể chỉ định cái bạn muốn trong lệnh gọi hàm.

Sử dụng

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

Hoặc sử dụng All.gestures(chỉ đường cho 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))
    }
}

Lời cảnh báo

Xin lưu ý rằng nếu bạn sử dụng tất cả các cử chỉ, chúng có thể xung đột và tôi đã không đưa ra bất kỳ giải pháp gọn gàng nào để giải quyết vấn đề đó.


nghĩa là gì eraseToAny()
Ravindra_Bhati

2

Phương pháp này cho phép bạn ẩn bàn phím trên các phím cách !

Đầu tiên hãy thêm chức năng này (Tín dụng được cấp cho: Casper Zandbergen, từ SwiftUI không thể khai thác trong Spacer của 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
        }
    }
}

Tiếp theo, thêm 2 chức năng sau (Credit Given To: rraphael, từ câu hỏi này)

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

Hàm bên dưới sẽ được thêm vào lớp View của bạn, chỉ cần tham khảo câu trả lời hàng đầu tại đây từ rraphael để biết thêm chi tiết.

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

Cuối cùng, bây giờ bạn có thể chỉ cần gọi ...

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

Điều này sẽ làm cho bất kỳ vùng đệm nào đóng bàn phím ngay bây giờ. Không cần phải xem nền trắng lớn nữa!

Theo giả thuyết, bạn có thể áp dụng kỹ thuật này extensioncho bất kỳ điều khiển nào bạn cần để hỗ trợ TapGestures hiện không làm như vậy và gọi onTapGesturehàm kết hợp với self.endEditing()để đóng bàn phím trong bất kỳ tình huống nào bạn muốn.


Câu hỏi của tôi bây giờ là làm thế nào để bạn kích hoạt một cam kết trên một trường văn bản khi bạn nhận được bàn phím theo cách này? hiện 'cam kết' chỉ kích hoạt nếu bạn nhấn phím quay lại trên bàn phím iOS.
Joseph Astrahan

2

Vui lòng kiểm tra https://github.com/michaelhenry/KeyboardAvoider

Chỉ cần bao gồm KeyboardAvoider {}trên đầu chế độ xem chính của bạn và đó là tất cả.

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

}

Điều này không hoạt động đối với dạng xem Biểu mẫu có Trường văn bản. Biểu mẫu không hiển thị.
Nils

2

Dựa trên câu trả lời của @ Sajjon, đây là một giải pháp cho phép bạn loại bỏ các cử chỉ nhấn phím, nhấn và giữ, kéo, phóng đại và xoay theo lựa chọn của bạn.

Giải pháp này đang hoạt động trong XCode 11.4

Cách sử dụng để nhận được hành vi do @IMHiteshSurani yêu cầu

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

Bạn hoàn toàn có thể tránh tương tác với UIKit và triển khai nó trong SwiftUI thuần túy . Chỉ cần thêm một công cụ .id(<your id>)sửa đổi vào của bạn TextFieldvà thay đổi giá trị của nó bất cứ khi nào bạn muốn loại bỏ bàn phím (khi vuốt, xem chạm, thao tác nút, ..).

Thực hiện mẫu:

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

Lưu ý rằng tôi chỉ thử nghiệm nó trong bản beta Xcode 12 mới nhất, nhưng nó sẽ hoạt động với các phiên bản cũ hơn (thậm chí cả Xcode 11) mà không gặp bất kỳ sự cố nào.


0

Bàn phím của Returnchính

Ngoài tất cả các câu trả lời về cách khai thác bên ngoài Trường văn bản, bạn có thể muốn loại bỏ bàn phím khi người dùng nhấn vào phím quay lại trên bàn phím:

xác định chức năng toàn cục này:

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

Và thêm sử dụng trong onCommitđối số nó:

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

Những lợi ích

  • Bạn có thể gọi nó từ mọi nơi
  • Nó không phụ thuộc vào UIKit hoặc SwiftUI (có thể được sử dụng trong các ứng dụng mac)
  • Nó hoạt động ngay cả trong iOS 13

Bản giới thiệu

bản giới thiệu


0

Cho đến nay các tùy chọn trên không hoạt động với tôi, vì tôi có các nút Biểu mẫu và bên trong, liên kết, bộ chọn ...

Tôi tạo mã dưới đây đang hoạt động, với sự trợ giúp từ các ví dụ trên.

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

Sử dụng:

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

-2

SwiftUI được phát hành vào tháng 6/2020 với Xcode 12 và iOS 14 bổ sung thêm công cụ sửa đổi hideKeyboardOnTap (). Điều này sẽ giải quyết trường hợp số 2. Giải pháp cho trường hợp số 1 của bạn được cung cấp miễn phí với Xcode 12 và iOS 14: bàn phím mặc định cho TextField tự động ẩn khi nhấn nút Quay lại.


1
Không có công cụ sửa đổi hideKeyboardOnTap trong iOS14
Teo Sartori

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.