Study CoreData 20 ~見えないモノこそ価値がある~

みなさん、楽しく開発してますかぁ〜!

この連続投稿企画もついに20回を超えました!!
なんとか、こうしてココまでやって来れたのも
いつも見てくれているみなさんのおかげです!
 
 
なかなか、コメントを頂けなくてもPVをみればどれだけの人に見て頂けているのかは何となく分かるので、本当にありがたい限りです<(_ _)>

ホントにありがとでっす★


注意:投稿者自身もCoraDataについて勉強中のため、このシリーズには誤りが含まれている可能性があります。もし、間違いに気付かれた方はコメント欄もしくはtwitterなどでご指摘いただけると幸いです<(_ _)>
また、開発環境はXcode3.2.3 iPhone SDK 4です。実機でのテストなどは自己責任でお願いいたします。

 
 
前回でこのシリーズの最大のヤマ場は超えた感じですので、あとは完成に向けて邁進していくとしましょうか。
(ただし、今回はCoreDataに関する内容はホンのちょこっとだけです^^;)
 

1. 愚者は経験に学び、賢者は歴史に学ぶ

第0回のデモ動画を見て頂いた方は分かると思うのですが、dotodoの検索バーのスタイルはiPhone標準アプリである『メモ』アプリを真似たスタイルになっています。

なにがしかのアプリを作る際に、同種/異種問わず“良質なアプリ”の実装を学ぶ事は、一から自分で考えるよりも
ずっと効率的で意味がある事だと思ってます。
 
 

まぁ、簡単に言えばパクっちゃえ!!ってことで(^〜^;)
(言うまでもなく、ライセンスがあるものやその利用を許可していないコードなどは別×)

 
ただ、どうせパクるなら中途半端はいけません。
まず、徹底的にその対象を知り尽くすってことも大事なポイントだと思います。
 
 
例えば今回のメモアプリなら…

テーブルビューをドラッグすると検索バーが引っ張られるように出てきますね。
これは誰でも気付くと思います。

で、さらに画像右のように検索バーが中途半端に見えている位置でドラッグをやめてみるとどうなるか?
みなさんもやってみてくださいね。

すると…検索バーが完全に表示/非表示になるまでスーっと自動的にスクロールしてくれます。

他には、検索バーを表示した状態でセルを選択して、戻ってきた時には検索バーはどうなるのか?とか。
 
 
こうして色々と調べてみると、ただ単純に使っているだけでは気付かなかった細かな実装が見えてきます。
(僕は『TapDays』開発の際に標準のCalender.appを徹底的に調べ倒しましたが、かなり多くのものを得ることができました。
ですが「Calender.appの実装をそのまま作りました」的な他社製ライブラリではその1/3くらいしか実装していなかったなんてことも…)

 
 

2. 学習を生かす。

では、今調べた事を実装していきましょう!
まずは、検索バーの表示や動きの部分を作っていきます。

まず、以下のようにデリゲートやプロパティなどを宣言します。

BOOL値として用意した[_searchBerIsVisible]は検索バーの表示/非表示状態を把握しておくため。
[_isSearching]は検索中かどうかを判定するために使います。

で、[_searchResults]は検索結果を保持するデータソースになります。
 
 
お次ぎにsynthesizeして…

RootViewControllerの初期化メソッドをオーバーライドします。

 
#define HEIGHT_HEADER 45.f

#pragma mark -
#pragma mark initiarizer
- (id)initWithStyle:(UITableViewStyle)style {
	if (self = [super initWithStyle:style]) {
		
		_searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0.f, 0.f, self.view.bounds.size.width, HEIGHT_HEADER)];
		[_searchBar sizeToFit];
		
		_searchBar.delegate = self;
		_searchBar.placeholder = @"Search Todo";

		self.tableView.tableHeaderView = _searchBar;
		[_searchBar release];
		
		// UISearchDisplayControllerを初期化した段階で自動的に<self.searchDisplayController>に入れられる。
		_searchDisplay = [[UISearchDisplayController alloc] initWithSearchBar:_searchBar contentsController:self];  
		_searchDisplay.delegate = self;
		_searchDisplay.searchResultsDelegate = self;
		_searchDisplay.searchResultsDataSource = self;
		
		_searchBarIsVisible = YES;
	}
	return self;
}

(※[HEIGHT_HEADER]として検索バーのheightを#defineしてます。)

この初期化メソッド内では、サーチバーをtableHeaderとして表示させて
検索結果を表示するための[UISearchDisplayController]にその検索バーをセットしています。
 
 
なぜこれらを[loadView]などではなく初期化メソッド内に書いているのか?
と言うと、
“self.searchDisplayController = _searchDisplay”
と認識させるためです。

UIViewControllerには“self.searchDisplayController”というプロパティがあるのですが、これはreadOnlyなプロパティなので
“self.searchDisplayController = [[UISearchDisplayController alloc] init…”
といった形で明示的にセットすることはできません。
 
ですが、初期化メソッド内で”UISearchDisplayController”を初期化することで、自動的に”self.searchDisplayController”として認識されるようになるみたいです。
 
 
そしたら、このままだとRootViewが表示された時から検索バーが見えてしまうので[viewWillApper]をオーバーライドしときます。

 
- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
	
	_isSearching = [self.searchDisplayController isActive];
	
	if (!_isSearching) {
		if (_searchBarIsVisible) {	// SearchBarを隠す
			
			CGRect frame = self.view.frame;
			frame.origin.y += HEIGHT_HEADER;
			
			[self.tableView scrollRectToVisible:frame animated:NO];
			_searchBarIsVisible = NO;
		}
		
		[self.tableView reloadData];
	}
	else {
		[self.searchDisplayController.searchResultsTableView reloadData];
	}
}

ここでは、まず[isActive]を使って検索中かどうか?を判定してます。

で、検索中でないならサーチバーを隠して今までのようにテーブルをリロード。
サーチバーを隠すには、UITableViewの親クラスであるUIScrollViewの[scrollRectToVisible: animated:]に表示したいRectを渡せばOKです。

あと検索中なら、テーブルビューの代わりに[searchResultsTableView]をリロードします。
 
 
それじゃ、一旦ビルドして確認してみましょうか?
 

3. Taskエンティティがない!?

また、エラーですねぇ…(T ^ T;)
なんか『Taskエンティティがないぞぇ!』って言われてるみたいですが、ワケが分かりません…

 
 
んで、
よくよく調べてみると[self.managedObjectContext]がnilになっちゃってます。
どーやら、コイツが原因のようですね。

ちょっと理由は分からないのですが、初期化メソッドを追加すると
AppDelegateでRootVIewControllerにコンテキストをセットするより先に[loadView]が呼ばれてしまうみたいです。

なので、AppDelegateからコンテキストを渡すのをやめて
RootViewController側からそのコンテキストを取りにいくことにします。

[DoTodoAppDelegate.m]

このようにハイライト部分を削除して
 

[RootViewController.m]

上のように[self.managedObjectContext]が呼ばれたら、appDelegateからコンテキストを取ってくるようにします。
(“DoTodoAppDelegate.h”もインポートしてくださいね。)
 
 
そしたら、一番初めにコンテクストを使用する[loadView]で[self.managedObjectContext]を呼ぶように変更。

 
これで大丈夫なはずっ!
もっかいビルドしてみましょ。
 
 

4. コロンブスの卵

今度はちゃんとビルドされましたね^^;

まぁ、このままでもいいっちゃぁいいんですが
せっかくですのでもう少しこだわってメモアプリに近付けましょう。
 
 
まずは、検索バーを上部に固定する実装です。

こういったある種ギミック的な実装をするには、
何よりも『発想の転換』が大切だと思います。
つまり、普通に見えている視点とは違う視点から対象を見てみるってこと。

今回の場合なら、見えるままに
『検索バーを上部に固定するにはどうしたらいいんだろう?』
と考えると難しく、実現できても複雑な実装になりがちだと思います。
 
 
それを…

『どうしたら上部に固定されているように”見える”だろうか?』
と考えると、ずっと簡単でシンプルな解決法が見つかります。

 
 
そう!
固定しなくても固定しているように見えれば、結果は同じなんです!

つまり、テーブルビューがプルダウンされた分だけ
検索バーの位置を上へ移動してやればいいだけ
です。
 
 

5. 親クラスのメソッドを使う。

で、それらを実現するには
親クラスであるUIScrollViewControllerのメソッドを使います。

スクロールに関するメソッドには、こんなモノがあります。

 
// ドラッグの開始
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView;

// スクロール中
- (void)scrollViewDidScroll:(UIScrollView *)scrollView;

// ドラッグ終了
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate;

// 慣性スクロール終了
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView;

// スクロールアニメーションが完全に終了
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView

そして、今回はこのうちの[scrollViewWillBeginDragging:]以外の4つのメソッドを使います。
 
 
まずは『検索バーを固定しているように見せる』でしたね。
(※以下に載せるコード内の細かい数字(2.fなど)は、調整済みの数字です。)

 
#pragma mark -
#pragma mark Pull Down SearchBar (UIScrollView Delegate Methods)
// スクロール中
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
	
	if (!_isSearching) {
		float offsetY = scrollView.contentOffset.y;

		if (offsetY < 2.f && offsetY < HEIGHT_HEADER) {			// SearchBarの位置を上部に固定
			
			CGRect viewFrame = _searchBar.frame;
			viewFrame.origin.y = scrollView.contentOffset.y;
			_searchBar.frame = viewFrame;
		}
		

		if (offsetY <= 0) { _searchBarIsVisible = YES; }		// ドラッグ中の表示/非表示状態を判定
		if (offsetY >= HEIGHT_HEADER) { _searchBarIsVisible = NO; }
	}
}

このメソッドではまず、
上下にスクロールした分だけ検索バーの位置を反対側に上下させています。
ちなみに[offsetY]の値は、
検索バーがぴったり表示されている場合が”0.f”、
ちょうど非表示になっている場合が”44.f”になってます。

それから、
指を離さないまま下にドラッグした後に上にドラッグしたりされた場合には、
ドラッグ中にも表示/非表示状態が変わるので、それを判定させてあげてます。
 
 
で、お次はドラッグ終了時のスクロールです。

 
// ドラッグ終了
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {

	if (!_isSearching && !decelerate) {
		float offsetY = scrollView.contentOffset.y;
		
		if (offsetY > 0.f && offsetY < HEIGHT_HEADER) {			// 中途半端な位置でドラッグが止められた場合
																// SerchBarが表示/非表示になるようにスクロール
			CGRect frame = self.view.frame;
			if (_searchBarIsVisible)
				frame.origin.y += HEIGHT_HEADER;
			
			[self.tableView scrollRectToVisible:frame animated:YES];
		}
	}
}

// 慣性スクロール終了 スクロールが終わった位置でSearchBarの表示/非表示状態を判定
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
	
	if (!_isSearching) {
		float offsetY = scrollView.contentOffset.y;
		
		if (offsetY > 0.f && offsetY < HEIGHT_HEADER - 1.f) {		// 中途半端な位置でスクロールが止まった場合
																	// SerchBarが表示/非表示になるようにスクロール
			CGRect frame = self.view.frame;
			if (_searchBarIsVisible)
				frame.origin.y += HEIGHT_HEADER;
			
			[self.tableView scrollRectToVisible:frame animated:YES];
		}
	}
}

[DidEndDragging:]はドラッグ終了時に呼ばれ、[DidEndDecelerating:]はドラッグが終わったあとの余韻的スクロールが終わった時に呼ばれます。

この2つのメソッドでは共に、
検索バーが中途半端に見えている位置で止まろうとした場合のみ
完全に表示/非表示になる位置までスクロールさせています。
[(BOOL)decelerate]は、慣性スクロールが実行されるかどうか?ですね。
(ハイライトされている部分以外は全く同じです。)
 
 
で、完全にスクロールが終了した時点で、再度Visible状態を判定します。

 
// スクロールアニメーション終了([scrollViewDidEndDragging:]でのスクロール後)
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
	if (!_isSearching)
		_searchBarIsVisible = (scrollView.contentOffset.y <= 0) ? YES : NO;
}

 
 
これで完璧っっ
…と言いたいところですが、もうひとつ。

実はこれだけだと、セルが追加/削除された時に隠れたはずの検索バーが勝手に出てきたりしてしまうんです。

なので、それを防ぐためにセルが追加/削除された後に呼ばれる[controllerDidChangeContent:]の一番最後に以下を加えます。

ここでやっている事は2つで
ひとつは検索バーが隠れているならセルが増減しても隠れた状態を保つこと。
もうひとつはセルが削除されてセルが0(データも0)になった場合の処理です。
この場合には、データが0なら検索バーを表示しておく必要はないのでアニメーション付きで隠します。
 
 
さ、これで検索バーの表示とスクロールに対応する実装は完了です!

さっそくビルドして遊んでみましょ〜♬
 
 

6. 単なるギミックじゃないっ!

みなさんもうまくいきましたかね〜?

今回の実装は一見『単なるギミック』にも思えますが、
実はすごく理にかなった実装方法だと思います。
 

なぜかと言うと、
まず第一に”検索ボタン”を設置せずに済みます。
つまり、ボタンの数を減らせるってコトです。
 
 
第二に、通常では検索バーを非表示にしておく事で
一度に見えるリストの数がひとつ増えます。
つまりリストがちょっとだけでも見やすくなるってコト。
 
 

そして最大のポイントは、
遅くとも、検索の必要があるほどデータが多くなってきた時点で
ユーザーはほぼ必ずこの隠された検索バーの存在に気付く。

ってコトです!

 
 
こう考えてみると
『さすが、Apple純正アプリは考え込まれてるなぁ』
なんて思っちゃうんですよね。
 
 
自分のアプリでも、こんな風に理にかなったギミック(?)ならどんどん入れていきたいなぁって(^〜^)
 

ってなワケで、ほぼCoreDataとは関係のない内容ばかりでしたが^^;
明日はちゃんと検索できるようにしていきますので、お楽しみにっ!

ばいちゃ。
 

広告

Study CoreData 20 ~見えないモノこそ価値がある~」への1件のフィードバック

  1. ピンバック: [夕刊] Simcity Deluxe が出た!ルナスケープが出る!iPod touchに3G接続可能にするケース?!

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中