SwiftUI를 사용할 때 키보드를 숨기는 방법은 무엇입니까?


88

아래의 경우를 keyboard사용하여 숨기는 방법 SwiftUI?

사례 1

나는이 TextField와 나는를 숨길 필요가 keyboard사용자가 클릭 할 때 return버튼을 누릅니다.

사례 2

나는이 TextField와 나는를 숨길 필요가 keyboard사용자가 외부 탭 때.

어떻게 이것을 사용하여 할 수 SwiftUI있습니까?

노트 :

에 대해 질문하지 않았습니다 UITextField. 을 사용하여 수행하고 싶습니다 SwifUI.TextField.


29
@DannyBuonocore 다시 조심스럽게 내 질문을 읽으십시오!
Hitesh Surani

9
@DannyBuonocore 이것은 언급 된 질문의 중복이 아닙니다. 이 질문은 SwiftUI에 대해, 그리고 다른 하나는 정상 UIKit입니다
Johnykutty

1
@DannyBuonocore 는 UIKit과 SwiftUI의 차이점을 찾기 위해 developer.apple.com/documentation/swiftui 를 참조하십시오 . 감사합니다
Hitesh Surani 19-06-07

여기 에 내 솔루션을 추가 했습니다. 도움이 되었기를 바랍니다.
Victor Kushnerov

대부분의 솔루션은 다른 제어 탭에서 원하는 반응을 비활성화하므로 원하는대로 작동하지 않습니다. 작동하는 솔루션은 여기에서 찾을 수 있습니다 : forums.developer.apple.com/thread/127196
Hardy

답변:


79

공유 응용 프로그램에 작업을 전송하여 첫 번째 응답자가 사임하도록 강제 할 수 있습니다.

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

1
.keyWindow이제 더 이상 사용되지 않습니다. Lorenzo Santini의 답변을 참조하십시오 .
LinusGeffarth

3
또한 .tapAction이름이 변경되었습니다.onTapGesture
LinusGeffarth

대체 컨트롤이 활성화되면 키보드를 닫을 수 있습니까? stackoverflow.com/questions/58643512/…
Yarm

1
whitebackground없이이 작업을 수행 할 수있는 방법이 있습니까? 스페이서를 사용하고 있으며 스페이서에서 탭 제스처를 감지하는 데 필요합니다. 또한 흰색 배경 전략은 지금 위에 여분의 화면 공간이있는 최신 iPhone에서 문제를 만듭니다. 도움을 주시면 감사하겠습니다!
Joseph Astrahan

나는 당신의 디자인을 향상시키는 답변을 게시했습니다. 원하는 경우 답변을 수정 해 주시면 신용을 신경 쓰지 않습니다.
Joseph Astrahan

60

많은 시도 끝에 나는 (현재) 어떤 컨트롤도 차단하지 않는 솔루션을 찾았습니다-제스처 인식기를 UIWindow.

  1. 드래그를 처리하지 않고 외부 탭에서만 키보드를 닫으려면 UITapGestureRecognizer3 단계 만 사용하면됩니다 .
  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
        }
    }
    

이제 모든보기의 모든 키보드는 터치시 닫히거나 외부로 드래그됩니다.

추신 : 특정 TextField 만 닫으려면 TextField 콜백이 호출 될 때마다 창에 제스처 인식기를 추가하고 제거하십시오. onEditingChanged


3
이 대답은 맨 위에 있어야합니다. 보기에 다른 컨트롤이 있으면 다른 응답이 실패합니다.
Imthath

1
@RolandLariotte는이 동작을 수정하기 위해 답변을 업데이트했습니다. AnyGestureRecognizer의 새로운 구현을 살펴보세요
Mikhail

1
멋진 대답입니다. 완벽하게 작동합니다. @Mikhail은 실제로 일부 텍스트 필드에 대한 제스처 인식기를 제거하는 방법을 알고 싶어합니다 (태그로 자동 완성 기능을 만들었으므로 목록에서 요소를 탭할 때마다이 특정 텍스트 필드가 초점을 잃는 것을 원하지 않습니다)
Pasta

1
이 솔루션은 실제로 훌륭하지만 3 개월 정도 사용한 후 안타깝게도 이러한 종류의 해킹으로 인해 직접 발생하는 버그를 발견했습니다. 제발, 당신에게도 같은 일이 일어나고 있음을 인식하십시오
glassomoss

1
환상적인 대답! scenedelegate없이 iOS 14에서 이것이 어떻게 구현 될지 궁금합니다.
Dom

28

@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

복제 할 수 없습니다. 11/1부터 Apple의 최신 상품을 사용하여 여전히 작동 중입니다. 효과가 있었습니까?
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

23

NavigationView 내에서 TextField를 사용하는 동안 이것을 경험했습니다. 이것이 나의 해결책입니다. 스크롤을 시작할 때 키보드를 닫습니다.

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

이로 인해 onDelete (스 와이프하여 삭제)가 이상한 동작으로 이어집니다.
Tarek Hallak

이것은 좋지만 탭은 어떻습니까?
Danny182

20

keyWindow속성에 액세스 할 필요가없는 키보드를 해제하는 다른 방법을 찾았습니다 . 사실 컴파일러는 다음을 사용하여 경고를 반환합니다.

UIApplication.shared.keyWindow?.endEditing(true)

'keyWindow'는 iOS 13.0에서 더 이상 사용되지 않음 : 연결된 모든 장면에서 키 창을 반환하므로 여러 장면을 지원하는 응용 프로그램에 사용해서는 안됩니다.

대신 다음 코드를 사용했습니다.

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

15

'SceneDelegate.swift'파일의 SwiftUI는 다음을 추가합니다. .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
이것은 또 다른 문제를 제공합니다. 텍스트 필드와 함께 Form {}에 선택기가 있는데 응답하지 않습니다. 이 주제의 모든 답변을 사용하여 해결책을 찾지 못했습니다. 그러나 귀하의 대답은 선택기를 사용하지 않는 경우 다른 곳에서 탭하여 키보드를 해제하는 데 유용합니다.
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
안녕하세요, 현재 두 가지 해결책이 있습니다. 첫 번째는 리턴 버튼에서 해제 된 네이티브 키보드를 사용하는 것이고 두 번째는 탭 처리를 약간 변경하는 것입니다 (일명 'костыль')-window.rootViewController = UIHostingController (rootView : contentView.onTapGesture (count : 2, perform : {window.endEditing (true)})) 도움이
Dim Novo

여보세요. 감사합니다. 두 번째 방법은 그것을 해결했습니다. 저는 숫자 패드를 사용하고있어서 사용자가 숫자 만 입력 할 수 있고 리턴 키가 없습니다. 탭핑으로 해산하는 것이 내가 찾고 있던 것이었다.
Nalov

이로 인해 목록을 탐색 할 수 없습니다.
Cui Mingda

13

SwiftUI 2

다음은 업데이트 된 솔루션입니다. SwiftUI 2 / 아이폰 OS (14) (원래 제안 여기 미하일에 의해).

사용하지 않습니다 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
이것은 완벽하게 작동합니다!
해결해

2
새로운 SwiftUI 라이프 사이클을 염두에두기 때문에 이것은 맨 위에 있어야합니다.
carlosobedgomez

이것은 훌륭하게 작동합니다. 그러나 텍스트 필드를 두 번 탭하면 텍스트를 선택하는 대신 키보드가 사라집니다. 선택을 위해 두 번 탭하는 방법을 알고 있습니까?
게리

@Gary 다른 제스처 중에 탭을 감지하지 않으려면 하단 확장에서 설명 이 false로 설정된 줄을 볼 수 있습니다 . 로 설정하십시오 return false.
pawello2222

false로 설정하면 작동하지만 누군가가 텍스트 영역 외부를 길게 누르거나 끌거나 스크롤해도 키보드가 닫히지 않습니다. 더블 클릭에 대해서만 false로 설정하는 방법이 있습니까 (텍스트 필드 내부에서 더블 클릭이 바람직하지만 모든 더블 클릭에서도 가능합니다).
Gary

11

내 솔루션은 사용자가 외부를 탭할 때 소프트웨어 키보드를 숨기는 방법입니다. 전체 View 컨테이너를 감지 하려면 contentShapewith 를 사용해야 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를 기반으로 함)의 답변을 확장하면 다음과 같은 다른 제스처에서 키보드를 해제 할 수있는 훨씬 더 표현력 있고 강력한 솔루션이 있습니다. 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

이 방법을 사용하면 키보드숨길 수 있습니다 . 스페이서를!

먼저이 기능을 추가하십시오 (Credit Given To : 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
        }
    }
}

다음으로 다음 두 가지 기능을 추가하십시오 (이 질문에서 제공 한 크레딧 : 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 19


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 포함)에서도 작동해야합니다.


0

키보드의 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

Xcode 12 및 iOS 14와 함께 2020 년 6 월에 출시 된 SwiftUI는 hideKeyboardOnTap () 수정자를 추가합니다. 그러면 케이스 번호 2가 해결됩니다. 케이스 번호 1에 대한 솔루션은 Xcode 12 및 iOS 14에서 무료로 제공됩니다. Return 버튼을 누르면 TextField의 기본 키보드가 자동으로 숨겨집니다.


1
iOS14에는 hideKeyboardOnTap 수정자가 없습니다
Teo Sartori

아마도 혼란 스러웠을
Jessy
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.