Ad

Our DNA is written in Swift
Jump

Radar: First Responder defunct when used in Storyboard

Hot on the heels of my research into the responder chain comes this bug report. Nikita Korchagin deserves the main credit for mentioning this first to me.

Turns out that Apple broke the responder chain as it used to work on Mac and non-storyboard apps. I’m filing this as a bug report to find out if this indeed a bug or an “undocumented feature”. rdar://12402078

Update Oct 5th: Apple closed this as suplicate of rdar://12395544

Summary

Controls hooked up to user-defined actions on the First Responder proxy object fail to message the responder chain.

Steps to Reproduce

  • Create a new Utility app
  • Add a doSomething:(id)sender to the app delegate, add a breakpoint there
  • in the storyboard for FlipsideViewController define doSomething: under user-defined actions
  • replace the link of the Done button with one to the First Responder proxy object and choose the doSomething action
  • Build&Run

Expected Results

The action should travel the responder chain and end up at the breakpoint in the app delegate

Actual Results

No action is invoked

Regression

The behavior is broken in Storyboards. It works when not using story boards.

Notes

On a non-storyboard app the sendAction of UIApplication has the next upper view controller as target. When using a storyboard the sendAction instead messages the UIBarButtonItem first. I suspect that because UIBarButtonItem is not a UIResponder subclass the default behavior is for the event bubbling to stop there.

If you add a new button to the FlipsideController and also hook this hop to the doSomething: action it doesn’t even call the application’s sendAction. On a non-storyboard app this would also normally send the action to the closest view controller.


Categories: Bug Reports

2 Comments »

  1. The last responder in the responder chain is UIApplication not UIApplicationDelegate,

    The expected the behavior works by subclassing UIApplication and put doSomething:(id)sender there.

    I use this for detecting missing methods in debug mode.

    @implementation UIApplication (UIResponderHandler)
    static NSSet* ignoreSelectors = nil;

    + (void)load{
    @autoreleasepool {
    ignoreSelectors = [[NSSet alloc] initWithObjects:
    NSStringFromSelector(@selector(_shouldApplyExclusiveTouch)),
    NSStringFromSelector(@selector(accessibilityInitialize)),
    NSStringFromSelector(@selector(applicationSuspend:settings:)),
    NSStringFromSelector(@selector(applicationResume:settings:)),
    nil];
    }
    }

    – (NSArray*) responderChain{
    NSMutableArray* chain = [NSMutableArray array];
    for (UIResponder* responder = self; responder != nil; responder = [responder nextResponder]) {
    [chain addObject: responder];
    }

    return chain;
    }

    – (BOOL)respondsToSelector:(SEL)selector {
    BOOL responds = [super respondsToSelector:selector];

    if (!responds && ![ignoreSelectors containsObject:NSStringFromSelector(selector)]) {
    NSLog(@”UIApplication does not respond to selector %@. Usually this only happens, when a dispatchEventSelector or targetAction was not caught! ”
    “Please review the code and try to send the action to the right target or implement it at an earlier object in the responder chain. ”
    “You can run \”po [self responderChain]\” in gdb to look at the chain when the action is send. (At this point the sender is unknown)”,
    NSStringFromSelector(selector));
    }

    return responds;
    }

    @end

    Kind Regards,
    Florian

  2. The app delegate can be a UIResponder subclass since iOS 5 and then also participate in the responder chain.

    Thanks for your suggestions!