จะบอกได้อย่างไรเมื่อ UITableView โหลด ReloadData เสร็จสมบูรณ์แล้ว


183

ฉันพยายามเลื่อนไปที่ด้านล่างของ UITableView หลังจากดำเนินการเสร็จแล้ว [self.tableView reloadData]

ฉันมี แต่เดิม

 [self.tableView reloadData]
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];

[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

แต่ฉันอ่านแล้วว่า reloadData เป็นแบบอะซิงโครนัสดังนั้นการเลื่อนจึงไม่เกิดขึ้นตั้งแต่นั้นself.tableViewมา[self.tableView numberOfSections]และ[self.tableView numberOfRowsinSectionเป็น 0 ทั้งหมด

ขอบคุณ!

มีอะไรแปลก ๆ ที่ฉันใช้:

[self.tableView reloadData];
NSLog(@"Number of Sections %d", [self.tableView numberOfSections]);
NSLog(@"Number of Rows %d", [self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1);

ในคอนโซลจะส่งคืน Sections = 1, Row = -1;

เมื่อฉันทำ NSLogs เดียวกันที่แน่นอนในcellForRowAtIndexPathฉันจะได้รับส่วน = 1 และแถว = 8; (8 ถูกต้อง)


อาจเป็นไปได้ซ้ำสำหรับคำถามนี้: stackoverflow.com/questions/4163579/…
pmk

2
ทางออกที่ดีที่สุดที่ฉันเคยเห็น stackoverflow.com/questions/1483581/…
Khaled Annajar

คำตอบของฉันสำหรับสิ่งต่อไปนี้อาจช่วยคุณได้stackoverflow.com/questions/4163579/…
Suhas Aithal

ลองคำตอบของฉันที่นี่ - stackoverflow.com/questions/4163579/ …
Suhas Aithal

คำตอบ:


288

การรีโหลดเกิดขึ้นในระหว่างเลย์เอาต์ถัดไปซึ่งโดยปกติจะเกิดขึ้นเมื่อคุณกลับการควบคุมไปยังลูปที่เรียกใช้ (หลังจากพูดการกระทำของปุ่มหรือสิ่งใดก็ตามที่ส่งคืน)

ดังนั้นวิธีหนึ่งในการเรียกใช้บางสิ่งหลังจากรีโหลดมุมมองตารางคือการบังคับให้มุมมองตารางแสดงเค้าโครงทันที:

[self.tableView reloadData];
[self.tableView layoutIfNeeded];
 NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection: ([self.tableView numberOfSections]-1)];
[self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];

อีกวิธีหนึ่งคือการกำหนดตารางโค้ด after-layout ให้ทำงานในภายหลังโดยใช้dispatch_async:

[self.tableView reloadData];

dispatch_async(dispatch_get_main_queue(), ^{
     NSIndexPath* indexPath = [NSIndexPath indexPathForRow: ([self.tableView numberOfRowsInSection:([self.tableView numberOfSections]-1)]-1) inSection:([self.tableView numberOfSections]-1)];

    [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
});

UPDATE

เมื่อตรวจสอบต่อไปผมพบว่ามุมมองตารางส่งtableView:numberOfSections:และไปยังแหล่งข้อมูลก่อนที่จะกลับมาจากtableView:numberOfRowsInSection: reloadDataหากดำเนินการมอบหมายtableView:heightForRowAtIndexPath:, มุมมองตารางยังส่งที่ (สำหรับแต่ละแถว) reloadDataก่อนที่จะกลับมาจาก

อย่างไรก็ตามมุมมองตารางจะไม่ส่งtableView:cellForRowAtIndexPath:หรือtableView:headerViewForSectionจนกว่าเฟสเลย์เอาต์ซึ่งจะเกิดขึ้นตามค่าเริ่มต้นเมื่อคุณกลับการควบคุมไปยังลูปรัน

ฉันยังพบว่าในโปรแกรมทดสอบเล็ก ๆ รหัสในคำถามของคุณจะเลื่อนไปที่ด้านล่างของมุมมองตารางโดยที่ฉันไม่ต้องทำอะไรเป็นพิเศษ (เช่นการส่งlayoutIfNeededหรือการใช้dispatch_async)


3
@rob ขึ้นอยู่กับว่าแหล่งข้อมูลของคุณมีขนาดใหญ่แค่ไหนคุณสามารถเคลื่อนไหวไปที่ด้านล่างของมุมมอง Table ในลูปรันเดียวกัน หากคุณลองใช้รหัสทดสอบกับตารางขนาดใหญ่กลอุบายของคุณในการใช้ GCD เพื่อเลื่อนการเลื่อนจนกว่าการวนรอบถัดไปจะทำงานได้ในขณะที่การเลื่อนแบบทันทีจะล้มเหลว แต่อย่างไรก็ตามขอบคุณสำหรับเคล็ดลับนี้ !!
นาย T

7
วิธีที่ 2 ไม่ได้ผลสำหรับฉันด้วยเหตุผลบางอย่างที่ไม่รู้จัก แต่เลือกวิธีแรกแทน
Raj Pawan Gumdal

4
dispatch_async(dispatch_get_main_queue())วิธีการไม่รับประกันว่าจะทำงาน ฉันเห็นพฤติกรรมที่ไม่ได้กำหนดไว้ล่วงหน้าซึ่งบางครั้งระบบได้เสร็จสิ้นการจัดวางหน้าตัวอย่างและการเรนเดอร์เซลล์ก่อนการบล็อกเสร็จสมบูรณ์และบางครั้งหลังจากนั้น ฉันจะโพสต์คำตอบที่เหมาะกับฉันด้านล่าง
Tyler Sheaffer

3
เห็นด้วยกับdispatch_async(dispatch_get_main_queue())ไม่ได้ทำงานเสมอ ดูผลลัพธ์แบบสุ่มที่นี่
Vojto

1
เธรดหลักรันNSRunLoopและ ลูปรันมีเฟสแตกต่างกันและคุณสามารถกำหนดเวลาการโทรกลับสำหรับเฟสที่ระบุ (โดยใช้กCFRunLoopObserver) UIKit จัดกำหนดการเค้าโครงให้เกิดขึ้นในช่วงต่อมาหลังจากที่ตัวจัดการเหตุการณ์ของคุณกลับมา
rob mayoff

106

สวิฟท์:

extension UITableView {
    func reloadData(completion:@escaping ()->()) {
        UIView.animateWithDuration(0, animations: { self.reloadData() })
            { _ in completion() }
    }
}

...somewhere later...

tableView.reloadData {
    println("done")
}

Objective-C:

[UIView animateWithDuration:0 animations:^{
    [myTableView reloadData];
} completion:^(BOOL finished) {
    //Do something after that...
}];

16
นั่นเทียบเท่ากับการส่งอะไรบางอย่างในหัวข้อหลักใน "อนาคตอันใกล้" อาจเป็นไปได้ว่าคุณเพิ่งเห็นมุมมองตารางแสดงวัตถุก่อนที่เธรดหลักจะบล็อกการบล็อกเสร็จ ไม่แนะนำให้ทำการแฮ็กประเภทนี้ในตอนแรก แต่ไม่ว่าในกรณีใดคุณควรใช้ dispatch_after หากคุณกำลังพยายามทำสิ่งนี้
seo

1
วิธีแก้ปัญหาของ Rob นั้นดี แต่ใช้ไม่ได้หากไม่มีแถวในมุมมองตาราง วิธีการแก้ปัญหาของ Aviel มีข้อได้เปรียบในการทำงานแม้ว่าตารางจะไม่มีบรรทัด แต่มีเพียงบางส่วนเท่านั้น
Chrstph SLN

@Christophe ณ ตอนนี้ฉันสามารถใช้การปรับปรุงของ Rob ในมุมมองตารางโดยไม่ต้องแถวใด ๆ โดยแทนที่ใน Mock view controller ของฉันtableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Intวิธีการและการแทรกในการแทนที่ของฉันสิ่งที่ฉันต้องการที่จะแจ้งให้ทราบว่าโหลดเสร็จแล้ว
Gobe

49

ตั้งแต่ Xcode 8.2.1, iOS 10 และ swift 3

คุณสามารถกำหนดจุดสิ้นสุดtableView.reloadData()ได้อย่างง่ายดายโดยใช้บล็อก CATransaction:

CATransaction.begin()
CATransaction.setCompletionBlock({
    print("reload completed")
    //Your completion code here
})
print("reloading")
tableView.reloadData()
CATransaction.commit()

ด้านบนใช้สำหรับระบุจุดสิ้นสุดของ reloadData () ของ UICollectionView และ UIPickerView ของ reloadAllComponents ()


👍ฉันยังใช้งานได้หากคุณทำการโหลดซ้ำแบบกำหนดเองเช่นการแทรกการลบหรือย้ายแถวในมุมมองตารางภายในbeginUpdatesและการendUpdatesโทรด้วยตนเอง
Darrarski

ฉันเชื่อว่านี่เป็นทางออกที่ทันสมัย แน่นอนมันเป็นรูปแบบทั่วไปใน iOS เช่น ... stackoverflow.com/a/47536770/294884
Fattie

ฉันลองสิ่งนี้ ฉันมีพฤติกรรมที่แปลกมาก มุมมองตารางของฉันแสดงสองส่วนหัวที่ถูกต้อง ข้างในการแสดงsetCompletionBlockของฉันnumberOfSections2 ... จนถึงดีมาก แต่ถ้าข้างในsetCompletionBlockฉันtableView.headerView(forSection: 1)กลับมาแล้วnil!!! ด้วยเหตุนี้ฉันคิดว่าบล็อกนี้อาจเกิดขึ้นก่อนที่จะโหลดซ้ำหรือจับอะไรบางอย่างก่อนหน้านี้ FYI ฉันลองคำตอบของ Tyler แล้วก็ใช้ได้! @Fattie
Honey

32

dispatch_async(dispatch_get_main_queue())วิธีการดังกล่าวจะไม่รับประกันในการทำงาน ฉันเห็นพฤติกรรมที่ไม่ได้กำหนดไว้ล่วงหน้าซึ่งบางครั้งระบบได้เสร็จสิ้นการจัดวางหน้าตัวอย่างและการเรนเดอร์เซลล์ก่อนการบล็อกเสร็จสมบูรณ์และบางครั้งหลังจากนั้น

นี่คือโซลูชันที่ใช้งานได้ 100% สำหรับฉันบน iOS 10 มันต้องใช้ความสามารถในการสร้าง UITableView หรือ UICollectionView เป็น subclass แบบกำหนดเอง นี่คือโซลูชัน UICollectionView แต่มันเหมือนกันทุกประการสำหรับ UITableView:

CustomCollectionView.h:

#import <UIKit/UIKit.h>

@interface CustomCollectionView: UICollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock;

@end

CustomCollectionView.m:

#import "CustomCollectionView.h"

@interface CustomCollectionView ()

@property (nonatomic, copy) void (^reloadDataCompletionBlock)(void);

@end

@implementation CustomCollectionView

- (void)reloadDataWithCompletion:(void (^)(void))completionBlock
{
    self.reloadDataCompletionBlock = completionBlock;
    [self reloadData];
}

- (void)layoutSubviews
{
    [super layoutSubviews];

    if (self.reloadDataCompletionBlock) {
        self.reloadDataCompletionBlock();
        self.reloadDataCompletionBlock = nil;
    }
}

@end

ตัวอย่างการใช้งาน:

[self.collectionView reloadDataWithCompletion:^{
    // reloadData is guaranteed to have completed
}];

ดูที่นี่สำหรับรุ่นสวิฟท์ของคำตอบนี้


นี่เป็นวิธีที่ถูกต้องเท่านั้น เพิ่มไปยังโครงการของฉันเพราะฉันต้องการเฟรมสุดท้ายของบางเซลล์เพื่อการเคลื่อนไหว ฉันยังเพิ่มและแก้ไขสำหรับ Swift หวังว่าคุณจะไม่รังเกียจ😉
Jon Vogel

2
หลังจากที่คุณเรียกบล็อกที่อยู่ในบล็อกlayoutSubviewsแล้วควรตั้งให้nilเป็นการเรียกครั้งต่อ ๆ ไปlayoutSubviewsซึ่งไม่จำเป็นต้องreloadDataถูกเรียกเนื่องจากจะส่งผลให้บล็อกถูกดำเนินการเนื่องจากมีการอ้างอิงที่แข็งแกร่งซึ่งไม่ใช่พฤติกรรมที่ต้องการ
Mark Bourke

เหตุใดฉันจึงใช้สิ่งนี้กับ UITableView ไม่ได้ มันไม่แสดงอินเทอร์เฟซที่มองเห็นได้ ฉันนำเข้าไฟล์ส่วนหัวด้วย แต่ก็ยังเหมือนเดิม
Julfikar

2
ภาคผนวกของคำตอบนี้เป็นไปได้ที่จะปิดกั้นการโทรกลับที่มีอยู่หากมีเพียงหนึ่งหมายถึงผู้โทรหลายคนจะมีสภาพการแข่งขัน วิธีแก้ปัญหาคือสร้างreloadDataCompletionBlockอาร์เรย์ของบล็อกและวนซ้ำในการประมวลผลและทำให้อาร์เรย์ว่างหลังจากนั้น
Tyler Sheaffer

1) นี่ไม่เทียบเท่ากับคำตอบแรกของ Rob นั่นคือการใช้ layoutIfNeeded? 2) ทำไมคุณถึงพูดถึง iOS 10 มันไม่ทำงานบน iOS 9!
ฮันนี่

30

ฉันมีปัญหาเช่นเดียวกับ Tyler Sheaffer

ฉันใช้วิธีแก้ปัญหาของเขาใน Swift และแก้ไขปัญหาของฉัน

สวิฟท์ 3.0:

final class UITableViewWithReloadCompletion: UITableView {
  private var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    reloadDataCompletionBlock?()
    reloadDataCompletionBlock = nil
  }


  func reloadDataWithCompletion(completion: @escaping () -> Void) {
    reloadDataCompletionBlock = completion
    self.reloadData()
  }
}

สวิฟท์ 2:

class UITableViewWithReloadCompletion: UITableView {

  var reloadDataCompletionBlock: (() -> Void)?

  override func layoutSubviews() {
    super.layoutSubviews()

    self.reloadDataCompletionBlock?()
    self.reloadDataCompletionBlock = nil
  }

  func reloadDataWithCompletion(completion:() -> Void) {
      reloadDataCompletionBlock = completion
      self.reloadData()
  }
}

ตัวอย่างการใช้งาน:

tableView.reloadDataWithCompletion() {
 // reloadData is guaranteed to have completed
}

1
ดี! nit-pick ขนาดเล็กคุณสามารถลบออกได้if letโดยพูดว่าreloadDataCompletionBlock?()จะเรียก iff ไม่ใช่ศูนย์💥
Tyler Sheaffer

ไม่มีโชคกับสิ่งนี้ในสถานการณ์ของฉันใน ios9
Matjan

self.reloadDataCompletionBlock? { completion() }น่าจะได้รับself.reloadDataCompletionBlock?()
emem

ฉันจะจัดการปรับขนาดความสูงของมุมมองตารางได้อย่างไร ก่อนหน้านี้ฉันเรียก tableView.beginUpdates () tableView.layoutIfNeeded () tableView.endUpdates ()
Parth Tamane

10

และUICollectionViewเวอร์ชันขึ้นอยู่กับคำตอบของ kolaworld:

https://stackoverflow.com/a/43162226/1452758

ต้องการการทดสอบ ทำงานได้แล้วบน iOS 9.2, Xcode 9.2 เบต้า 2 พร้อมการเลื่อนดูคอลเลกชันไปยังดัชนีเพื่อเป็นการปิด

extension UICollectionView
{
    /// Calls reloadsData() on self, and ensures that the given closure is
    /// called after reloadData() has been completed.
    ///
    /// Discussion: reloadData() appears to be asynchronous. i.e. the
    /// reloading actually happens during the next layout pass. So, doing
    /// things like scrolling the collectionView immediately after a
    /// call to reloadData() can cause trouble.
    ///
    /// This method uses CATransaction to schedule the closure.

    func reloadDataThenPerform(_ closure: @escaping (() -> Void))
    {       
        CATransaction.begin()
            CATransaction.setCompletionBlock(closure)
            self.reloadData()
        CATransaction.commit()
    }
}

การใช้งาน:

myCollectionView.reloadDataThenPerform {
    myCollectionView.scrollToItem(at: indexPath,
            at: .centeredVertically,
            animated: true)
}

6

ดูเหมือนว่าผู้คนยังคงอ่านคำถามนี้และคำตอบอยู่ B / c นั้นฉันกำลังแก้ไขคำตอบของฉันเพื่อลบคำว่าซิงโครนัสซึ่งไม่เกี่ยวข้องกับสิ่งนี้จริงๆ

When [tableView reloadData]ส่งคืนโครงสร้างข้อมูลภายในหลัง tableView ได้รับการปรับปรุง ดังนั้นเมื่อวิธีการเสร็จสมบูรณ์คุณสามารถเลื่อนไปที่ด้านล่างได้อย่างปลอดภัย ฉันยืนยันสิ่งนี้ในแอปของฉันเอง คำตอบที่ได้รับการยอมรับอย่างกว้างขวางโดย @ rob-mayoff ในขณะที่ยังทำให้เกิดความสับสนในคำศัพท์ก็ยอมรับเหมือนกันในการอัพเดตครั้งล่าสุดของเขา

หากคุณtableViewไม่ได้เลื่อนไปที่ด้านล่างคุณอาจมีปัญหาในรหัสอื่น ๆ ที่คุณไม่ได้โพสต์ บางทีคุณกำลังเปลี่ยนข้อมูลหลังจากการเลื่อนเสร็จสมบูรณ์และคุณไม่ได้โหลดซ้ำและ / หรือเลื่อนไปที่ด้านล่าง?

reloadDataเพิ่มเข้าสู่ระบบบางอย่างดังต่อไปนี้เพื่อตรวจสอบว่าข้อมูลตารางที่ถูกต้องหลังจากที่ ฉันมีรหัสต่อไปนี้ในแอปตัวอย่างและทำงานได้อย่างสมบูรณ์

// change the data source

NSLog(@"Before reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView reloadData];

NSLog(@"After reload / sections = %d, last row = %d",
      [self.tableView numberOfSections],
      [self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]);

[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[self.tableView numberOfRowsInSection:[self.tableView numberOfSections]-1]-1
                                                          inSection:[self.tableView numberOfSections] - 1]
                      atScrollPosition:UITableViewScrollPositionBottom
                              animated:YES];

ฉันอัพเดทคำถาม คุณรู้หรือไม่ว่าเหตุใด NSLogs ของฉันจึงให้ผลลัพธ์เช่นนี้
อลัน

8
reloadDataไม่ซิงโครนัส มันเคยเป็น - ดูคำตอบนี้: stackoverflow.com/a/16071589/193896
bendytree

1
มันเป็นแบบซิงโครนัส ง่ายมากที่จะทดสอบและดูด้วยแอปตัวอย่าง คุณเชื่อมโยงกับคำตอบของ @ rob ในคำถามนี้ หากคุณอ่านการอัปเดตของเขาที่ด้านล่างเขาได้ตรวจสอบสิ่งนี้เช่นกัน บางทีคุณกำลังพูดถึงการเปลี่ยนแปลงเค้าโครงภาพ มันเป็นความจริงที่ tableView ไม่ได้อัพเดทอย่างเห็นได้ชัดพร้อมกัน แต่เป็นข้อมูล นั่นเป็นสาเหตุที่ค่าที่ OP ต้องการนั้นถูกต้องทันทีหลังจากreloadDataกลับมา
XJones

1
reloadDataคุณอาจจะสับสนเกี่ยวกับสิ่งที่คาดว่าจะเกิดขึ้นใน ใช้กรณีทดสอบของฉันviewWillAppearเพื่อยอมรับscrollToRowAtIndexPath:บรรทัด b / c ที่ไม่มีความหมายหากtableViewไม่ปรากฏขึ้น คุณจะเห็นว่ามีการreloadDataปรับปรุงข้อมูลแคชในtableViewอินสแตนซ์และที่reloadDataซิงโครนัส หากคุณกำลังอ้างถึงtableViewวิธีการมอบหมายอื่น ๆ ที่เรียกว่าเมื่อtableViewกำลังเลย์เอาต์เหล่านั้นจะไม่ได้รับการเรียกหากtableViewไม่ปรากฏขึ้น หากฉันเข้าใจผิดสถานการณ์ของคุณโปรดอธิบาย
XJones

3
สนุกเท่าไหร่ มันเป็นปี 2014 และมีข้อโต้แย้งว่าวิธีการบางอย่างเป็นแบบซิงโครนัสและแบบอะซิงโครนัสหรือไม่ รู้สึกเหมือนเดา รายละเอียดการใช้งานทั้งหมดจะทึบแสงหลังชื่อวิธีการนั้นทั้งหมด การเขียนโปรแกรมไม่ดีหรือ
fatuhoku

5

ฉันใช้เคล็ดลับนี้ค่อนข้างแน่ใจว่าฉันโพสต์ไปยังซ้ำของคำถามนี้:

-(void)tableViewDidLoadRows:(UITableView *)tableView{
    // do something after loading, e.g. select a cell.
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    // trick to detect when table view has finished loading.
    [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(tableViewDidLoadRows:) object:tableView];
    [self performSelector:@selector(tableViewDidLoadRows:) withObject:tableView afterDelay:0];

    // specific to your controller
    return self.objects.count;
}

@Fattie มันไม่ชัดเจนหากคุณหมายถึงว่าเป็นความคิดเห็นเชิงบวกหรือความคิดเห็นเชิงลบ แต่ฉันเห็นว่าคุณได้แสดงความคิดเห็นคำตอบอีกว่า"นี่น่าจะเป็นทางออกที่ดีที่สุด!" ดังนั้นฉันคิดว่าค่อนข้างพูดคุณไม่คิดว่าวิธีนี้จะดีที่สุด
Cœur

1
การพึ่งพาด้านหนึ่งส่งผลต่อภาพเคลื่อนไหวปลอมหรือไม่? ไม่มีทางเป็นความคิดที่ดี เรียนรู้การเลือกหรือ GCD และทำอย่างถูกต้อง Btw ขณะนี้มีวิธีการโหลดตารางคุณสามารถใช้สิ่งนั้นได้ถ้าคุณไม่รังเกียจการใช้โปรโตคอลส่วนตัวที่น่าจะดีเพราะมันเป็นเฟรมเวิร์กที่เรียกรหัสของคุณแทนที่จะเป็นวิธีอื่น
malhal

3

อันที่จริงอันนี้แก้ปัญหาของฉัน:

-(void) tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

NSSet *visibleSections = [NSSet setWithArray:[[tableView indexPathsForVisibleRows] valueForKey:@"section"]];
if (visibleSections) {
    // hide the activityIndicator/Loader
}}

2

ลองวิธีนี้ใช้งานได้

[tblViewTerms performSelectorOnMainThread:@selector(dataLoadDoneWithLastTermIndex:) withObject:lastTermIndex waitUntilDone:YES];waitUntilDone:YES];

@interface UITableView (TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex;

@end

@implementation UITableView(TableViewCompletion)

-(void)dataLoadDoneWithLastTermIndex:(NSNumber*)lastTermIndex
{
    NSLog(@"dataLoadDone");


NSIndexPath* indexPath = [NSIndexPath indexPathForRow: [lastTermIndex integerValue] inSection: 0];

[self selectRowAtIndexPath:indexPath animated:YES scrollPosition:UITableViewScrollPositionNone];

}
@end

ฉันจะดำเนินการเมื่อโหลดตารางอย่างสมบูรณ์

โซลูชันอื่นคือคุณสามารถ subclass UITableView


1

ฉันสิ้นสุดการใช้โซลูชันของ Shawn:

สร้างคลาส UITableView แบบกำหนดเองด้วยผู้รับมอบสิทธิ์:

protocol CustomTableViewDelegate {
    func CustomTableViewDidLayoutSubviews()
}

class CustomTableView: UITableView {

    var customDelegate: CustomTableViewDelegate?

    override func layoutSubviews() {
        super.layoutSubviews()
        self.customDelegate?.CustomTableViewDidLayoutSubviews()
    }
}

จากนั้นในรหัสของฉันฉันใช้

class SomeClass: UIViewController, CustomTableViewDelegate {

    @IBOutlet weak var myTableView: CustomTableView!

    override func viewDidLoad() {
        super.viewDidLoad()

        self.myTableView.customDelegate = self
    }

    func CustomTableViewDidLayoutSubviews() {
        print("didlayoutsubviews")
        // DO other cool things here!!
    }
}

ตรวจสอบให้แน่ใจด้วยว่าคุณตั้งค่ามุมมองตารางเป็น CustomTableView ในตัวสร้างส่วนต่อประสาน:

ป้อนคำอธิบายรูปภาพที่นี่


วิธีนี้ใช้ได้ผล แต่ปัญหาคือวิธีการถูกโจมตีทุกครั้งที่ทำการโหลดเซลล์เดียวไม่ใช่การโหลดมุมมองทั้งหมดดังนั้นคำตอบนี้ไม่เกี่ยวข้องกับคำถามที่ถาม
Yash Bedi

จริงจะได้รับการเรียกมากกว่าหนึ่งครั้ง แต่ไม่ได้อยู่ในทุกเซลล์ ดังนั้นคุณสามารถฟังผู้รับมอบสิทธิ์คนแรกและละเว้นส่วนที่เหลือจนกว่าคุณจะเรียก reloadData อีกครั้ง
แซม

1

ใน Swift 3.0 +เราสามารถสร้างส่วนขยายUITableViewด้วยโดยมีescaped Closureลักษณะดังนี้:

extension UITableView {
    func reloadData(completion: @escaping () -> ()) {
        UIView.animate(withDuration: 0, animations: { self.reloadData()})
        {_ in completion() }
    }
}

และใช้งานได้เหมือนด้านล่างที่คุณต้องการ:

Your_Table_View.reloadData {
   print("reload done")
 }

หวังว่าสิ่งนี้จะช่วยให้ใครบางคน ไชโย!


ความคิดที่ยอดเยี่ยม .. สิ่งเดียวคือเพื่อหลีกเลี่ยงความสับสนฉันเปลี่ยนชื่อฟังก์ชั่น t reload แทน reloadData () ขอบคุณ
Vijay Kumar AB

1

รายละเอียด

  • Xcode เวอร์ชั่น 10.2.1 (10E1001), Swift 5

สารละลาย

import UIKit

// MARK: - UITableView reloading functions

protocol ReloadCompletable: class { func reloadData() }

extension ReloadCompletable {
    func run(transaction closure: (() -> Void)?, completion: (() -> Void)?) {
        guard let closure = closure else { return }
        CATransaction.begin()
        CATransaction.setCompletionBlock(completion)
        closure()
        CATransaction.commit()
    }

    func run(transaction closure: (() -> Void)?, completion: ((Self) -> Void)?) {
        run(transaction: closure) { [weak self] in
            guard let self = self else { return }
            completion?(self)
        }
    }

    func reloadData(completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadData() }, completion: closure)
    }
}

// MARK: - UITableView reloading functions

extension ReloadCompletable where Self: UITableView {
    func reloadRows(at indexPaths: [IndexPath], with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadRows(at: indexPaths, with: animation) }, completion: closure)
    }

    func reloadSections(_ sections: IndexSet, with animation: UITableView.RowAnimation, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections, with: animation) }, completion: closure)
    }
}

// MARK: - UICollectionView reloading functions

extension ReloadCompletable where Self: UICollectionView {

    func reloadSections(_ sections: IndexSet, completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadSections(sections) }, completion: closure)
    }

    func reloadItems(at indexPaths: [IndexPath], completion closure: ((Self) -> Void)?) {
        run(transaction: { [weak self] in self?.reloadItems(at: indexPaths) }, completion: closure)
    }
}

การใช้

UITableView

// Activate
extension UITableView: ReloadCompletable { }

// ......
let tableView = UICollectionView()

// reload data
tableView.reloadData { tableView in print(collectionView) }

// or
tableView.reloadRows(at: indexPathsToReload, with: rowAnimation) { tableView in print(tableView) }

// or
tableView.reloadSections(IndexSet(integer: 0), with: rowAnimation) { _tableView in print(tableView) }

UICollectionView

// Activate
extension UICollectionView: ReloadCompletable { }

// ......
let collectionView = UICollectionView()

// reload data
collectionView.reloadData { collectionView in print(collectionView) }

// or
collectionView.reloadItems(at: indexPathsToReload) { collectionView in print(collectionView) }

// or
collectionView.reloadSections(IndexSet(integer: 0)) { collectionView in print(collectionView) }

ตัวอย่างเต็ม

อย่าลืมเพิ่มรหัสโซลูชันที่นี่

import UIKit

class ViewController: UIViewController {

    private weak var navigationBar: UINavigationBar?
    private weak var tableView: UITableView?

    override func viewDidLoad() {
        super.viewDidLoad()
        setupNavigationItem()
        setupTableView()
    }
}
// MARK: - Activate UITableView reloadData with completion functions

extension UITableView: ReloadCompletable { }

// MARK: - Setup(init) subviews

extension ViewController {

    private func setupTableView() {
        guard let navigationBar = navigationBar else { return }
        let tableView = UITableView()
        view.addSubview(tableView)
        tableView.translatesAutoresizingMaskIntoConstraints = false
        tableView.topAnchor.constraint(equalTo: navigationBar.bottomAnchor).isActive = true
        tableView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tableView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
        tableView.dataSource = self
        self.tableView = tableView
    }

    private func setupNavigationItem() {
        let navigationBar = UINavigationBar()
        view.addSubview(navigationBar)
        self.navigationBar = navigationBar
        navigationBar.translatesAutoresizingMaskIntoConstraints = false
        navigationBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
        navigationBar.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        navigationBar.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        let navigationItem = UINavigationItem()
        navigationItem.rightBarButtonItem = UIBarButtonItem(title: "all", style: .plain, target: self, action: #selector(reloadAllCellsButtonTouchedUpInside(source:)))
        let buttons: [UIBarButtonItem] = [
                                            .init(title: "row", style: .plain, target: self,
                                                  action: #selector(reloadRowButtonTouchedUpInside(source:))),
                                            .init(title: "section", style: .plain, target: self,
                                                  action: #selector(reloadSectionButtonTouchedUpInside(source:)))
                                            ]
        navigationItem.leftBarButtonItems = buttons
        navigationBar.items = [navigationItem]
    }
}

// MARK: - Buttons actions

extension ViewController {

    @objc func reloadAllCellsButtonTouchedUpInside(source: UIBarButtonItem) {
        let elementsName = "Data"
        print("-- Reloading \(elementsName) started")
        tableView?.reloadData { taleView in
            print("-- Reloading \(elementsName) stopped \(taleView)")
        }
    }

    private var randomRowAnimation: UITableView.RowAnimation {
        return UITableView.RowAnimation(rawValue: (0...6).randomElement() ?? 0) ?? UITableView.RowAnimation.automatic
    }

    @objc func reloadRowButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Rows"
        print("-- Reloading \(elementsName) started")
        let indexPathToReload = tableView.indexPathsForVisibleRows?.randomElement() ?? IndexPath(row: 0, section: 0)
        tableView.reloadRows(at: [indexPathToReload], with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }

    @objc func reloadSectionButtonTouchedUpInside(source: UIBarButtonItem) {
        guard let tableView = tableView else { return }
        let elementsName = "Sections"
        print("-- Reloading \(elementsName) started")
        tableView.reloadSections(IndexSet(integer: 0), with: randomRowAnimation) { _tableView in
            //print("-- \(taleView)")
            print("-- Reloading \(elementsName) stopped in \(_tableView)")
        }
    }
}

extension ViewController: UITableViewDataSource {
    func numberOfSections(in tableView: UITableView) -> Int { return 1 }
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 20 }
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = "\(Date())"
        return cell
    }
}

ผล

ป้อนคำอธิบายรูปภาพที่นี่


0

เพียงที่จะนำเสนอวิธีการอื่นบนพื้นฐานความคิดความสำเร็จของการเป็น 'สุดท้ายที่มองเห็น' cellForRowเซลล์ที่จะถูกส่งไปยัง

// Will be set when reload is called
var lastIndexPathToDisplay: IndexPath?

typealias ReloadCompletion = ()->Void

var reloadCompletion: ReloadCompletion?

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    // Setup cell

    if indexPath == self.lastIndexPathToDisplay {

        self.lastIndexPathToDisplay = nil

        self.reloadCompletion?()
        self.reloadCompletion = nil
    }

    // Return cell
...

func reloadData(completion: @escaping ReloadCompletion) {

    self.reloadCompletion = completion

    self.mainTable.reloadData()

    self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last
}

ปัญหาที่เป็นไปได้ประการหนึ่งคือ: หากreloadData()เสร็จสิ้นก่อนที่จะมีการlastIndexPathToDisplayตั้งค่าเซลล์ 'ที่มองเห็นได้ครั้งสุดท้าย' จะปรากฏขึ้นก่อนที่จะlastIndexPathToDisplayถูกตั้งค่าและจะไม่ถูกเรียกใช้ความสำเร็จ (และจะอยู่ในสถานะ 'รอ'):

self.mainTable.reloadData()

// cellForRowAt could be finished here, before setting `lastIndexPathToDisplay`

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

reloadData()ถ้าเราย้อนกลับเราจะจบลงด้วยความสมบูรณ์ถูกเรียกโดยการเลื่อนก่อน

self.lastIndexPathToDisplay = self.mainTable.indexPathsForVisibleRows?.last

// cellForRowAt could trigger the completion by scrolling here since we arm 'lastIndexPathToDisplay' before 'reloadData()'

self.mainTable.reloadData()

0

ลองสิ่งนี้:

tableView.backgroundColor = .black

tableView.reloadData()

DispatchQueue.main.async(execute: {

    tableView.backgroundColor = .green

})

สี tableView จะเปลี่ยนจากสีดำเป็นสีเขียวหลังจากreloadData()ฟังก์ชันเสร็จสมบูรณ์เท่านั้น


0

คุณสามารถใช้ฟังก์ชั่น performBatchUpdates ของ uitableview

นี่คือวิธีที่คุณจะประสบความสำเร็จ

self.tableView.performBatchUpdates({

      //Perform reload
        self.tableView.reloadData()
    }) { (completed) in

        //Reload Completed Use your code here
    }

0

การสร้างส่วนขยายที่ใช้ซ้ำได้ของ CATransaction:

public extension CATransaction {
    static func perform(method: () -> Void, completion: @escaping () -> Void) {
        begin()
        setCompletionBlock {
            completion()
        }
        method()
        commit()
    }
}

ตอนนี้สร้างส่วนขยายของ UITableView ที่จะใช้วิธีการขยายของ CATransaction:

public extension UITableView {
    func reloadData(completion: @escaping (() -> Void)) {
       CATransaction.perform(method: {
           reloadData()
       }, completion: completion)
    }
}

การใช้งาน:

tableView.reloadData(completion: {
    //Do the stuff
})

-2

คุณสามารถใช้เพื่อทำบางสิ่งหลังจากโหลดข้อมูลซ้ำ:

[UIView animateWithDuration:0 animations:^{
    [self.contentTableView reloadData];
} completion:^(BOOL finished) {
    _isUnderwritingUpdate = NO;
}];

-20

ลองตั้งค่าความล่าช้า:

[_tableView performSelector:@selector(reloadData) withObject:nil afterDelay:0.2];
[_activityIndicator performSelector:@selector(stopAnimating) withObject:nil afterDelay:0.2];

14
สิ่งนี้เป็นอันตราย จะเป็นอย่างไรหากใช้เวลาโหลดนานกว่าความล่าช้าของคุณ
ปล้น
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.