UIRefreshControl - startRefreshing ไม่ทำงานเมื่อ 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

ดูเหมือนว่าถ้าคุณเริ่มรีเฟรชแบบเป็นโปรแกรมคุณต้องเลื่อนมุมมองตารางด้วยตัวเองพูดโดยเปลี่ยน 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 + 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 จะปรับชดเชยตามต้องการ
Dmitry Shevchenko

9
BTW หากคุณใช้รูปแบบอัตโนมัติคุณสามารถแทนที่บรรทัดในคำตอบด้วยสิ่งนี้: [self.tableView setContentOffset: CGPointMake (0, -self.topLayoutGuide.length) ภาพเคลื่อนไหว: ใช่];
Eric Baker

4
@EricBaker ฉันเชื่อว่าจะไม่ทำ UITableViewControllers บางตัวไม่แสดงแถบการนำทาง สิ่งนี้จะนำไปสู่ ​​topLayoutGuide ของความยาว 20 และออฟเซ็ตเล็กเกินไป
Fábio Oliveira

3
@EricBaker คุณสามารถใช้: [self.tableView setContentOffset: CGPointMake (0, self.topLayoutGuide.length -self.refreshControl.frame.size.height) ภาพเคลื่อนไหว: ใช่];
ZYiOS

1
การคัดลอกวางใช้งานได้สำหรับฉันซึ่งยอดเยี่ยมมาก ฉันรู้สึกเบื่อหน่ายกับสตอรี่บอร์ดของ Apple
okysabeni

78

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

สวัสดีขอบคุณมากฉันยังอยากรู้เกี่ยวกับจุดมหัศจรรย์ (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
Juan Boero

10
ฉันขอแนะนำให้วางไว้sendActionsForControlEvents(UIControlEvents.ValueChanged)ที่ส่วนท้ายของฟังก์ชันนี้มิฉะนั้นตรรกะการรีเฟรชจริงจะไม่ทำงาน
Colin Basnett

นี่เป็นวิธีที่ดีที่สุดในการทำ รวมถึงความคิดเห็นของ Colin Basnett เพื่อการทำงานที่ดีขึ้น สามารถใช้ได้ทั่วทั้งโครงการโดยกำหนดเพียงครั้งเดียว!
JoeGalind

1
@ColinBasnett: การเพิ่มรหัสนั้น (ซึ่งตอนนี้คือ sendActions (สำหรับ: 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วิธีดึงมันลงโดยใช้โปรแกรม?
Gank

1
@Gank ฉันไม่เคยใช้ SVPullToRefresh คุณลองอ่านเอกสารของพวกเขาแล้วหรือยัง? ดูเหมือนค่อนข้างชัดเจนจากเอกสารที่สามารถดึงลงโดยใช้โปรแกรมได้: "หากคุณต้องการทริกเกอร์การรีเฟรชโดยใช้โปรแกรม (เช่นใน viewDidAppear :) คุณสามารถทำได้โดย: [tableView triggerPullToRefresh];" ดู: github.com/samvermette/ SVPullToRefresh
Kyle Robson

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)

หวังว่านี่จะช่วยได้!


8

นี่คือ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

7

ดูคำถามนี้ด้วย

UIRefreshControl ไม่แสดง spiny เมื่อเรียก beginRefreshing และ contentOffset เป็น 0

ดูเหมือนว่าเป็นข้อบกพร่องสำหรับฉันเพราะจะเกิดขึ้นเมื่อคุณสมบัติ 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){

        }];

    }
}

1
นี่ไม่เป็นความจริงฉันมี UIViewControllers สองตัวที่แตกต่างกันซึ่งทั้งสองมี contentOffset เป็น 0 เมื่อ viewDidLoad และหนึ่งในนั้นดึง refreshControl ลงอย่างถูกต้องเมื่อเรียก [self.refreshControl beginRefreshing] และอีกอันไม่มี: /
simonthumper

เอกสารประกอบไม่ได้บอกอะไรเกี่ยวกับการแสดงการควบคุมbeginRefreshingแต่สถานะจะเปลี่ยนไป ตามที่ฉันเห็นคือการป้องกันไม่ให้เริ่มการดำเนินการรีเฟรชสองครั้งดังนั้นการรีเฟรชทางโปรแกรมอาจยังคงทำงานอยู่การดำเนินการที่เริ่มโดยผู้ใช้จะไม่เริ่มต้นอีกครั้ง
โคเอ็น.

ฉันสังเกตเห็นปัญหาเกี่ยวกับการใช้ setContentOffset: วิธีการเคลื่อนไหวดังนั้นวิธีนี้จึงใช้ได้กับฉัน
Marc Etcheverry

4

มันใช้งานได้ดีสำหรับฉัน:

Swift 3:

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

4

สำหรับสวิฟท์ 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

Fort Swift 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)
    }
}

งานนี้สำหรับ swift 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

ฉันใช้เทคนิคเดียวกันในการแสดงเครื่องหมายภาพ "data is update" ของผู้ใช้ ผลลัพธ์ที่ผู้ใช้นำแอปจากพื้นหลังและฟีด / รายการจะได้รับการอัปเดตด้วย UI เช่นผู้ใช้ดึงตารางเพื่อรีเฟรชตัวเอง เวอร์ชันของฉันมี 3 อย่าง

1) ผู้ส่ง "ตื่น"

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

2) Observer ใน 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.