UIViewのタッチイベントをUIViewControllerにデリゲートする

コントローラに定義したメソッドをタッチで呼び出したくて奮闘した。
デリゲートって、なんか小難しい技なのかと思ってたけど、単にコントローラとかのインスタンスのポインタをビューのインスタンス変数に入れておくってだけっぽい。
正確には違うのかもしれないけど、多分そんな感じ。
 
先週末辺りから会社でiPhoneアプリ作ってる。
面白い。
 

ソース

登場人物は、2つ。
・Controller => UIViewControllerを継承したRootViewController。
・View => UIViewを継承したRootView。
 
RootView.delegateにRootViewControllerを代入。
RootViewでタッチイベントを受け取る。
RootViewからRootView.delegateに代入されてるRootViewControllerのメソッドを呼び出す。
 

RootViewController.h
#import <UIKit/UIKit.h>
#import "RootView.h"
#import "TouchesDelegate.h"


@interface RootViewController : UIViewController <TouchesDelegate> {
}

@end

 

RootViewController.m
#import "RootViewController.h"


@implementation RootViewController

/*
 // The designated initializer.  Override if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if ((self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil])) {
        // Custom initialization
    }
    return self;
}
*/

// Implement loadView to create a view hierarchy programmatically, without using a nib.
- (void)loadView {
	RootView *contentView = [[RootView alloc] initWithFrame:CGRectMake(0,20,320,460)];
	[contentView setDelegate:self];
	self.view = contentView;
	[contentView release];
}

/*
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
    [super viewDidLoad];
}
*/

/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    // Return YES for supported orientations
    return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/

- (void)didReceiveMemoryWarning {
    // Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

    // Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
    [super viewDidUnload];
    // Release any retained subviews of the main view.
    // e.g. self.myOutlet = nil;
}


/* TouchesDelegate */

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

- (void)view:(UIView*)view touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
	NSLog(@"delegate touchesMoved");
}

- (void)view:(UIView*)view touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
	NSLog(@"delegate touchesEnded");
}

- (void)view:(UIView*)view touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event {
	NSLog(@"delegate touchesCancelled");
}


- (void)dealloc {
    [super dealloc];
}


@end

 

RootView.h
#import <UIKit/UIKit.h>
#import "TouchesDelegate.h"


@interface RootView : UIView {
	id<TouchesDelegate> delegate;
}

@property (nonatomic, retain) id delegate;

@end

 

RootView.m
#import "RootView.h"


@implementation RootView
@synthesize delegate;


- (id)initWithFrame:(CGRect)frame {
    if ((self = [super initWithFrame:frame])) {
        // Initialization code
    }
    return self;
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/


/* touch */

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

- (void)touchesMoved: (NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesMoved");
	[delegate view:self touchesMoved:touches withEvent:event];
}

- (void)touchesEnded: (NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesEnded");
	[delegate view:self touchesEnded:touches withEvent:event];
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesCancelled");
	[delegate view:self touchesCancelled:touches withEvent:event];
}


- (void)dealloc {
    [super dealloc];
}


@end

 

TouchesDelegate.h
#import <UIKit/UIKit.h>


@protocol TouchesDelegate

@optional
- (void)view:(UIView*)view touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event;
- (void)view:(UIView*)view touchesCancelled:(NSSet*)touches withEvent:(UIEvent*)event;

@end

 

デバッグログ

ちゃんとデリゲートされてる。

2010-08-03 04:15:32.991 TouchTest[1834:207] touchesBegan
2010-08-03 04:15:32.992 TouchTest[1834:207] delegate touchesBegan
2010-08-03 04:15:33.170 TouchTest[1834:207] touchesMoved
2010-08-03 04:15:33.170 TouchTest[1834:207] delegate touchesMoved
2010-08-03 04:15:33.237 TouchTest[1834:207] touchesMoved
2010-08-03 04:15:33.237 TouchTest[1834:207] delegate touchesMoved
2010-08-03 04:15:33.321 TouchTest[1834:207] touchesMoved
2010-08-03 04:15:33.321 TouchTest[1834:207] delegate touchesMoved
2010-08-03 04:15:33.393 TouchTest[1834:207] touchesEnded
2010-08-03 04:15:33.394 TouchTest[1834:207] delegate touchesEnded

 

参考URL

【コラム】実践! iPhoneアプリ開発 (23) タワーディフェンスゲームの作り方 (6) - キャノンを配置する | エンタープライズ | マイコミジャーナル
http://journal.mycom.co.jp/column/iphone/023/index.html
 

追記 2010-08-03 17:21:38

プロトコルの@optional指定したメソッドを実装しなかった時にエラーが出た。

2010-08-03 16:51:33.240 TouchTest[3343:207] touchesBegan
2010-08-03 16:51:33.247 TouchTest[3343:207] *** -[RootViewController view:touchesBegan:withEvent:]: unrecognized selector sent to instance 0x3912fc0
2010-08-03 16:51:33.248 TouchTest[3343:207] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[RootViewController view:touchesBegan:withEvent:]: unrecognized selector sent to instance 0x3912fc0'
2010-08-03 16:51:33.248 TouchTest[3343:207] Stack: (
    29291611,
    2480882953,
    29673531,
    29243124,
    29095618,
    9600,
    2825523,
    2734024,
    2760801,
    37387609,
    29076352,
    29072456,
    37381653,
    37381850,
    2764719,
    8198
)

 

respondsToSelector

実装されているかチェックしてから実行しないといけないらしい。
nullのオブジェクトのメソッドを呼んでも例外を発生させないObjective-Cの事だから、きっと大丈夫と思ってたら、駄目だった。
ゆるいのは、オブジェクトそのものに対してだけらしい。
メソッドの有無は、respondsToSelectorを使って調べる。
RootView.mを変更。

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesBegan");
	if ([delegate respondsToSelector:@selector(view:touchesBegan:withEvent:)]) {
		[delegate view:self touchesBegan:touches withEvent:event];
	}
}

- (void)touchesMoved: (NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesMoved");
	if ([delegate respondsToSelector:@selector(view:touchesMoved:withEvent:)]) {
		[delegate view:self touchesMoved:touches withEvent:event];
	}
}

- (void)touchesEnded: (NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesEnded");
	if ([delegate respondsToSelector:@selector(view:touchesEnded:withEvent:)]) {
		[delegate view:self touchesEnded:touches withEvent:event];
	}
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
	NSLog(@"touchesCancelled");
	if ([delegate respondsToSelector:@selector(view:touchesCancelled:withEvent:)]) {
		[delegate view:self touchesCancelled:touches withEvent:event];
	}
}

 
コンパイルしたら、こんなwarningが出た。

'-respondsToSelector:' not found in protocol

 
respondsToSelectorはNSObjectで定義されているメソッドなので、プロトコルにNSObjectを継承させる。
TouchesDelegate.hを変更。

@protocol TouchesDelegate <NSObject>

 

参考URL

iPhoneアプリ開発、その(120) 一人時間差攻撃〜!|テン*シー*シー
http://ameblo.jp/xcc/entry-10325508764.html