LoginSignup
58
50

More than 5 years have passed since last update.

UIRefreshControl の罠

Last updated at Posted at 2014-10-23

UIRefreshControl は罠がいくつかあるので、使い方を含めて対策をまとめてみました。

UIRefreshControl の使い方

UITableViewController のセットアップ時に UIRefreshControl を生成し、ハンドラを設定した上で self.refreshControl に入れてあげます。

MyTableViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];

    // Refresh Control のインスタンス化
    UIRefreshControl* refreshControl = [UIRefreshControl new];
    // ユーザが Pull to refresh したときのハンドラを設定
    [refreshControl addTarget:self
                       action:@selector(refreshControlStateChanged:)
             forControlEvents:UIControlEventValueChanged];
    // TableViewController に追加
    self.refreshControl = refreshControl;
}

ハンドラはユーザが Pull to refresh を行ったときに呼び出されます。
一般的にはこのとき TableView の更新処理を行い、これが完了したら [self.refreshControl endRefreshing]; を呼び出せば、表示中の Refresh Control が消失します。
お試し用のダミーコードはこんな感じです。

MyTableViewController.m
- (void)refreshControlStateChanged:(id)sender
    // 3 秒待ってからハンドリングを行う、URL リクエストとレスポンスに似せたダミーコード
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC));
    dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

        // モデルの更新などのレスポンス処理...

        // UI 更新
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.tableView reloadData];
            [self.refreshControl endRefreshing];
        });
    });
}

実は Collection View でも使える

UICollectionViewController には refreshControl プロパティがありませんが、下記のようにすれば Refresh Control を挿入することができます。

self.collectionView.backgroundView = refreshControl;

罠 1: endRefresing してもきれいに引っ込まないことがある

Pull to Refresh 後、以下の状態で endRefreshing を呼んでも、contentOffset が元に戻らないという問題があります。

  • TableView に表示する項目数が、スクロールが発生するほど存在する。
  • endRefreshing される前に、Refresh Control が一部隠れるくらいスクロールした状態にしておく。

ss1.png

この問題を解消するには、endRefreshing する際に明示的に contentOffset を調整します。
ただこの contentOffset の値は (0, 0) とは限らないので、

  • viewDidAppear などのタイミングでデフォルトの contentOffset を取得しておく。
  • ステータスバーとナビゲーションバーの高さから算出しておく。

のような方法で求める必要があります。
あとはこの値を設定してあげるだけなのですが、contentOffset プロパティを書き換えるのではなく setContentOffset:animated: メソッドを使ってアニメーションさせるのもポイントです。

// defaultContentOffset は上記の方法などであらかじめ求めておく
[self.tableView setContentOffset:self.defaultContentOffset animated:YES];

[self.tableView reloadData];
[self.refreshControl endRefreshing];

でもユーザが下のほうにスクロールしていたら、これだと無理矢理一番上へ戻されてしまわない?と思うかもしれませんが、なぜか戻されない...謎です。
(iOS 7.1 〜 8.1 では問題ないことを確認しています)

罠 2: attributedTitle を設定するタイミングによって表示がおかしくなる

Refresh Control には attributedTitle というプロパティがあり、インジケーターの下に任意のテキストを表示することができます。
しかしこのプロパティにテキストを設定すると、タイミングによって Table View の表示がおかしくなることがあります。

attributeTitle を使って最終更新日時を表示する場合を考えます。
まず、処理が完了した時点で設定することには全く問題ありません。

[self.tableView setContentOffset:self.defaultContentOffset animated:YES];
[self.tableView reloadData];

// 最終更新日時を設定
NSAttributedString* lastUpdatedDateTime = ...;
self.refreshControl.attributedTitle = lastUpdatedDateTime;

[self.refreshControl endRefreshing];

次にこの画面が初めて表示されたときでも、前回の最終更新日時を表示するようにしてみましょう。

MyTableViewController.m
- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];

    NSAttributedString* lastUpdatedDateTime = ...;
    self.refreshControl.attributedTitle = lastUpdatedDateTime;
}

しかしこの状態で Pull to refresh を行おうとすると...こんな残念なことに!
バウンスもなんだかおかしなことになっています。

ss2.png

これをなんとかするには、「Refresh Control を ViewController の view 配下に置く前に、attributedTitle に空でないテキストを入れておく」必要があります。
(これを前もって行うことで、attributedTitle を含めたレイアウトの調整が行われると考えられる)

MyTableViewController.m
- (void)viewDidLoad {
    [super viewDidLoad];

    UIRefreshControl* refreshControl = [UIRefreshControl new];
    [refreshControl addTarget:self action:@selector(refreshControlEventChanged:) forControlEvents:UIControlEventValueChanged];

    // attributedTitle にテキストを入れておく
    // self.refreshControl にセットする前に行うこと!
    refreshControl.attributedTitle = [[NSAttributedString alloc] initWithString:@" " attributes:nil];

    self.refreshControl = refreshControl;
}

対症療法的な解決でいまいちすっきりしませんが、もし同じ問題でお困りの方の一助になれば幸いです。

58
50
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
50