Question

I'm building a custom view that contains several other subviews (NSTextField, WebView,...). I'd like to have my custom view draw a different border when one of the subviews is the first responder, and act as a single item that can be acted upon with menu items and keyboard shortcuts. It looks something like this:

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

So far, I've had success subclassing NSTextField and others to notify a delegate when - (BOOL)becomeFirstResponder and - (BOOL)resignFirstResponder are called. This approach doesn't work with WebView though, as it itself contains many subviews--I can't subclass them all!

Is there a better way to detect when subviews change their first responder status? Or a better way to create a custom view?

Was it helpful?

Solution 2

Both WebViewEditingDelegate method will be call,

Resign first responder:

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

}

and when become first responder:

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

OTHER TIPS

A different approach would be to override the -makeFirstResponder: method on NSWindow to send out a notification.

- (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];
}

You can then listen for the notification in your custom view or a view controller and check if the previous or next responders are subviews using -isDescendantOf: and set needsDisplay as needed.

This is not an ideal solution though, because the custom view is no longer self-contained. It works for now, but hopefully a better approach will be shared.

I had this problem with iOS / UIWebView, which doesn't implement makeFirstResponder in UIWindow, nor webViewDidEndEditing or shouldBeginEditingInDOMRange. However, with the use of Swizzling I was able to create a helper category that allows for retrieval of the current first responder, as well as posting a notification every time first responder changes. Really frustrating how all this should be public API, but isn't, as swizzle isn't normally a first goto, but this worked well enough.

First, setup your category header:

@interface UIResponder (Swizzle)
+ (UIResponder *)currentFirstResponder;
- (BOOL)customBecomeFirstResponder;
@end

Then Category implementation

@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

Finally, using a helper like JR Swizzle, swap the classes.

#import "JRSwizzle.h"

- (void)applicationLoaded {  
    if(![UIResponder jr_swizzleMethod:@selector(becomeFirstResponder) withMethod:@selector(customBecomeFirstResponder) error:&error]) { 
        NSLog(@"Error swizzling - %@",error);
    }
}

Thought I'd share. Valid in App store as it doesn't utilize private API, and while Apple cautions against swizzling base classes there is no edict against doing so.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top