我正在构建一个自定义视图,其中包含其他几个子视图(
NSTextField
,WebView
,...)。当其中一个子视图是第一响应者时,我想让我的自定义视图绘制不同的边框,并充当可以通过菜单项和键盘快捷键进行操作的单个项目。它看起来像这样:
+-------------+
| NSTextField |
+-------------+
| WebView |
+-------------+
到目前为止,我已经成功地子类化了
NSTextField
和其他人,以便在调用 - (BOOL)becomeFirstResponder
和 - (BOOL)resignFirstResponder
时通知委托。不过,这种方法不适用于 WebView
,因为它本身包含许多子视图——我无法将它们全部子类化!
是否有更好的方法来检测子视图何时更改其第一响应者状态?或者创建自定义视图的更好方法?
另一种方法是重写
-makeFirstResponder:
上的 NSWindow
方法来发送通知。
- (BOOL)makeFirstResponder:(NSResponder *)responder {
id previous = self.firstResponder ?: [NSNull null];
id next = responder ?: [NSNull null];
NSDictionary *userInfo = @{
BrFirstResponderPreviousKey: previous,
BrFirstResponderNextKey: next,
};
[[NSNotificationCenter defaultCenter] postNotificationName:BrFirstResponderWillChangeNotification object:self userInfo:userInfo];
return [super makeFirstResponder:responder];
}
然后,您可以在自定义视图或视图控制器中监听通知,并使用
-isDescendantOf:
检查上一个或下一个响应者是否是子视图,并根据需要设置 needsDisplay
。
但这不是一个理想的解决方案,因为自定义视图不再是独立的。目前它有效,但希望能分享更好的方法。
两个 WebViewEditingDelegate 方法都会被调用,
辞职第一响应者:
-(void)webViewDidEndEditing:(NSNotification *)notification
{
}
当成为第一响应者时:
-(BOOL)webView:(WebView *)webView shouldBeginEditingInDOMRange:(DOMRange *)range
{
return YES;
}
我在 iOS /
UIWebView
上遇到了这个问题,它没有在 makeFirstResponder
中实现 UIWindow
,也没有实现 webViewDidEndEditing
或 shouldBeginEditingInDOMRange
。然而,通过使用 Swizzling,我能够创建一个帮助程序类别,允许检索当前的第一响应者,并在每次第一响应者发生变化时发布通知。真的很令人沮丧,所有这些应该是公共 API,但事实并非如此,因为 swizzle 通常不是第一个转到,但这已经足够好了。
首先,设置您的类别标题:
@interface UIResponder (Swizzle)
+ (UIResponder *)currentFirstResponder;
- (BOOL)customBecomeFirstResponder;
@end
然后类别实现
@implementation UIResponder (Swizzle)
// It's insanity that there is no better way to get a notification when the first responder changes, but there it is.
static UIResponder *sCurrentFirstResponder;
+ (UIResponder *)currentFirstResponder {
return sCurrentFirstResponder;
}
- (BOOL)customBecomeFirstResponder {
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:2];
if(sCurrentFirstResponder) {
[userInfo setObject:sCurrentFirstResponder forKey:NSKeyValueChangeOldKey];
}
sCurrentFirstResponder = self;
if(sCurrentFirstResponder) {
[userInfo setObject:sCurrentFirstResponder forKey:NSKeyValueChangeNewKey];
}
[[NSNotificationCenter defaultCenter] postNotificationName:kFirstResponderDidChangeNotification
object:nil
userInfo:userInfo];
return [self customBecomeFirstResponder];
}
@end
最后,使用像 JR Swizzle 这样的助手,交换类。
#import "JRSwizzle.h"
- (void)applicationLoaded {
if(![UIResponder jr_swizzleMethod:@selector(becomeFirstResponder) withMethod:@selector(customBecomeFirstResponder) error:&error]) {
NSLog(@"Error swizzling - %@",error);
}
}
我想分享一下。在 App store 中有效,因为它不使用私有 API,虽然 Apple 警告不要混合基类,但没有禁止这样做的法令。
extension NSWindow {
open override func validateProposedFirstResponder(_ responder: NSResponder, for event: NSEvent?) -> Bool {
if let previous = self.firstResponder {
let next = responder
print("ZZZ prev: \(previous), next: \(next)")
}
return super.validateProposedFirstResponder(responder, for: event)
}
}