【问题标题】:Is there a robust way to detect first responder changes in subviews?是否有一种可靠的方法来检测子视图中的第一响应者变化?
【发布时间】:2013-08-09 22:37:22
【问题描述】:

我正在构建一个包含其他几个子视图(NSTextField、WebView、...)的自定义视图。当其中一个子视图是第一响应者时,我想让我的自定义视图绘制不同的边框,并充当可以使用菜单项和键盘快捷键操作的单个项目。它看起来像这样:

+-------------+
| NSTextField |
+-------------+
| WebView     |
+-------------+

到目前为止,我已经成功地对 NSTextField 和其他人进行了子类化,以便在调用 - (BOOL)becomeFirstResponder 和 - (BOOL)resignFirstResponder 时通知委托人。这种方法不适用于WebView,因为它本身包含许多子视图——我不能将它们全部子类化!

是否有更好的方法来检测子视图何时更改其第一响应者状态?还是创建自定义视图的更好方法?

【问题讨论】:

  • 这里的问题一模一样,你找到解决办法了吗?

标签: objective-c cocoa webview nsview first-responder


【解决方案1】:

我在 iOS / UIWebView 中遇到了这个问题,它没有在 UIWindow 中实现 makeFirstResponder,也没有在 webViewDidEndEditing 或 shouldBeginEditingInDOMRange 中实现。然而,通过使用 Swizzling,我能够创建一个帮助类别,允许检索当前的第一响应者,并在每次第一响应者更改时发布通知。真的令人沮丧,这一切应该是公共 API,但不是,因为 swizzle 通常不是第一个 goto,但这已经足够好了。

首先,设置您的类别标题:

@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 警告不要混合基类,但没有禁止这样做的法令。

【讨论】:

    【解决方案2】:

    WebViewEditingDelegate方法都会被调用,

    让第一响应者辞职:

    -(void)webViewDidEndEditing:(NSNotification *)notification
    {
    
    }
    

    以及何时成为第一响应者:

    -(BOOL)webView:(WebView *)webView shouldBeginEditingInDOMRange:(DOMRange *)range
    {
        return YES;
    }
    

    【讨论】:

    • 如果这个 API 可用于 UIWebKit 那就太好了 :)
    【解决方案3】:

    另一种方法是覆盖NSWindow 上的-makeFirstResponder: 方法以发送通知。

    - (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。

    但这不是一个理想的解决方案,因为自定义视图不再是独立的。它目前有效,但希望能分享更好的方法。

    【讨论】:

      猜你喜欢
      • 2023-04-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-08
      • 2016-12-26
      • 1970-01-01
      相关资源
      最近更新 更多