What's the trick to pass an event to the next responder in the responder chain?

前端 未结 8 986
悲&欢浪女
悲&欢浪女 2020-12-05 14:44

Apple is really funny. I mean, they say that this works:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch* touch = [touches any         


        
相关标签:
8条回答
  • 2020-12-05 15:18

    According to a similar question, your method should work.

    This leads me to think that your view's nextResponder is not actually the ViewController, as you suspect.

    I would add a quick NSLog in your forwarding code to check what your nextResponder really points to:

    if (numTaps < 2) {
        NSLog(@"nextResponder = %@", self.nextResponder);
        [self.nextResponder touchesBegan:touches withEvent:event];
    }
    

    You can also change your other NSLog messages so that they output type and address information:

    NSLog(@"touched %@", self);
    

    touched <UIView 0x12345678>

    This should get you started on diagnosing the problem.

    0 讨论(0)
  • 2020-12-05 15:19

    use this

        [super.nextResponder touchesBegan:touches withEvent:event];
    

    before this line in your code

        [self.nextResponder touchesBegan:touches withEvent:event];
    

    it means

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
         UITouch* touch = [touches anyObject];
         NSUInteger numTaps = [touch tapCount];
         if (numTaps < 2) {
             [super.nextResponder touchesBegan:touches withEvent:event];
             [self.nextResponder touchesBegan:touches withEvent:event];
         } else {
             [self handleDoubleTap:touch];
         }
    }
    

    **Swift 4 Version **

    func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        let touch = touches?.first as? UITouch
        let numTaps: Int? = touch?.tapCount
        if (numTaps ?? 0) < 2 {
            if let aTouches = touches as? Set<UITouch> {
                super.next?.touchesBegan(aTouches, with: event)
                next?.touchesBegan(aTouches, with: event)
            }
        } else {
            handleDoubleTap(touch)
        }
    }
    
    0 讨论(0)
  • 2020-12-05 15:24

    To reference a view controller, you need to use

    [[self nextResponder] nextResponder]
    

    because [self nextResponder] means a view controller's view, and [[self nextResponder] nextResponder] means the view controller itself, now you can get the touch event

    0 讨论(0)
  • 2020-12-05 15:26

    Had trouble with this, as my custom view was deeper in the view hierarchy. Instead, I climbed the responder chain until it finds a UIViewController;

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
        // Pass to top of chain
        UIResponder *responder = self;
        while (responder.nextResponder != nil){
            responder = responder.nextResponder;
            if ([responder isKindOfClass:[UIViewController class]]) {
                // Got ViewController
                break;
            }
        }
        [responder touchesBegan:touches withEvent:event];
    }
    
    0 讨论(0)
  • 2020-12-05 15:36

    If you look at the documentation of UIResponder the description says that by default:

    UIView implements this method by returning the UIViewController object that manages it (if it has one) or its superview (if it doesn’t);

    If your view isn't returning the view controller, it must be returning its superview then (as you figured out).

    UIApplication actually has a method that is supposed to handle this use case exactly:

    - (BOOL)sendAction:(SEL)action
                    to:(id)target
                  from:(id)sender
              forEvent:(UIEvent *)event
    

    Since you don't have a reference to the target, you can work your way up the responder chain by setting target value as nil since according to the documentation:

    If target is nil, the app sends the message to the first responder, from whence it progresses up the responder chain until it is handled.

    You can of course get access to the UIApplication with its class method [UIApplication sharedApplication]

    More info here: UIApplication documents

    This is extra, but if you absolutely need access the view controller to do something a little more complex, you can work your way up the responder chain manually until you reach the view controller by calling the nextResponder method until it returns a view controller. For example:

    UIResponder *responder = self;
    while ([responder isKindOfClass:[UIView class]])
        responder = [responder nextResponder];
    

    then you can just cast it as your view controller and proceed do whatever you wanted to do with it:

    UIViewController *parentVC = (UIViewController *)responder
    

    but note that kind of breaks the MVC pattern and likely means you're doing something in a "hacky" way

    0 讨论(0)
  • 2020-12-05 15:37

    Yes it should 100% work in common case. I've just tried with test project and everything seems to be ok.

    I've created View-Based Application. Using Interface Builder put new UIView up to the present View. Then create new file with subclass of UIView and select just created class for my new view. (Interface Builder->Class identity->Class->MyViewClass)

    Add touches handler functions both for MyViewClass and UIViewController.

    //MyViewClass

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    NSLog(@"myview touches");
    [self.nextResponder touchesBegan:touches withEvent:event];
    }
    

    //ViewController

    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {  
      NSLog(@"controller touches");
    }   
    

    I see both NSLogs when press MyViewClass. Did you use Interface Builder and XIB file when loading your ViewController or set view programatically with loadView function?

    0 讨论(0)
提交回复
热议问题