Handling Touch Events from Child to Parent While Retaining Screen Coordinate Data Relative to Window

Handling subview’s touch events within its parent while retaining screen coordinate data relative to window

Overview

In this article, we will discuss how to handle touch events for a subview (in this case, an UIImageView) that is covered by its parent view (UIImageView as well). The main goal is to be able to capture the touch events and use them to perform actions on either the child or parent view. We’ll explore two scenarios: one where the child touches send events to the parent, and another where the parent needs to receive touch events with coordinates relative to the window.

Handling Touch Events from Child to Parent

Scenario 1: Child Touches Send Events to Parent

In this scenario, we want the child UIImageView to capture its own touch events and perform an action on its parent. The problem arises when the child touches cover the parent view, making it impossible for the parent to receive touches.

One way to solve this issue is by using a technique called “event forwarding”. We can add a custom method to our child UIImageView class that will forward any touch events received by it to its parent. Here’s an example implementation:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    NSUInteger tapCount = [touch tapCount];

    switch (tapCount) {
        case 1:
            [self parent:self touchBegin:touch];
            break;
        default:
            break;
    }
}

- (void)parent:(UIImageView *)parent touchBegin:(UITouch *)touch {
    // Perform some action on the parent, e.g., set a new image
    UIImage *img = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"child" ofType:@"png"]];
    [self setImage=img];

    // Forward the event to its parent (if it's a superview)
    UIView *superview = self.superview;
    while (superview) {
        if ([superview isKindOfClass:[UIImageView class]]) {
            [[superview performSelector:@selector(touchesBegan:withEvent:) withObject:touches withEvent:event]];
            break;
        }
        superview = superview.superview;
    }
}

In this code, when the child touches are received, we call our custom method parent:touchBegin: which not only performs an action on the parent but also forwards the event to its superview (if it’s a UIImageView). This allows the touch events to be propagated up the view hierarchy.

Scenario 2: Parent Receiving Touch Events with Coordinates

In this scenario, we want our parent UIImageView to receive touch events and use them to perform actions on itself. The problem is that when the child touches cover the parent view, making it impossible for the parent to receive touches.

To solve this issue, we can add a custom method to our parent UIImageView class that will override the default behavior of receiving touch events. Here’s an example implementation:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // Save the original touchesBegan:withEvent:, so we can restore it later
    NSMethodSignature *signature = [NSMethodSignature signatureWithSelector:@selector(touchesBegan:withEvent:) objectClass:[NSObject class]];
    NSUInteger result = [[self methodSignatureForSelector:signature] selectorReturnLength];
    id selfPointer = self;
    while (result > 0) {
        result -= [self methodSignatureForSelector:signature].instanceMethodReturnLength;
        [[self methodSignatureForSelector:signature] invokeWithSelf: selfPointer arguments:nil];
    }

    // Perform some action on the parent, e.g., set a new image
    UIImage *img = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"parent" ofType:@"png"]];
    [self setImage=img];

    // Restore the original touchesBegan:withEvent:
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    // Forward the event to its superview (if it's a superview)
    UIView *superview = self.superview;
    while (superview) {
        if ([superview isKindOfClass:[UIImageView class]]) {
            [[superview performSelector:@selector(touchesMoved:withEvent:) withObject:touches withEvent:event]];
            break;
        }
        superview = superview.superview;
    }

    // Perform some action on the parent, e.g., update its position
    CGPoint center = self.center;
    [self setCenter:center];
}

In this code, when the parent touches are received, we override the default behavior by saving the original touchesBegan:withEvent: method and restoring it later. We also forward any touch moved events to its superview (if it’s a UIImageView). This allows the touch events to be propagated up the view hierarchy.

Adding a UIButton as a Custom Button

Another way to solve this issue is by adding a custom button (UIButton) on top of the child UIImageView. The parent can then capture the touch events and perform actions on itself. Here’s an example implementation:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    // Save the original touchesBegan:withEvent:, so we can restore it later
    NSMethodSignature *signature = [NSMethodSignature signatureWithSelector:@selector(touchesBegan:withEvent:) objectClass:[NSObject class]];
    NSUInteger result = [[self methodSignatureForSelector:signature] selectorReturnLength];
    id selfPointer = self;
    while (result > 0) {
        result -= [self methodSignatureForSelector:signature].instanceMethodReturnLength;
        [[self methodSignatureForSelector:signature] invokeWithSelf: selfPointer arguments:nil];
    }

    // Perform some action on the parent, e.g., set a new image
    UIImage *img = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"parent" ofType:@"png"]];
    [self setImage=img];

    // Forward the event to its superview (if it's a superview)
    UIView *superview = self.superview;
    while (superview) {
        if ([superview isKindOfClass:[UIImageView class]]) {
            [[superview performSelector:@selector(touchesBegan:withEvent:) withObject:touches withEvent:event]];
            break;
        }
        superview = superview.superview;
    }

    // Check if the touch event is on the custom button
    UIButton *customButton = (UIButton *)self.subviews.firstObject;
    if ([touchs containsObject:[customButton touchesForEvent:event]]) {
        // Perform some action on the parent, e.g., set a new image
        UIImage *img = [UIImage imageWithContentsOfFile: [[NSBundle mainBundle] pathForResource:@"child" ofType:@"png"]];
        [self setImage=img];

        // Set the custom button's selected state to false
        customButton.selected = NO;
    }
}

In this code, we add a custom button on top of the child UIImageView. We then override the default behavior of receiving touch events and check if the touch event is on the custom button. If it is, we perform some action on the parent and set the custom button’s selected state to false.

Conclusion

Capturing touch events in a view hierarchy can be challenging, especially when dealing with complex layouts or multiple views. However, by using the techniques outlined in this article, you can solve these issues and create interactive applications that respond to user input.

By forwarding touch events up the view hierarchy using `performSelector:withObject:selector:, you can propagate touch events from child views to their superviews, allowing for more complex interactions.

Additionally, by overriding the default behavior of receiving touch events, you can save the original method signature and restore it later, allowing for more control over how touch events are handled.

Finally, by adding a custom button on top of a child view, you can capture touch events and perform actions on the parent view, creating interactive applications that respond to user input.


Last modified on 2025-02-28