preface

A long time ago, I came across a summary article on iOS responder chains and event handling, which was commented on as a way to do simple inter-UI event handling. I didn’t pay much attention, I didn’t understand. Later, when I summarized this piece of content, I wrote this little tool.

Subview -> vcView -> VC. Usually we pass the events in the subview to the VC and process them in the VC. Basically through notifications, proxies, blocks, etc. But often we have multiple view hierarchies, and often we have to pass it up layer by layer.

Is there an easy way to avoid writing all this pass-through code?

Review of Basic Knowledge

The responder chain and event handling mechanism are not discussed here. Just a quick review of two.

  • The link of the responder chain is: from the view nearest to the user to the system.Initial view -- > super view -- >... . - > View Controller - > Window - > Application - > AppDelegate
  • Transfer chain:

    Pass from the system to the view nearest the user.

    Foreground App - > Window - > root View - >... - > view

The principle of

  1. On the responder chain, we iterate to determine if the responder has implemented a protocol to receive event processing.
  2. Within the View hierarchy, the delivery chain is continuously recursed to determine whether there is an implementation protocol that accepts event processing.

With three enumerated values (ignore the event and continue passing, handle the event and continue passing, handle the event and end passing)

implementation

Define the agreement
@protocol THUIEventBusProtocol <NSObject>
​
@optional
​
/// receiving upward-passed events with event name
/// @param eventName event name
/// @param data data
/// @param callback after handle callback
- (THUIEventResult)receivingUpwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^_Nullable)(id _Nullable otherData))callback;
​
​
/// receiving downward-passed events with event name
/// @param eventName event name
/// @param data data
/// @param callback after handle callback
- (THUIEventResult)receivingDownwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^_Nullable)(id _Nullable otherData))callback;
​
@end
Copy the code
Two core processing methods
  1. Pass events up the responder link (e.g. view to vc)

    + (void)passingEventsUpwardsWithEventName:(nonnull NSString *)eventName responder:(nonnull UIResponder *)responder data:(nullable id)data callback:(void(^)(id _Nullable otherData))callback { if (eventName.length < 1 || ! responder || ! [responder isKindOfClass:[UIResponder class]]) return; UIResponder <THUIEventBusProtocol> *curResponder = (UIResponder <THUIEventBusProtocol> *)responder.thNextResponder; while (curResponder && ! [curResponder isKindOfClass:[UIWindow class]] && ! [curResponder isKindOfClass:[UIApplication class]]) {NSLog(@"🌺 cur is %@", NSStringFromClass([curResponder class])); if ([curResponder conformsToProtocol:@protocol(THUIEventBusProtocol)]) { if ([curResponder respondsToSelector:@selector(receivingUpwardPassedEventsWithEventName:data:callback:)]) { THUIEventResult res = [curResponder receivingUpwardPassedEventsWithEventName:eventName data:data callback:callback]; if (res == THUIEventResultHandleAndStop) break; } } curResponder = (UIResponder <THUIEventBusProtocol> *)curResponder.thNextResponder; }}Copy the code
  2. Pass down the chain (e.g. Vc passes to all its subviews)

    + (void)passingEventsDownWithEventName:(nonnull NSString *)eventName responder:(nonnull UIResponder *)responder data:(nullable id)data callback:(void(^)(id _Nullable otherData))callback { if (eventName.length < 1 || ! responder || ! [responder isKindOfClass:[UIResponder class]]) return; NSLog(@"🍊 cur responder is %@", NSStringFromClass([responder class])); THUIEventResult res = THUIEventResultIgnoreAndContinue; if ([responder isKindOfClass:[UIViewController class]]) { // vc UIViewController <THUIEventBusProtocol> *responderVC = (UIViewController <THUIEventBusProtocol> *)responder; if ([responderVC conformsToProtocol:@protocol(THUIEventBusProtocol)] && [responderVC respondsToSelector:@selector(receivingDownwardPassedEventsWithEventName:data:callback:)]) { res = [responderVC receivingDownwardPassedEventsWithEventName:eventName data:data callback:callback]; if (res == THUIEventResultHandleAndStop) return; } the for (UIViewController * childVC in responderVC childViewControllers) {/ / child vc [the self passingEventsDownWithEventName:eventName responder:childVC data:data callback:callback]; } / / child view [THUIEventBus handleViewWithEventName: eventName responder: responderVC. View data: data callback, the callback]; } // view if ([responder isKindOfClass:[UIView class]]) { ... }}Copy the code

    How to distinguish between received events by eventName, ignore and continue? Process and continue passing? Process and stop the delivery?

    other

    Add a thNextResponder class extension to UIResponder.

    NextResponder can be set actively to control the transmission of events.

    Method mapping table

    Such as the following scenarios:

    In the vc

    - (THUIEventResult)receivingUpwardPassedEventsWithEventName:(nonnull NSString *)eventName data:(nullable id)data callback:(void(^)(id _Nullable otherData))callback {
        return THUIEventResultHandleAndStop;
    }
    Copy the code

    We accept events from the child View in this method, which may have different processing logic depending on eventName. So the first thing that comes to mind is if else. If there are many events, consider writing a method mapping table.

    - (NSDictionary <NSString *, NSInvocation *> *)methodDict {
        if (_methodDict == nil) {
            _methodDict = @{
                kUIEventA : [self createInvocationWithSelector:@selector(handleEventA:)],
                kUIEventB : [self createInvocationWithSelector:@selector(handleEventB:)],
                kUIEventC : [self createInvocationWithSelector:@selector(handleEventC:)]
            };
        }
        return _methodDict;
    }
    Copy the code
    - (NSInvocation *)createInvocationWithSelector:(SEL)selector {
        NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:selector];
        
        if (signature == nil) {
            NSString *info = [NSString stringWithFormat:@"-[%@ %@] : unrecognized selector sent to instance", [self class],
                              NSStringFromSelector(selector)];
            @throw [[NSException alloc] initWithName:@"No such method" reason:info userInfo:nil];
            return nil;
        }
        
        NSInvocation *invo = [NSInvocation invocationWithMethodSignature:signature];
        
        invo.target = self;
        invo.selector = selector;
        return invo;
    }
    Copy the code

The project address

Afterword.

Sometimes the basic knowledge is simple, and the diligent conversion into code helps to understand, and maybe find something.