- ベストアンサー
カスタムビューを重ねて、閉じる
- Objective-Cで開発を行っている者です。ViewControllerにTableViewを乗せ、そこにUIButtonを乗せたカスタムセルを乗せて、ボタンを押すとカスタムUIViewをaddViewする、というアプリを作りました。
- 後半のカスタムUIViewを出すところは、こちらのページを参考にさせていただきました。http://montan.hateblo.jp/entry/2012/08/21/223335
- また、カスタムUIViewにもボタンを置き、それを押すと[self removeFromSuperview];で閉じるようにしました。ビルドすると、カスタムセルのボタンを押してUIViewを出し、さらにそこのボタンを押してUIViewを閉じるところまでは問題なく動きました。しかしもう一度セルのボタンを押すと、unrecognized selector sent to instance エラーが出てしまいます。原因として考えられることはなにがあるでしょうか。
- みんなの回答 (5)
- 専門家の回答
質問者が選んだベストアンサー
No.1,4です。 > 今回の問題を解決するために極力シンプルなプロジェクトをつくっていじっているので、 >見ていただいたコード以外にはCustomViewクラスがあるのみです。 > その中にも、指摘していただいたような箇所は見当たりませんでした・・・。 storyBoardやxibファイル内の各部品のイベント定義で 何か誤ってaddViewメソッドを設定しているようなこともなかったですか? > 少し自分でも混乱してきたので、一度整理してからもう一度チャレンジしてみたいと思います。 私がやったように、1から新規プロジェクトを生成して、動作するか確認してみると よいと思います。それが動けば、エラーが出る方と何が違うか比較して問題点を見つけることが できるはずです。 参考に、私が1から作った手順を示します。 (極力シンプルにするため、No.4回答でコメントした通りpushBtnメソッドは作らず ViewController内でaddtargetします。また、No.2お礼でaddSubViewをNSLogに 変えても問題が発生するとのことでしたので、CustomViewも作らずNSLogで ログ出力するだけのものを示します。) 1.Single View Applicationで新しいプロジェクトを作成する。 2.新規ファイル作成メニューで、CustomCellクラス(.h/.m/.xibファイル)を作成する。 設定項目は以下の通り。 「Class」 : CustomCell 「Subclass of」 : UITableViewCell 「Also create XIB file」 : チェックON 3.CustomCell.xibのセル(Content View内)にUIButtonを1個貼付ける。 4.UIButtonのReferencing OutletsをCustomCell.hに接続し、プロパティ名をmyButtonとする。 5.StoryBoardのViewController(View内)にUITableViewを貼付ける。 6.ViewController.hを以下の通りとする。 ------------------------------------------- #import <UIKit/UIKit.h> @interface ViewController : UIViewController <UITableViewDelegate,UITableViewDataSource> @property (weak, nonatomic) IBOutlet UITableView *tableView; @end ------------------------------------------- 7.StoryBoardのUITableViewのReferencing OutletsをViewController.hのtableViewプロパティに接続する。 8.ViewController.mを以下の通りとする。 ------------------------------------------- #import "ViewController.h" #import "CustomCell.h" @interface ViewController () @end @implementation ViewController - (void)viewDidLoad { [super viewDidLoad]; _tableView.dataSource = self; _tableView.delegate = self; [_tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:nil] forCellReuseIdentifier:@"Cell"]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; [cell.myButton addTarget:self action:@selector(addView:) forControlEvents:UIControlEventTouchUpInside]; return cell; } -(void)addView:(id)sender{ NSLog(@"addView"); } @end ------------------------------------------- これで実行するとボタンを押すたびにログに「addView」が表示されます。 2回目のタップでエラーが発生するようなことはありません。 これがうまくいくことを確認してからCustomViewを追加すればよいと思います。
その他の回答 (4)
- Lchan0211b
- ベストアンサー率61% (573/930)
No.1です。 No.2お礼にあったコードをそのままコピペして、 足りないところを補って新規プロジェクトを作ったところ、 問題なくカスタムViewの表示と削除を何度も繰り返すことが できました。 なので、ここに書いてある部分以外の問題だろうと思います。 エラーメッセージより、本来ViewControllerのaddViewを 呼び出すはずが、UIButtonのaddViewを呼び出そう としてエラーになっています。 これはつまり、addTargetする時の指定オブジェクトに ViewControllerではなくUIButtonのオブジェクトを 指定している場合があるということになります。 あるいは、storyboardやxibの中で定義したUIButtonの イベントにaddViewメソッドを登録しているものが あるのかもしれません。 開示されたコードでは、CustomCellのpushBtnの中で addTargetしており、その呼び出しはViewControllerの cellForRowAtIndexPathからしか呼び出されていませんが、 実際のコードで、それ以外にaddTargetしたりpushBtnを 呼び出したりしているところはないでしょうか? あるいは、開示されたコードでは、cellForRowAtIndexPath の中で必ずpushBtnを呼び出すようになっていますが、 おそらく実際のコードはこの通りではなくいろいろな処理を しているのだと思います。その処理の中で条件によっては pushBtnが呼ばれないようなケースがないでしょうか? そのあたりを確認してみるとよいと思います。 あと、今回の問題の原因と直接関係ないとは思いますが、 カスタムセル内のボタンのイベントを拾うのに無駄に 複雑なことをしすぎです。 質問に書かれてあったサイトを参考にして、カスタムセルの クラス内にaddTargetするメソッド(pushBtn)を作り、 それをcellForRowAtIndexPathから呼び出していますが、 そんなことをしなくても、cellForRowAtIndexPathの中で [cell.myButton addTarget:self action:@selector(addView:) forControlEvents:UIControlEventTouchUpInside]; と書くだけでいいです。 pushBtnメソッドは不要です。つまりカスタムセル内は 新たなメソッドを作る必要ありません。 あなたが参考にされたサイトは、その冒頭に 「エンジニアのたまごです」 と書かれている通り、初心者が書かれているものです。 おそらくcell.myButtonでボタンにアクセスできることに 気付かずに回り道をしたのだと思います。 もちろん、カスタムセル内に一定の機能を持たせ、 その機能の一環でボタンのイベントを自動登録するというケースも あるのですが、pushBtnとかbtnPushedといったメソッド名では そのような意図は感じられず、さらにボタンイベントの登録を する処理ではなくボタンが押された時に呼ばれるメソッドのような 印象を与える名前で、とても悪いネーミングだと思います。 このようなネーミング一つでとてもわかりにくいコーディングに なります。No.3さんにスパゲティコードだと言われても仕方のない ところだと思います。
お礼
お礼が遅くなり大変申し訳ありません。 色々なご指摘をしていただきましてありがとうございます。 >実際のコードで、それ以外にaddTargetしたりpushBtnを 呼び出したりしているところはないでしょうか? >その処理の中で条件によっては pushBtnが呼ばれないようなケースがないでしょうか? 今回の問題を解決するために極力シンプルなプロジェクトをつくっていじっているので、見ていただいたコード以外にはCustomViewクラスがあるのみです。 その中にも、指摘していただいたような箇所は見当たりませんでした・・・。 少し自分でも混乱してきたので、一度整理してからもう一度チャレンジしてみたいと思います。 メソッドのネーミングについては今まで自分がわかればそれでいいというようにやってきたので、改めたいと思います。
- harawo
- ベストアンサー率58% (3742/6450)
回答No.2への「お礼」に対して: プログラムコードを見せろといったのは、たしかにこちらのほうですが、正直そんなことをいって後悔しています。みごとな「スパゲティコード」です。ただそれでも勉強させてもらったのは、たった1本のスパゲティでも、もつれさせることは可能だという事実です。 もつれたスパゲティをほぐす気は、はっきりいって私にはありません。その代わり、ちょっとはましな、(おそらく)おなじことをするコードを載せておきます。あなたの勉強の足しになれば、幸いです。 ※「@」が文中に含まれると、OKWaveはセキュリティに問題ありと判断するようなので、すべて全角に置き換えてあります。 ViewController.h -------- #import <UIKit/UIKit.h> @interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate> @property (weak, nonatomic) IBOutlet UIButton *hideButton; // Storyboardで配置。テーブルセル上のボタンを押すと、表示され、このボタンを押すと、自身を非表示にします。 @end ViewController.m --------- #import "ViewController.h" #import "MyTableViewCell.h" #define CELL @"Cell" @interface ViewController () @property (weak, nonatomic) IBOutlet UITableView *tableView; - (IBAction)hideMe:(id)sender; // hideButtonに組みこんだアクションメソッド @end @implementation ViewController { NSMutableArray *data; } - (void)viewDidLoad { [super viewDidLoad]; [self.tableView registerClass: [MyTableViewCell class] forCellReuseIdentifier: CELL]; data = [NSMutableArray arrayWithObjects: @"Angel", @"Brutus", @"Curry", @"Design", @"Empty", @"Friday", @"Gregory", nil]; } #pragma mark Table View Delegate and DataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return data.count; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { MyTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: CELL forIndexPath: indexPath]; cell.myLabel.text = data[indexPath.row]; cell.viewController = self; // MyTableViewCellのプロパティ「viewControllerに、自身を渡す。 return cell; } - (IBAction)hideMe:(UIButton *)sender { // 引数の型idをUIButton *に変更してあります。 sender.hidden = YES; // sender(hideButton)を非表示にする。 } @end MyTableViewCell.h -------- #import <UIKit/UIKit.h> #import "ViewController.h" @interface MyTableViewCell : UITableViewCell @property (nonatomic) UILabel *myLabel; @property (nonatomic) ViewController *viewController; // hideButtonのあるView Controller @end MyTableViewCell.m --------- #import "MyTableViewCell.h" @interface MyTableViewCell () // クラスエクステンションというやつ。 @property (nonatomic) UIButton *leftButton; // hideButtonを表示するボタン。 @end @implementation MyTableViewCell - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { // leftButtonの初期化 self.leftButton = [UIButton buttonWithType: UIButtonTypeSystem]; [self.leftButton setTitle: @"Show" forState: UIControlStateNormal]; self.leftButton.frame = CGRectMake(20.0, 8.0, 80.0, 24.0); [self.leftButton addTarget: self action: @selector(showButton:) forControlEvents: UIControlEventTouchUpInside]; [self.contentView addSubview: self.leftButton]; self.myLabel = [[UILabel alloc] initWithFrame: CGRectMake(120.0, 8.0, 160.0, 24.0)]; self.myLabel.text = @"label"; [self.contentView addSubview: self.myLabel]; } return self; } // leftButtonに組みこむアクションメソッド - (void)showButton: (id)sender { if (self.viewController != nil) { self.viewController.hideButton.hidden = NO; } } @end
お礼
お礼が遅くなりまして大変申し訳ありません。 ご親切にサンプルコードを載せていただき、ありがとうございます。 まだ解決までは至っておりませんが、こちらを参考にして解決を目指したいと思います。 コードの汚さについては、自分が今までネット上の知識を中心に「とりあえず動くものを」といった姿勢でアプリを作ってきたことが原因かと思います。 今後、わかりやすいコードを書くことも意識して取り組んで行きたいと思います。
- harawo
- ベストアンサー率58% (3742/6450)
> 原因として考えられることはなにがあるでしょうか。 原因の数は、星の数ほどあります。それを知っているだけ並べ立てろと、回答者に要求するなら、それはあなた、驕りすぎです。 あなたが書いたプログラムコードを提示して、「どこがおかしいですか?」とお尋ねになるのなら、回答者は、それなりにピンポイントで指摘することができるでしょう。 直接の原因でなく、方向性として間違っているからという、間接の原因なら、ボタンをaddSubview(addViewではない)、removeFromSuperviewをくり返すという、無意味なオブジェクトの生成と解放にあります。 あなたはただボタンを「置く」、「閉じる」という軽い感覚でそういう処理をしているようですが、コンピュータ(この場合は、iPhone/iPad)にとってみると、相応に重い処理です。とくにそれ自体頻繁に生成と解放をくり返すUITabelViewCell上のオブジェクトであるなら、なおさらです。 UIViewのプロパティ「hidden」で、非表示にする、表示するという繰り返しなら、オブジェクトの生成と解放を行っているわけではないので、コンピュータ側の負担は小さいはずです。それと、「宣言されていないセレクタ」なるもを呼ぶことも防げるでしょう。
お礼
ご回答ありがとうございます。 ご指摘の通り、質問の仕方があいまいで、また必要な情報もお見せしていませんでした。申し訳ありませんでした。 その上で図々しいかと思いますが、コードを載せますので解決のヒントをいただけないでしょうか。 どうかよろしくお願い致します。 //TableViewをセットしたViewController.m - (void)viewDidLoad { [super viewDidLoad]; _tableView.dataSource = self; _tableView.delegate = self; [_tableView registerNib:[UINib nibWithNibName:@"CustomCell" bundle:nil] forCellReuseIdentifier:@"Cell"]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 1; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"]; [cell pushBtn:self]; return cell; } -(void)addView:(id)sender{ CustomView *customView = [[CustomView alloc] init]; [self.view addSubview:customView]; } //TableViewに登録したCustomCell.m - (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier { self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]; if (self) { } return self; } - (void)awakeFromNib { } - (void)setSelected:(BOOL)selected animated:(BOOL)animated { [super setSelected:selected animated:animated]; } - (void)pushBtn:(id)mainView { [_myButton addTarget:mainView action:@selector(addView:) forControlEvents:UIControlEventTouchUpInside]; } No.1の方のご助言から、カスタムセルのボタンを2回目に押した際にaddViewが見つからない、ということはわかりました。 (エラーメッセージ:[UIButton addView:]: unrecognized selector sent to instance 0x8f6a910) しかし、なぜ1回目は動くのに2回目だとエラーが出るのか、またその修正の仕方がわかりません・・・。 色々といじってみましたが、addView内の内容は関係ないようでした。 (addSubviewを、教えていただいたhiddenや、単純なNSLog等に置き換えてみても同様のエラーが起きました。)
- Lchan0211b
- ベストアンサー率61% (573/930)
> unrecognized selector sent to instance エラーが出てしまいます。 このメッセージの直前(同じ行)に、どのクラスの 何と言うメソッドを呼び出そうとして失敗したのかが 書かれてあるはずです。呼び出そうとしたクラスに 指定メソッドが無いということです。 たいてい、思っていたものと別のクラスに対して メソッドを呼び出そうとしたケースが多いと思います。 設計ミスやコーディングミスでよくあるエラーです。 エラーメッセージ内容をちゃんと全部書いてないので、 それ以上はわかりません。 丁度、 http://www.raywenderlich.com/ja/50797/%E3%82%A2%E3%83%97%E3%83%AA%E3%81%8C%E3%82%AF%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5%E3%81%97%E3%81%A1%E3%82%83%E3%81%A3%E3%81%9F%E3%80%82%E3%81%95%E3%81%A6%E3%80%81%E3%81%A9%E3%81%86%E3%81%97%E3%81%BE に、unrecognized selector sent to instance が発生した時に どのようにデバッグすればいいか、初心者向けにとても丁寧に 解説されている記事がありましたので、前半だけでもしっかり読んでみる ことをお勧めします。
お礼
いつも詳しくありがとうございます。 教えていただいたページを読んでエラーの原因を探ったところ、カスタムセルにセットしたボタンを押した際に呼び出すはずのメソッドが見つからない、というところまではわかりました。 最初に押した時にはきちんと動くのに、2回目だとなぜエラーが出るのか、まだわかりませんが、、、ともかく少し解決に近づけました。 ありがとうございます。
お礼
またお礼が遅れてしまい申し訳ありません。 ご指摘のようにもう一度最初から作り直したところ、うまく動きました! 本当にありがとうございます! 結局、なにが原因でエラーが出ていたのかはわからず終いでしたが・・・ともあれ、これからもうまくいくはずなのに出来ないときには一からやり直してみる、というのをやってみようと思います。 改めてありがとうございました。