CodeOwl
CodeOwl

Reputation: 672

UIView subclass not receiving touchesBegan, touchesEnded

Ok- so I have an application which subclasses UIView, and relies on touchesBegan and touchesEnded. I refer of course to these callbacks:

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

I updated my iOS from 7 to 9 just the other day (I know, I'm lazy) to find that the touches aren't working. Turns out the above functions aren't getting called in my UIView subclass. Curious, I started a blank project, which is short enough that I'll just post the code here.

main.m:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

Here's AppDelegate. Create a window and a view dynamically.

#import "AppDelegate.h"
#import "SubView.h"

@implementation AppDelegate


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    //create a window and give it a frame. makeKeyAndVisible
    CGRect rect = [[UIScreen mainScreen] bounds];
    self.window = [[UIWindow alloc] initWithFrame:rect];
    self.window.frame = [[UIScreen mainScreen] bounds];
    [self.window makeKeyAndVisible];

    //Create a subview. Make it a red square. make sure it has a background.
    //set the userInteractionEnabled and multipleTouchEnabled and for
    //good meausure call becomeFirstResponder.
    SubView *view = [[SubView alloc] initWithFrame:CGRectMake(100,100,100,100)];
    view.backgroundColor = [UIColor redColor];
    [self.window addSubview:view];
    view.userInteractionEnabled = YES;
    view.multipleTouchEnabled = YES;
    [view becomeFirstResponder];


    //Create default UIViewControllers because Objective-C fails if the windows don't have
    //root view controllers. I don't like being muscled into MVC but what are you gonna do
    NSArray *windows = [[UIApplication sharedApplication] windows];
    for(UIWindow *window in windows) {
        if(window.rootViewController == nil){
            UIViewController* vc = [[UIViewController alloc]initWithNibName:nil bundle:nil];
            window.rootViewController = vc;
            window.userInteractionEnabled = YES;
            window.multipleTouchEnabled = YES;
        }
    }

return YES;
}

@end

And here's the SubView class. Does just has the touchesBegan touchesEnded.

SubView.h

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface SubView : UIView {
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event;

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event;

@end

SubView.m

#import "SubView.h"

@implementation SubView

//delegate method for touches began
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    //[super touchesBegan:touches withEvent:event];
    NSLog(@"touchesBegan!");

}


//delegate method for touches ending.
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

    //[super touchesEnded:touches withEvent:event];
    NSLog(@"touchesEnded!");
}

//just for good measure.
- (BOOL) canBecomeFirstResponder {
    return YES;
}

@end

Pretty simple. Create a window, create a SubView, add SubView to the window, set userInteractionEnabled. And yet, it doesn't work. I've looked at the following S.O. posts:

subviews are not receiving touches

iPhone SDK - Forwarding touches from a UIViewController to a subview

touchesBegan not responding

touchesBegan in UIView not being called

touchesBegan not called in UIView subclass

touchesBegan not called in a UIView subclass

They all say userInteractionEnabled = YES should fix it, or there might be view covering another view, or the view might have a transparent background. None of that applies here. I've set view.userInteractionEnabled = YES, and I've even tried to give the view first-responder status. None of it seems to do any good.

Astute observers will also see that I'm setting these values on the window itself:

window.userInteractionEnabled = YES;
window.multipleTouchEnabled = YES;

I've tried it with and without. No dice. I've tried calling

[super touchesBegan:touches withEvent:event]

and

[super touchesEnded:touches withEvent:event]

in those callbacks on SubView. No dice. Any help you can give me would be greatly appreciated. Thanks!

Upvotes: 1

Views: 2437

Answers (1)

rob mayoff
rob mayoff

Reputation: 385590

Look at your view hierarchy while the app is running, either using recursiveDescription or using Xcode 7's “Debug View Hierarchy” button.

(lldb) po [[[UIApplication sharedApplication] keyWindow] recursiveDescription]
<UIWindow: 0x7fc1f9d17b20; frame = (0 0; 414 736); gestureRecognizers = <NSArray: 0x7fc1f9d18910>; layer = <UIWindowLayer: 0x7fc1f9d16560>>
   | <SubView: 0x7fc1f9c04350; frame = (100 100; 100 100); layer = <CALayer: 0x7fc1f9c04730>>
   | <UIView: 0x7fc1f9c07500; frame = (0 0; 414 736); autoresize = W+H; layer = <CALayer: 0x7fc1f9c07fc0>>

Views later in the list of subviews are “closer” to the screen and thus receives touches first. The view created by the generic UIViewController fills the window and is in front of your SubView. Thus your SubView can't receive touches.

Apple added the rootViewController property to UIWindow in iOS 4.0—over five years ago. By fighting it, you're only making trouble for yourself. Just let rootViewController.view be the single top-level view in your window, and add your subviews under that. Nobody's forcing you to add any other view controllers to your app.

Upvotes: 5

Related Questions