如何判断macOS应用退至后台时是否有文本输入框处于激活状态?
判断macOS应用退后台时是否有激活的文本输入框
要实现这个需求,核心是在应用即将失去焦点的时机,记录当前是否有文本输入控件处于激活状态——因为应用恢复焦点时,无法直接回溯退后台时的控件状态。以下是几种可行的AppKit/SwiftUI方案:
方案1:利用第一响应者快速判断(推荐)
AppKit中,激活的文本输入控件(NSTextField、SwiftUI TextField)会成为当前窗口的第一响应者。我们可以通过检查第一响应者的类型,快速判断是否有激活的文本输入框:
步骤1:实现全局判断方法
private func isAnyTextInputActive() -> Bool { guard let keyWindow = NSApplication.shared.keyWindow, let firstResponder = keyWindow.firstResponder else { return false } // 覆盖原生文本控件及SwiftUI封装的文本控件 return firstResponder is NSTextField || firstResponder is NSTextView || // 兼容SwiftUI TextField底层的私有封装(非公开API,但实际稳定) (firstResponder.value(forKey: "_textView") as? NSTextView) != nil }
步骤2:在状态变更时记录并使用状态
修改你的SwiftUI View代码,在应用即将失去焦点时保存状态,恢复时读取:
struct FooView: View { @Environment(\.controlActiveState) private var controlActiveState @State private var wasTextInputActiveOnBackground = false var body: some View { Text("Hello World") .onChange(of: controlActiveState) { newState in if newState == .inactive { // 应用即将退后台,记录当前激活状态 wasTextInputActiveOnBackground = isAnyTextInputActive() } else { // 应用恢复焦点,判断之前的状态 guard wasTextInputActiveOnBackground else { return } print("Should only be reached when the app was backgrounded with an active text input") } } } private func isAnyTextInputActive() -> Bool { guard let keyWindow = NSApplication.shared.keyWindow, let firstResponder = keyWindow.firstResponder else { return false } return firstResponder is NSTextField || firstResponder is NSTextView || (firstResponder.value(forKey: "_textView") as? NSTextView) != nil } }
方案2:遍历视图层级(无私有API依赖)
如果不想依赖私有API,可以递归遍历所有窗口的视图层级,检查是否有处于编辑状态的文本控件:
private func isAnyTextInputActive() -> Bool { // 遍历所有应用窗口 for window in NSApplication.shared.windows { if checkSubviewsForActiveTextInput(window.contentView) { return true } } return false } private func checkSubviewsForActiveTextInput(_ view: NSView?) -> Bool { guard let view = view else { return false } // 检查当前视图是否为编辑中的文本控件 if let textField = view as? NSTextField, textField.isEditing { return true } if let textView = view as? NSTextView, textView.isEditing { return true } // 递归检查子视图 for subview in view.subviews { if checkSubviewsForActiveTextInput(subview) { return true } } return false }
这个方法更安全,但在复杂界面下会有轻微性能损耗,不过在应用退后台的时机调用几乎无影响。
方案3:全局监听通知(适用于全应用范围)
如果需要在应用全局处理这个逻辑,可以在App结构体中监听系统通知:
@main struct MyApp: App { @State private var wasTextInputActive = false var body: some Scene { WindowGroup { FooView() } .onReceive(NotificationCenter.default.publisher(for: NSApplication.willResignActiveNotification)) { _ in // 应用即将失去焦点时记录状态 wasTextInputActive = isAnyTextInputActive() } .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in // 应用恢复焦点时判断 if wasTextInputActive { print("App resumed with previously active text input") } } } private func isAnyTextInputActive() -> Bool { guard let keyWindow = NSApplication.shared.keyWindow, let firstResponder = keyWindow.firstResponder else { return false } return firstResponder is NSTextField || firstResponder is NSTextView || (firstResponder.value(forKey: "_textView") as? NSTextView) != nil } }
关键注意事项
- 必须在
NSApplication.willResignActiveNotification(或controlActiveState变为.inactive)时记录状态,此时第一响应者或控件的编辑状态还未被系统重置,能准确获取退后台时的真实状态。 - SwiftUI的
TextField在macOS底层基于NSTextField封装,因此判断NSTextField类型即可覆盖绝大多数场景。
内容的提问来源于stack exchange,提问作者Tudor Andreescu
相关产品推荐
相关产品推荐

