UIRefreshControl - почне оновлення не працює, коли UITableViewController знаходиться всередині UINavigationController


120

Я встановив 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;

Я відчуваю, що це має щось спільне з ієрархією вкладеного перегляду в навігаційному контролері, що не грає добре з керуючим керуванням - якісь пропозиції?

Дякую

Відповіді:


195

Здається, що якщо ви почнете програмно оновлювати, вам доведеться самостійно прокручувати подання таблиці, скажімо, змінюючи зміщення змісту

[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)        
    }
}

6
Це дивно, в моїх тестах endRefreshing коригує зміщення за потребою
Дмитро Шевченко

9
BTW, якщо ви використовуєте автоматичний макет, ви можете замінити рядок у відповіді таким: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) анімований: ТАК];
Ерік Бейкер

4
@EricBaker Я вважаю, що цього не буде. Не всі UITableViewControllers показують панелі навігації. Це призведе до того, що вийде topLayoutGuide довжиною 20, а компенсація занадто мала.
Фабіо Олівейра

3
@EricBaker ви можете використовувати: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) анімований: ТАК];
ZYiOS

1
Копія паста працює для мене, що дивовижно. Я втомлювався від матеріалів будівельника Apple.
okysabeni

78

UITableViewController автоматично встановлює властивість AdjustsScrollViewInsets після 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];

привіт, велике дякую, мені також цікаво чарівна точка (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.
Хуан Боеро

10
Я рекомендую поставити sendActionsForControlEvents(UIControlEvents.ValueChanged)в кінці цієї функції, інакше логіка оновлення фактично не буде запущена.
Колін Баснетт

Це, безумовно, найелегантніший спосіб зробити це. У тому числі коментар Коліна Баснетта для кращої функціональності. його можна використовувати в усьому проекті, визначивши його один раз!
JoeGalind

1
@ColinBasnett: Додавання цього коду (який зараз відправляєтьсяАкцій (для: 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, як програматизувати це?
Ганк

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

зробить прядок видимим. Але це не оживить. Єдине, що я змінив - це порядок цих двох методів, і все працювало:

[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

Дивіться також це питання

UIRefreshControl не показує шпигун при виклику beginПоновлення та contentOffset дорівнює 0

Мені це здається помилкою, оскільки він виникає лише тоді, коли властивість contentOffset таблиціView дорівнює 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
Це неправда, у мене є два різних UIViewControllers, обидва з яких мають contentOffset 0 на viewDidLoad, і один з них правильно знімає refreshControl при виклику [self.refreshControl beginRefreshing], а інший не: /
simonthumper

Документація нічого не говорить про відображення управління beginRefreshing, лише про те, що стан його змінюється. Як я бачу, це перешкоджати ініціюванню дії оновлення двічі, так що, можливо, все-таки запущена програмно названа оновлення, ініційована користувачем дія не запустить іншу.
Коен.

Я зазначив проблеми із використанням анімованого методу setContentOffset: анімація, тому це рішення працювало на мене.
Marc

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насправді те, що змушує роботу анімації
спінерів

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 Inspector для управління оновленням у Interface Builder.


3

Форт Свіфт 2.2+

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

Я об'єднав це з прийнятою відповіддю, оскільки це лише оновлення.
Тудор

3

Якщо ви використовуєте 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)
    }
}

Ця робота для швидких 3.1, iOS 10.


1
Його sendActionsзапуск, rxякий робить цю відповідь пов’язаною зі RxSwiftсправою, когось цікавить з першого погляду
carbonr

Мені довелося використовувати екземпляр refreshControl з tableViewController, а не tableView. Крім того, цей setContentOffset не працював для мене, орієнтуючись на iOS10. Це працює, однак: 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

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

результат

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.