UIRefreshControl - beginRefreshing не работает, когда UITableViewController находится внутри UINavigationController

Я установил UIRefreshControl в моем UITableViewController (который находится внутри UINavigationController), и он работает, как ожидалось (т.е. при спуске срабатывает правильное событие). Однако, если я программно вызываю метод экземпляра 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;

Я чувствую, что это как-то связано с иерархиями вложенных представлений в контроллере навигации, которые не очень хорошо сочетаются с элементом управления обновлением - какие-либо предложения?

Спасибо


person wows    schedule 05.02.2013    source источник


Ответы (15)


Похоже, если вы начнете обновляться программно, вам придется самостоятельно прокручивать представление таблицы, например, изменяя contentoffset

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

Я бы предположил, что причина этого в том, что было бы нежелательно прокручивать до элемента управления обновлением, когда пользователь находится в середине / внизу представления таблицы?

Версия Swift 2.2 от @muhasturk

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

Вкратце, чтобы сохранить переносимость, добавьте это расширение

UIRefreshControl + ProgramaticBeginRefresh.swift

extension UIRefreshControl {
    func programaticallyBeginRefreshing(in tableView: UITableView) {
        beginRefreshing()
        let offsetPoint = CGPoint.init(x: 0, y: -frame.size.height)
        tableView.setContentOffset(offsetPoint, animated: true)        
    }
}
person Dmitry Shevchenko    schedule 06.02.2013
comment
Спасибо - добились желаемого эффекта! Мне пришлось снова установить смещение содержимого на 0, 0 после завершения обновления. - person wows; 06.02.2013
comment
Странно, в моих тестах endRefreshing подстраивает смещение по мере необходимости - person Dmitry Shevchenko; 06.02.2013
comment
Иногда так и бывало, а другие нет. Может быть связано с проблемой UINavigationController? Не уверен: S - person wows; 06.02.2013
comment
Кстати, если вы используете автоматический макет, вы можете заменить строку в ответе на это: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) animated: YES]; - person Eric Baker; 30.10.2013
comment
@EricBaker: Думаю, так не пойдет. Не все UITableViewController показывают панели навигации. Это приведет к тому, что topLayoutGuide будет иметь длину 20 и смещение будет слишком маленьким. - person Fábio Oliveira; 17.02.2014
comment
@EricBaker, вы можете использовать: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) animated: YES]; - person ZYiOS; 30.10.2014
comment
Копипаст у меня работает, и это здорово. Я устал от компоновщика раскадровки Apple. - person okysabeni; 18.03.2015
comment
Мне пришлось вызвать beginRefreshing () в viewDidAppear вместо viewDidLoad, иначе был виден только заголовок, а не вращающаяся анимация. - person Jadamec; 10.11.2016
comment
Это должно решить проблему - (недействительно) viewDidLoad {[super viewDidLoad]; dispatch_async (dispatch_get_main_queue (), ^ {[refreshControl beginRefreshing];}); } - person Bilal; 21.11.2016
comment
Просто общее мнение ... Это нужно радаром, если это не было исправлено в 11. Это похоже на недосмотр. - person Dru Freeman; 18.07.2017
comment
Измените beginRefreshing () на последнюю строку, чтобы исправить проблему с tintColor, не соблюдаемым при первом показе: stackoverflow.com/a/20383030/860488 < / а> - person Morten Holmgaard; 21.03.2019

UITableViewController имеет свойство automaticAdjustsScrollViewInsets после iOS 7. Табличное представление может уже иметь contentOffset, обычно (0, -64).

Итак, правильный способ показать refreshControl после начала обновления программирования - это добавить высоту refreshControl к существующему contentOffset.

 [self.refreshControl beginRefreshing];
 [self.tableView setContentOffset:CGPointMake(0, self.tableView.contentOffset.y-self.refreshControl.frame.size.height) animated:YES];
person Igotit    schedule 18.03.2014
comment
привет, Большое спасибо, мне также интересно узнать о волшебной точке (0, -64), которую я встретил при отладке. - person inix; 21.04.2015
comment
@inix 20 для высоты строки состояния + 44 для высоты панели навигации - person Igotit; 24.04.2015
comment
Вместо -64 лучше использовать -self.topLayoutGuide.length - person Diogo T; 24.11.2015

Вот расширение 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()
    }
}
person Chris Wagner    schedule 17.02.2016
comment
Работает для UITableView. - person Juan Boero; 02.03.2016
comment
Я бы порекомендовал поставить sendActionsForControlEvents(UIControlEvents.ValueChanged) в конце этой функции, иначе фактическая логика обновления не будет запущена. - person Colin Basnett; 27.06.2016
comment
Это, безусловно, самый элегантный способ сделать это. Включая комментарий Колина Баснета для улучшения функциональности. его можно использовать во всем проекте, определив его один раз! - person JoeGalind; 14.08.2016
comment
@ColinBasnett: добавление этого кода (который теперь называется sendActions (для: UIControlEvents.valueChanged)) приводит к бесконечному циклу ... - person Kendall Helmstetter Gelner; 11.07.2018

Ни один из других ответов не помог мне. Они заставили бы счетчик показывать и вращать, но само действие обновления никогда не происходило бы. Это работает:

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];

Обратите внимание: в этом примере используется табличное представление, но с тем же успехом оно могло быть и представлением коллекции.

person Kyle Robson    schedule 20.05.2014
comment
Это решило мою проблему. А вот сейчас пользуюсь SVPullToRefresh, как прогнать программно? - person Gank; 21.12.2014
comment
@Gank Я никогда не использовал SVPullToRefresh. Вы пробовали читать их документы? Судя по документации, кажется совершенно очевидным, что его можно вытащить программно: если вы хотите программно запустить обновление (например, в viewDidAppear :), вы можете сделать это с помощью: [tableView triggerPullToRefresh]; См.: github.com/samvermette/SVPullToRefresh - person Kyle Robson; 21.12.2014
comment
Идеально! Для меня это выглядело странно с анимацией, поэтому я просто заменил ее на scrollView.contentOffset = CGPoint(x: 0, y: scrollView.contentOffset.y - frame.height) - person user1366265; 15.09.2016

Уже упомянутый подход:

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

сделает спиннер видимым. Но он не оживал. Единственное, что я изменил, - это порядок этих двух методов, и все сработало:

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

Для Swift 4 / 4.1

Сочетание существующих ответов делает работу за меня:

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

Надеюсь это поможет!

person Isaac Bosca    schedule 27.05.2018

См. Также этот вопрос

Для меня это похоже на ошибку, потому что она возникает только тогда, когда свойство contentOffset tableView равно 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){

        }];

    }
}
person Peter Lapisu    schedule 27.04.2013
comment
Это неправда, у меня есть два разных UIViewController, оба из которых имеют contentOffset 0 при viewDidLoad, и один из них правильно вытягивает refreshControl при вызове [self.refreshControl beginRefreshing], а другой нет: / - person simonthumper; 11.07.2013
comment
В документации ничего не говорится об отображении элемента управления на beginRefreshing, только об изменении его состояния. Насколько я понимаю, это сделано для того, чтобы не запускать действие обновления дважды, чтобы, если программно вызываемое обновление все еще работало, действие, инициированное пользователем, не запускало другое. - person Koen.; 25.12.2015
comment
Я заметил проблемы с использованием метода setContentOffset: animated, поэтому это решение сработало для меня. - person Marc Etcheverry; 29.01.2019

Вот расширение 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()
      }
   }
}
person Ghulam Rasool    schedule 10.10.2017
comment
Из всех вышеперечисленных ответов это был единственный, с которым я смог работать над iOS 11.1 / xcode 9.1. - person Bassebus; 21.11.2017
comment
asyncAfter - это на самом деле то, что заставляет анимацию счетчика работать (iOS 12.3 / Xcode 10.2) - person francybiga; 22.08.2019

Для 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)
    }

}
person LukasHromadnik    schedule 14.04.2019
comment
вызов метода отправки действий - единственный способ, которым он работает для меня! - person jegadeesh; 24.03.2021
comment
запускает бесконечный цикл - person Mishka; 17.07.2021

Мне это идеально подходит:

Swift 3:

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

В дополнение к решению @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 Inspector для управления обновлением в Интерфейсном Разработчике.

person lyzkov    schedule 11.03.2014

Форт Свифт 2.2+

    self.tableView.setContentOffset(CGPoint(x: 0, y: -refreshControl.frame.size.height), animated: true)
person muhasturk    schedule 25.05.2016
comment
Я объединил это с принятым ответом, так как это просто обновление. - person Tudor; 05.10.2016

Если вы используете Rxswift для 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.

person jkyin    schedule 05.05.2017
comment
Это sendActions вызвать rx, что делает этот ответ связанным с RxSwift, если кому-то интересно с первого взгляда - person carbonr; 09.05.2017
comment
Мне пришлось использовать экземпляр refreshControl из tableViewController, а не из tableView. Кроме того, этот setContentOffset не работал у меня с таргетингом на iOS10. Однако этот работает: self.tableView.setContentOffset(CGPoint(x:0, y:self.tableView.contentOffset.y - (refreshControl.frame.size.height)), animated: true) - person nmdias; 12.07.2017

протестировано на 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() 
    }
}
person Pramodya Abeysinghe    schedule 26.03.2020

Я использую ту же технику для отображения визуального знака "данные обновляются". В результате пользователь извлекает приложение из фона, а каналы / списки будут обновляться с помощью пользовательского интерфейса, как если бы пользователь сам тянул таблицы, чтобы обновить себя. Моя версия содержит 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];
    }
}

результат

person WINSergey    schedule 23.12.2016