我遇到了类似的问题。在调用
-[NSView enterFullScreenMode:withOptions:]
后,我无法收到所有的keyDown:事件(特别是Esc键按下),直到我点击全屏视图。
通过在
-[NSResponder doCommandBySelector:]
上设置一个符号断点,我找到了问题所在,它显示了堆栈跟踪:
-[NSApplication sendEvent:]
-[NSWindow sendEvent:]
-[NSWindow keyDown:]
-[NSWindow doCommandBySelector:]
-[NSResponder doCommandBySelector:]
此时会播放系统提示音,表明没有对象可以处理keyDown事件。
查看汇编输出显示,它正在检查窗口的键和主要状态。根本原因是私有的全屏窗口(由AppKit附加到视图上)不会自动成为主窗口或关键窗口,因此无法像预期的那样接收键事件。
修复方法是在调用
-[NSView enterFullScreenMode:withOptions:]
后调用私有全屏窗口的
-makeKeyAndOrderFront:
方法。
这是通过
-[NSObject performSelector:withObject:afterDelay:]
来实现的,因为直到运行循环的下一次迭代,视图的
window
属性才设置为私有全屏窗口(而不是其原始窗口)。我不确定还有其他引用私有窗口的方法。
[self.view.window performSelector:@selector(makeKeyAndOrderFront:)
withObject:nil
afterDelay:0];
在
NSView
上的全屏模式是通过AppKit将视图从其原始窗口中移除,然后将其设置为类型为
_NSFullScreenWindow
的私有窗口的
contentView
来实现的(该窗口在其他方面不具有标题栏)。当视图处于全屏模式时,可以通过选择
Debug>View Debugging>Capture View Hierarchy
来查看。退出全屏模式将其从
_NSFullScreenWindow
中移除,并将其设置为原始窗口的
contentView
。
编辑:
我删除了上面描述的先前修复方法,因为在重新配置我处理按键事件的方式之后它不再起作用。现在应用程序中的按键事件通过窗口的内容视图处理,该内容视图是自定义的
NSView
子类。应用启动时,将
initialResponder
和
firstResponder
都设置为窗口的
contentView
。在调用后必须重新设置这两个窗口属性:
-[NSView exitFullScreenModeWithOptions:]
由于AppKit在全屏过程中会更改它们,因此需要注意。
在我的NSView子类中,处理键盘事件以及全屏:
[self exitFullScreenModeWithOptions:nil];
[self.window setInitialResponder:self];
[self.window makeFirstResponder:self];
我曾遇到一个问题,即当视图处于全屏模式时,键盘事件仍无法在10.9.5上正常工作。问题在于用于全屏模式的私有窗口未将其下一个响应者设置为原始窗口的下一个响应者,就像AppKit在10.11+上自动完成的那样(我不确定在10.10上的行为)。以下操作解决了该问题:
// Get a reference to the window controller from the window BEFORE full screen mode is enabled
// and the view's window is set to the private AppKit "_NSFullScreenWindow" instance.
NSWindowController *windowController = self.window.windowController;
// Enable full screen mode on the view
[self enterFullScreenMode:screen withOptions:opts];
// Compatibility: On 10.9.5 the window controller is not set as the nextResponder on the private full-screen window automatically
// Set the existing window controller as the next responder for the private full screen window to ensure it is placed in the responder chain
[self.window setNextResponder:windowController];