如何在SwiftUI中检测键盘是否存在

29

我想知道在按下按钮时键盘是否存在。我该怎么做?我已经尝试过了,但没有成功。谢谢。

5个回答

57

使用这个协议,KeyboardReadable,你可以符合任何View并从中获取键盘更新。

KeyboardReadable协议:

KeyboardReadable协议:

import Combine
import UIKit


/// Publisher to read keyboard changes.
protocol KeyboardReadable {
    var keyboardPublisher: AnyPublisher<Bool, Never> { get }
}

extension KeyboardReadable {
    var keyboardPublisher: AnyPublisher<Bool, Never> {
        Publishers.Merge(
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillShowNotification)
                .map { _ in true },
            
            NotificationCenter.default
                .publisher(for: UIResponder.keyboardWillHideNotification)
                .map { _ in false }
        )
        .eraseToAnyPublisher()
    }
}

它通过使用Combine并创建一个发布者,以便我们可以接收键盘通知来实现。

以下是一个应用示例:

struct ContentView: View, KeyboardReadable {
    
    @State private var text: String = ""
    @State private var isKeyboardVisible = false
    
    var body: some View {
        TextField("Text", text: $text)
            .onReceive(keyboardPublisher) { newIsKeyboardVisible in
                print("Is keyboard visible? ", newIsKeyboardVisible)
                isKeyboardVisible = newIsKeyboardVisible
            }
    }
}

现在你可以阅读 isKeyboardVisible 变量以确定键盘是否可见。

TextField 激活并显示键盘时,会打印以下内容:

键盘是否可见?true

当按下回车键隐藏键盘后,将打印以下内容:

键盘是否可见?false

您可以使用 keyboardWillShowNotification/keyboardWillHideNotification 来在键盘开始出现或消失时进行更新,以及 keyboardDidShowNotification/keyboardDidHideNotification 变体来在键盘已经出现或消失后更新。我更喜欢使用 will 变体,因为当键盘显示时更新是即时的。


严格来说,对于 PO 问题,您应该使用 DidShow/DidHide 通知,因为 Will* 变体是在被询问状态之前发布的。有时这很重要。 - Asperi
@Asperi 很好的观点 - 我现在已经添加了,谢谢!我通常更喜欢 will 变体,因为更新是即时的,而 did 变体需要等待整个出现/消失动画。 - George
这是一个非常聪明的答案。我尝试着想出使用Combine的方法,但我从未想过这个。但我足够聪明,能够利用它! - P. Ent

22

我对George的回答进行了一点改进。

在View协议内部实现发布者权限。

import Combine
extension View {
  var keyboardPublisher: AnyPublisher<Bool, Never> {
    Publishers
      .Merge(
        NotificationCenter
          .default
          .publisher(for: UIResponder.keyboardWillShowNotification)
          .map { _ in true },
        NotificationCenter
          .default
          .publisher(for: UIResponder.keyboardWillHideNotification)
          .map { _ in false })
      .debounce(for: .seconds(0.1), scheduler: RunLoop.main)
      .eraseToAnyPublisher()
  }
}

我还添加了防抖操作符,以防止在您有多个文本字段并且用户在它们之间移动时进行真 - 假切换。

在任何视图中使用

struct SwiftUIView: View {
  @State var isKeyboardPresented = false
  @State var firstTextField = ""
  @State var secondTextField = ""
  
  var body: some View {
    VStack {
      TextField("First textField", text: $firstTextField)
      TextField("Second textField", text: $secondTextField)
    }
    .onReceive(keyboardPublisher) { value in
      isKeyboardPresented = value
    }
  }
}

2
等等,什么?制表符缩进设置为2?你去吧Max,向他们证明吧!(自80年代以来,我一直拒绝将我的制表符设置为除2以外的任何值 :-) - Yohst

14

iOS 15:

您可以使用focused(_:)视图修饰符和@FocusState属性包装器来判断一个文本字段是否正在编辑,并改变其编辑状态。

@State private var text: String = ""
@FocusState private var isTextFieldFocused: Bool

var body: some View {
    VStack {
        TextField("hello", text: $text)
            .focused($isTextFieldFocused)
        
        if isTextFieldFocused {
            Button("Keyboard is up!") {
                isTextFieldFocused = false
            }
        }
    }
}

21
聚焦和键盘存在并不一定是同一回事。如果有外接键盘连接,那么这不会显示出正确的键盘存在情况。 - ThomasCle

9

使用环境密钥...

借鉴@Lepidopteron的答案,但使用它来驱动环境密钥。

这使您可以在任何视图中访问键盘状态

@Environment(\.keyboardShowing) var keyboardShowing

你所需要做的就是在你的层次结构顶部添加一个视图修饰符。
RootView()
.addKeyboardVisibilityToEnvironment()

这一切都由以下的ViewModifier文件驱动...

public extension View {
    
    /// Sets an environment value for keyboardShowing
    /// Access this in any child view with
    /// @Environment(\.keyboardShowing) var keyboardShowing
    func addKeyboardVisibilityToEnvironment() -> some View {
        modifier(KeyboardVisibility())
    }
}

private struct KeyboardShowingEnvironmentKey: EnvironmentKey {
    static let defaultValue: Bool = false
}

extension EnvironmentValues {
    var keyboardShowing: Bool {
        get { self[KeyboardShowingEnvironmentKey.self] }
        set { self[KeyboardShowingEnvironmentKey.self] = newValue }
    }
}

private struct KeyboardVisibility:ViewModifier {
    
#if os(macOS)
    
    fileprivate func body(content: Content) -> some View {
        content
            .environment(\.keyboardShowing, false)
    }
    
#else
    
    @State var isKeyboardShowing:Bool = false
    
    private var keyboardPublisher: AnyPublisher<Bool, Never> {
        Publishers
            .Merge(
                NotificationCenter
                    .default
                    .publisher(for: UIResponder.keyboardWillShowNotification)
                    .map { _ in true },
                NotificationCenter
                    .default
                    .publisher(for: UIResponder.keyboardWillHideNotification)
                    .map { _ in false })
            .debounce(for: .seconds(0.1), scheduler: RunLoop.main)
            .eraseToAnyPublisher()
    }
    
    fileprivate func body(content: Content) -> some View {
        content
            .environment(\.keyboardShowing, isKeyboardShowing)
            .onReceive(keyboardPublisher) { value in
                isKeyboardShowing = value
            }
    }
    
#endif
}

这真的很简单...非常感谢你。对于像我这样的初学者,请确保在文件顶部添加import SwiftUIimport Combine。我在初始的WindowGroup中的第一个调用ContentView()中添加了.addKeyboardVisibilityToEnvironment(),并在我的ContentView视图顶部添加了@Environment(\.keyboardShowing) var keyboardShowing。一切都运行得很完美。再次感谢。 - SPM

2
你还可以创建一个viewModifier来接收键盘通知。
struct OnkeyboardAppearHandler: ViewModifier {
    var handler: (Bool) -> Void
    func body(content: Content) -> some View {
        content
            .onAppear {
                NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillShowNotification, object: nil, queue: .main) { _ in
                    handler(true)
                }
                
                NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification, object: nil, queue: .main) { _ in
                    handler(false)
                }
            }
    }
}

extension View {
    public func onKeyboardAppear(handler: @escaping (Bool) -> Void) -> some View {
        modifier(OnkeyboardAppearHandler(handler: handler))
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接