Twitterのアプリなどが採用していた引っ張って更新するためのコントロールは、UIRefreshControlとしてiOS6からiOS SDKに正式採用されています。ところが、このUIRefreshControl下へ引っ張って更新するのには対応していますが、上へ引っ張って更新するのには対応していません。

上へ引っ張って更新が欲しくなるのは、コンテンツが上から下に並んでいて、追加で読み込むコンテンツを一番下に追加したいような場合です。例えば2chビューワーなんかがそうですよね。

上へ引っ張って更新する操作には標準のUIRefreshControlが使えないので、自前で実装するかライブラリを使用するしか方法はありません。

自前で実装する

実はこれは試していません。先にライブラリ探しから始めてしまったので!

ざっと調べたところでは、UITableViewfooterViewを使うとかなんとか、そんなような感じでした。スクロールを検知して追加コンテンツを読み込み「引っ張って更新」のViewの位置を再調整それば良さそうでした。

ライブラリを使う

デファクトスタンダードなライブラリがどれかわかりませんでした。もしそういうのがあったら教えて欲しいです。

僕が使ったのはこれ。

pilot34/MNMBottomPullToRefresh

MNMBottomPullToRefreshの導入

READMEにはありませんが、CocoaPodsで導入可能です。

1# Podfile
2
3pod 'MNMBottomPullToRefresh'
1$ pod install

使い方

UITableViewを置いたUIViewControllerを例に実装を紹介。

ViewController.hMNMBottomPullToRefreshManagerClientを継承します。

1
2#import <MNMBottomPullToRefreshManager.h>
3
4@interface ViewController: UIViewController<MNMBottomPullToRefreshManagerClient, UITableViewDelegate, UITableViewDataSource>
5
6@property (weak, nonatomic) IBOutlet UITableView *tableView;
7
8@end

ViewController.mではちょっとした実装と、MNMBottomPullToRefreshManagerClientのメソッドの定義を行います。

  • ViewControllerのプロパティにMNMBottomPullToRefreshManagerを持つ
  • scrollViewDidScroll
  • scrollViewDidEndDragging
  • bottomPullToRefreshTriggered
 1@interface ViewController ()
 2
 3@property (nonatomic, strong) MNMBottomPullToRefreshManager* refreshManager;
 4@end
 5
 6
 7- (void)viewDidLoad
 8{
 9    [super viewDidLoad];
10
11    self.tableView.delegate = self;
12    self.tableView.dataSource = self;
13
14    // ここ
15    self.refreshManager = [[MNMBottomPullToRefreshManager alloc] initWithPullToRefreshViewHeight:60.0 tableView:self.tableView withClient:self];
16}
17
18
19# pragma mark - MNMBottomPullToRefreshManager
20- (void)scrollViewDidScroll:(UIScrollView *)scrollView
21{
22    [self.refreshManager tableViewScrolled];
23}
24
25- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
26{
27    [self.refreshManager tableViewReleased];
28}
29
30- (void)bottomPullToRefreshTriggered:(MNMBottomPullToRefreshManager *)manager {
31
32    [self performSelector:@selector(refresh) withObject:nil afterDelay:0.3f];
33}

最後のbottomPullToRefreshTriggeredで更新処理を実行します。ここではperformSelector: withObject: afterDelay:を用いて非同期実行しています。確かActivityIndicatorと同じく、そうしておかないと動かなかったような…。

そして更新メソッドの最後でself.refreshManagerに更新終了を通知します。

1- (void)refresh
2{
3    // データ更新
4
5    // 更新後に呼び出す
6    [self.refreshManager tableViewReloadFinished];
7}

このライブラリにはもうひとつ注意があって、引っ張って更新するためのViewをTableの一番下に相当する位置に配置しているのですが、行が増えるたびに再配置し直さないとおかしな場所に配置されてしまうことになります。

そこでviewDidLayoutSubviewsをオーバーライドします。

1
2- (void)viewDidLayoutSubviews
3{
4    [super viewDidLayoutSubviews];
5    [self.refreshManager relocatePullToRefreshView];
6}

また場合によってはviewDidLayoutSubviewsが呼ばれないケースがありました(どのようなケースで起こるのかはわかりっていません)。そういう場合はデータ更新後にlayoutIfNeededを呼んであげることで再レイアウトさせることができるようです。

 1- (void)refresh
 2{
 3    // データ更新 ...
 4
 5    // 再レイアウト
 6    [self.view layoutIfNeeded];
 7
 8
 9    // 更新後に呼び出す
10    [self.refreshManager tableViewReloadFinished];
11}