UIRefreshControl-UITableViewControllerがUINavigationController内にある場合、beginRefreshingが機能しない


120

UITableViewController(UINavigationController内にあります)にUIRefreshControlを設定しましたが、期待どおりに機能します(つまり、プルダウンで正しいイベントが発生します)。ただし、次のようにプログラムでbeginRefreshing更新コントロールのインスタンスメソッドを呼び出すと、

[self.refreshControl beginRefreshing];

何も起こりません。アニメートしてスピナーを表示します。endRefreshing私はリフレッシュした後、それを呼び出したときに方法が正しく動作します。

私はこの動作で基本的なプロトタイププロジェクトを作成しましたが、UITableViewControllerがアプリケーションデリゲートのルートビューコントローラーに直接追加された場合、正しく動作します。例:

self.viewController = tableViewController;
self.window.rootViewController = self.viewController;

しかし、tableViewController最初にUINavigationControllerにを追加し、次にナビゲーションコントローラーをとして追加するrootViewControllerと、beginRefreshingメソッドは機能しなくなります。例えば

UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:tableViewController];
self.viewController = navController;
self.window.rootViewController = self.viewController;

これは、ナビゲーションコントローラ内のネストされたビュー階層がリフレッシャコントロールでうまく機能していないことに関係していると思います-何か提案はありますか?

ありがとう

回答:


195

プログラムで更新を開始する場合、たとえばcontentoffsetを変更して、テーブルビューを自分でスクロールする必要があるようです

[self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];

これは、ユーザーがテーブルビューの中央/下部にいるときに更新コントロールにスクロールすることが望ましくない可能性があるためでしょうか。

@muhasturkによるSwift 2.2バージョン

self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)

一言で言えば、この移植性を維持するには、この拡張機能を追加

UIRefreshControl + ProgramaticallyBeginRefresh.swift

extension UIRefreshControl {
    func programaticallyBeginRefreshing(in tableView: UITableView) {
        beginRefreshing()
        let offsetPoint = CGPoint.init(x: 0, y: -frame.size.height)
        tableView.setContentOffset(offsetPoint, animated: true)        
    }
}

6
それは、必要に応じて調整するオフセットendRefreshing、私のテストでは、奇妙だ
ドミトリーシェフチェンコ

9
ところで、自動レイアウトを使用している場合は、回答の行を次のように置き換えることができます。[self.tableView setContentOffset:CGPointMake(0、-self.topLayoutGuide.length)animated:YES];
Eric Baker

4
@EricBaker私はそれはしないと信じています。すべてのUITableViewControllersにナビゲーションバーが表示されるわけではありません。これは、長さが20のtopLayoutGuideになり、オフセットが小さすぎます。
ファビオ・オリベイラ

3
使用できる@EricBaker:[self.tableView setContentOffset:CGPointMake(0、self.topLayoutGuide.length -self.refreshControl.frame.size.height)animated:YES];
ZYiOS 2014年

1
コピーペーストは私にとっては素晴らしいです。私はAppleのストーリーボードビルダーに飽きていました。
okysabeni 2015年

78

UITableViewControllerには、iOS 7以降、automaticAdjustsScrollViewInsetsプロパティがあります。テーブルビューには、通常(0、-64)のcontentOffsetがすでに含まれている場合があります。

したがって、プログラムで更新を開始した後にrefreshControlを表示する正しい方法は、refreshControlの高さを既存のcontentOffsetに追加することです。

 [self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];

こんにちは、ありがとうございました。デバッグ時に出会った魔法のポイント(0、-64)にも興味があります。
inix

2
@inix 20ステータスバーの高さ+ 44ナビゲーションバーの高さ
Igotit

1
代わり-64に使用する方が良い-self.topLayoutGuide.length
Diogo T

33

上記の戦略を使用したSwift拡張機能を次に示します。

extension UIRefreshControl {
    func beginRefreshingManually() {
        if let scrollView = superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: true)
        }
        beginRefreshing()
    }
}

UITableViewで動作します。
ファンボエロ16年

10
sendActionsForControlEvents(UIControlEvents.ValueChanged)この関数の最後に置くことをお勧めします。そうしないと、実際のリフレッシュロジックロジックが実行されません。
コリン・バスネット

これは、最もエレガントな方法です。機能向上のためのColin Basnettのコメントを含みます。一度定義すれば、プロジェクト全体で使用できます!
JoeGalind 2016

1
@ColinBasnett:そのコード(現在はsendActions(for:UIControlEvents.valueChanged))を追加すると、無限ループが発生します...
Kendall Helmstetter Gelner

15

他の答えはどれもうまくいきませんでした。それらはスピナーを表示して回転させますが、更新アクション自体は決して発生しません。これは機能します:

id target = self;
SEL selector = @selector(example);
// Assuming at some point prior to triggering the refresh, you call the following line:
[self.refreshControl addTarget:target action:selector forControlEvents:UIControlEventValueChanged];

// This line makes the spinner start spinning
[self.refreshControl beginRefreshing];
// This line makes the spinner visible by pushing the table view/collection view down
[self.tableView setContentOffset:CGPointMake(0, -1.0f * self.refreshControl.frame.size.height) animated:YES];
// This line is what actually triggers the refresh action/selector
[self.refreshControl sendActionsForControlEvents:UIControlEventValueChanged];

この例ではテーブルビューを使用していますが、コレクションビューでもかまいません。


これは私の問題を解決しました。しかし、今私はを使用SVPullToRefreshしています、それをプログラムでプルダウンする方法は?
2014

1
@Gank私はSVPullToRefreshを使用したことがありません。彼らのドキュメントを読んでみましたか?ドキュメントに基づいてプルダウンできることは非常に明白です:「プログラムで更新をトリガーする場合(たとえば、viewDidAppear:で)、次のようにして更新できます: [tableView triggerPullToRefresh];参照:github.com/samvermette/ SVPullToRefresh
カイル・ロブソン

1
パーフェクト!アニメーションでは奇妙な動作だったので、単に置き換えましたscrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height)
user1366265

13

すでに述べたアプローチ:

[self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];

スピナーが見えるようになります。しかし、アニメーションにはなりません。私が変更したのは、これら2つの方法の順序とすべてが機能することです。

[self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
[self.refreshControl beginRefreshing];

11

Swift 4 / 4.1の場合

既存の答えの組み合わせが私のために仕事をします:

refreshControl.beginRefreshing()
tableView.setContentOffset(CGPoint(x: 0, y: tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)

お役に立てれば!


7

この質問も参照してください

beginRefreshingを呼び出し、contentOffsetが0の場合、UIRefreshControlがとげのある表示をしない

tableViewのcontentOffsetプロパティが0の場合にのみ発生するため、私にはバグのように見えます

次のコード(UITableViewControllerのメソッド)で修正しました。

- (void)beginRefreshingTableView {

    [self.refreshControl beginRefreshing];

    if (self.tableView.contentOffset.y == 0) {

        [UIView animateWithDuration:0.25 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^(void){

            self.tableView.contentOffset = CGPointMake(0, -self.refreshControl.frame.size.height);

        } completion:^(BOOL finished){

        }];

    }
}

1
これは真実ではありません。2つの異なるUIViewControllerがあり、どちらもviewDidLoadでcontentOffsetが0であり、1つは[self.refreshControl beginRefreshing]を呼び出すときにrefreshControlを正しくプルダウンし、もう1つはそうしません:/
simonthumper

ドキュメンテーションは、コントロールをに表示することについて何も述べておらずbeginRefreshing、その状態が変化するだけです。私が見るように、更新アクションを2回開始するのを防ぐためです。プログラムで呼び出された更新がまだ実行されている場合でも、ユーザーが開始したアクションは別のアクションを開始しません。
公園。

setContentOffset:animatedメソッドの使用に関する問題を指摘したので、この解決策がうまくいきました。
Marc Etcheverry

7

スピナーを表示するだけでなく、アニメーション化するSwift 3以降の拡張機能を次に示します。

import UIKit
extension UIRefreshControl {

func beginRefreshingWithAnimation() {

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {

        if let scrollView = self.superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - self.frame.height), animated: true)
          }
        self.beginRefreshing()
      }
   }
}

2
上記のすべての回答の中で、これがiOS 11.1 / xcode 9.1で作業できる唯一の回答でした
Bassebus

1
これasyncAfterが実際にスピナーアニメーションを機能させるものです(iOS 12.3 / Xcode 10.2)
francybiga

4

それは私にとって完璧な作品です:

スウィフト3:

self.tableView.setContentOffset(CGPoint(x: 0, y: -self.refreshControl!.frame.size.height - self.topLayoutGuide.length), animated: true)

4

Swift 5の場合、私にとって欠けているのはを呼び出すことだけでしたrefreshControl.sendActions(.valueChanged)。よりクリーンにするために拡張を行いました。

extension UIRefreshControl {

    func beginRefreshingManually() {
        if let scrollView = superview as? UIScrollView {
            scrollView.setContentOffset(CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height), animated: false)
        }
        beginRefreshing()
        sendActions(for: .valueChanged)
    }

}

3

@Dymitry Shevchenkoソリューションに加えて。

この問題の回避策を見つけました。UIRefreshControlその上書きメソッドの拡張を作成できます。

// Adds code forgotten by Apple, that changes content offset of parent scroll view (table view).
- (void)beginRefreshing
{
    [super beginRefreshing];

    if ([self.superview isKindOfClass:[UIScrollView class]]) {
        UIScrollView *view = (UIScrollView *)self.superview;
        [view setContentOffset:CGPointMake(0, view.contentOffset.y - self.frame.size.height) animated:YES];
    }
}

Identity Builderでカスタムクラスを設定して新しいクラスを使用し、Interface Builderで更新を制御できます。


3

フォートスイフト2.2以降

    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)

これは単なる更新なので、これを承認済みの回答とマージしました。
チューダー

3

Rxswift for swift 3.1を使用している場合は、以下を使用できます。

func manualRefresh() {
    if let refreshControl = self.tableView.refreshControl {
        self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.height), animated: true)
        self.tableView.refreshControl?.beginRefreshing()
        self.tableView.refreshControl?.sendActions(for: .valueChanged)
    }
}

これは、Swift 3.1、iOS 10で動作します。


1
誰かが最初に見て疑問に思っている場合に関連してこの回答を作成するsendActionsためのそのトリガーrxRxSwift
カーボン

tableViewではなく、tableViewControllerのrefreshControlインスタンスを使用する必要がありました。また、そのsetContentOffsetは、iOS10をターゲットにした場合は機能しませんでした。この1しかし作品: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true)
nmdias

1

Swift 5でテスト済み

これを使う viewDidLoad()

fileprivate func showRefreshLoader() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) {
        self.tableView.setContentOffset(CGPoint(x: 0, y: self.tableView.contentOffset.y - (self.refreshControl.frame.size.height)), animated: true)
        self.refreshControl.beginRefreshing() 
    }
}

0

私は、ユーザーに「データは更新」という視覚的標識を表示する場合と同じ手法を使用します。ユーザーがアプリをバックグラウンドから持ってくると、ユーザーがテーブルをプルして自分を更新するように、フィード/リストがUIで更新されます。私のバージョンには3つのものが含まれています

1)「ウェイクアップ」を送信するユーザー

- (void)applicationDidBecomeActive:(UIApplication *)application {
    [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationHaveToResetAllPages object:nil];
}

2)UIViewControllerのオブザーバー

- (void)viewDidLoad {
    [super viewDidLoad];

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(forceUpdateData) name:kNotificationHaveToWakeUp:nil];
}

3)プロトコル

#pragma mark - ForcedDataUpdateProtocol

- (void)forceUpdateData {
    self.tableView.contentOffset = CGPointZero;

    if (self.refreshControl) {
        [self.refreshControl beginRefreshing];
        [self.tableView setContentOffset:CGPointMake(0, -self.refreshControl.frame.size.height) animated:YES];
        [self.refreshControl performSelector:@selector(endRefreshing) withObject:nil afterDelay:1];
    }
}

結果

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.