SBPullToRefreshHeaderView - 「引っ張って更新」を再実装してみる
- 2012.02.25 Saturday
- dev
HMDT さんから開発者向け電子書籍がリリースされています ([本] HMDT JOURNAL 創刊 | Cocoaの日々情報局 経由)。
早速、Vol.001 と Vol.002 を購入してみました。木下さんらしい読みやすい文体で、かなり丁寧に書かれています。
……特定の連載記事だけをバラで購入できたら嬉しいなぁ……なんて。ともあれ、今後の展開が楽しみです。
EGOTableViewPullRefresh
さて、この HMDT JOURNAL Vol.001 で紹介されている EGOTableViewPullRefresh
は、いわゆる「引っ張って更新」を実現するクラスで、Facebook 謹製のライブラリでも採用されていたりする有名なライブラリです。
組み込みも簡単で、コードも分かりやすく記述されているライブラリなのですが、導入にあたってコントローラ側で UIScrollViewDelegate
のメッセージをフォワードする必要がある点がちょっと不満。
例えば、UIButton
ではボタンのハイライトのためにタッチイベントをコントローラ側からフォワードしなくても、ボタン側で勝手に処理してくれるように、EGOTableViewPullRefresh
もスクロールイベントを勝手に処理してくれたらなぁと思うわけです。
SBPullToRefreshHeaderView
- yet another "pull-to-refresh"
……そこで自分で「引っ張って更新」するクラス SBPullToRefreshHeaderView
を自作してみました。
ARC 対応で __weak
修飾子を使っているので、iOS5 以降でお使いいただけます。
他に EGOTableViewPullRefresh
から微妙に変わっている部分に「離して更新」の時にリロード用画像を使っています。OS X 向け Twitter アプリケーションのような感じ。
サンプルデモのコードにある通り、コントローラ側では SBPullToRefreshHeaderView
の初期化メソッドにターゲットとなるスクロールビューを渡すと、あとは SBPullToRefreshHeaderView
側でよしなにしてくれます……はずです。
mRefreshHeaderView = [[SBPullToRefreshHeaderView alloc] initOnScrollView:self.tableView withDelegate:self];
デリゲート SBPullToRefreshHeaderViewDelegate
のメソッドはふたつ。
@protocol SBPullToRefreshHeaderViewDelegate <NSObject> /// 「引っ張り更新」の発動 (ユーザが更新するためにスクロールビューを離した) を通知します。 /// @param headerView - 関連する SBPullToRefreshHeaderView のインスタンス - (void)didTriggerRefresh:(SBPullToRefreshHeaderView *)headerView; /// ターゲットが「ローディング」状態がどうかを確認します。 /// @param headerView - 関連する SBPullToRefreshHeaderView のインスタンス /// @return ローディング状態にある場合、YES をそうでない場合 NO を返します - (BOOL)isRefreshStillProcessing:(SBPullToRefreshHeaderView *)headerView; @end
コントローラ側では基本的にはこのふたつのデリゲートメソッドを実装して、ローディングが終了した後に「resetView:
」メソッドを送信します。
SBPullToRefreshHeaderView
の実装
SBPullToRefreshHeaderView
ではスクロールイベントを処理するために KVO (Key-Value Observing) を使っています。
スクロールが発生すると、UIScrollView
の contentOffset
が変化することに着目して、そこでスクロール時に処理する内容を記述しています。
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (object == mScrollView && [keyPath isEqual:@"contentOffset"]) { [self scrollViewDidScroll:mScrollView]; if (mIsDragging != mScrollView.isDragging) { if (!mScrollView.isDragging) { [self stopDragging]; } mIsDragging = mScrollView.isDragging; } } }
ユーザーがターゲットのスクロールビューから指を離したかどうかの判定もここで行っています。スクロールの変化で判定しているので、実際のタッチリリースとは異なるタイミングになりますが、動作させてみた感じ特に違和はない感じ。
オブザーブを開始・終了するタイミングが少しハック的な実装になっています。
……というのもオブザーブを開始した時点で参照カウンタがインクリメントされてしまうため、どこか適当なタイミングでオブザーブを終了してあげないといつまで経ってもオブジェクトが解放されなくなってしまいます。
SBPullToRefreshHeaderView
の実装では、ターゲットとなっているスクロールビューの子ビューとして追加されたときにオブザーブを開始、親ビューがターゲットから外れた時点でオブザーブを終了するようにします。
- (void)didMoveToSuperview { if (self.superview == mScrollView) { // The view has been added into the target scroll view so it starts // observing contentOffset changes. [mScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil]; } else { // The view has been removed from the target scroll view so it stops // observing contentOffset changes. [mScrollView removeObserver:self forKeyPath:@"contentOffset"]; } }
UIView
の 「didMoveToSuperview
」メソッドで親ビューの変化をキャッチできるので、そこでオブザーブの開始・終了を制御しています。
GitHub にサンプルデモと一緒に置いてみましたので、もしよろしければお使いください (MIT-License)。
スポンサーリンク