我如何知道UICollectionView已完全加载?


98

每当UICollectionView完全加载时,我都必须执行一些操作,即那时应调用所有UICollectionView的数据源/布局方法。我怎么知道的?是否有任何委托方法可以知道UICollectionView的加载状态?

Answers:


62
// In viewDidLoad
[self.collectionView addObserver:self forKeyPath:@"contentSize" options:NSKeyValueObservingOptionOld context:NULL];

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary  *)change context:(void *)context
{
    // You will get here when the reloadData finished 
}

- (void)dealloc
{
    [self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];
}

1
当我向后导航时,这会崩溃,否则效果很好。
ericosg

4
@ericosg添加[self.collectionView removeObserver:self forKeyPath:@"contentSize" context:NULL];-viewWillDisappear:animated:为好。
pxpgraphics

辉煌!非常感谢你!
Abdo 2015年

41
滚动时内容大小也会更改,因此不可靠。
Ryan Heitner

我已经尝试过使用异步rest方法获取数据的集合视图。这意味着在触发此方法时,可见单元始终为空,这意味着我一开始会滚动到集合中的某个项目。
Chucky

155

这对我有用:

[self.collectionView reloadData];
[self.collectionView performBatchUpdates:^{}
                              completion:^(BOOL finished) {
                                  /// collection-view finished reload
                              }];

Swift 4语法:

self.collectionView.reloadData()
self.collectionView.performBatchUpdates(nil, completion: {
    (result) in
    // ready
})

1
在iOS 9上为我工作
Magoo,2016年

5
@ G.Abhisek Errrr,当然....您需要解释什么?第一行reloadData刷新了collectionView它,因此它再次使用了它的数据源方法... performBatchUpdates附加了一个完成块,因此,如果两者都在主线程上执行,则您知道替换的任何代码都/// collection-view finished reload将重新执行并进行布局collectionView
Magoo

8
当collectionView具有动态大小的单元格时
它对

2
此方法是完美的解决方案,没有任何头痛。
arjavlad

1
@ingaham这对动态大小的单元格非常适合我,Swift 4.2 IOS 12
Romulo BM

27

实际上非常简单。

例如,当您调用UICollectionView的reloadData方法或其布局的invalidateLayout方法时,请执行以下操作:

dispatch_async(dispatch_get_main_queue(), ^{
    [self.collectionView reloadData];
});

dispatch_async(dispatch_get_main_queue(), ^{
    //your stuff happens here
    //after the reloadData/invalidateLayout finishes executing
});

工作原理:

主线程(我们应该在其中进行所有UI更新)包含主队列,该主队列本质上是串行的,即它以FIFO方式工作。因此,在上面的示例中,第一个块被调用,其中reloadData调用了我们的方法,第二个块中随后调用了其他任何东西。

现在,主线程也正在阻塞。因此,如果您reloadData需要3秒钟来执行,则第二个块的处理将被这3秒钟推迟。


2
我实际上只是对此进行了测试,该块在所有其他委托方法之前被调用。这样不行吗?
taylorcressy 2014年

@taylorcressy嘿,我已经更新了代码,现在我在2个生产应用程序中使用了这些代码。还包括说明。
dezinezync 2014年

对我不起作用。当我调用reloadData时,collectionView在第二个块执行后请求单元格。所以我遇到了与@taylorcressy似乎相同的问题。
拉斐尔2014年

1
您可以尝试将第二个通话区放在第一个通话区中吗?未验证,但可能有效。
dezinezync 2014年

6
reloadData现在,将单元的加载放入主线程中(即使从主线程调用也是如此)。因此,返回cellForRowAtIndexPath:后实际上并未加载单元格(未调用)reloadData。包装要在单元格加载后执行的代码,dispatch_async(dispatch_get_main...并在调用后对其进行调用reloadData将获得所需的结果。
Tylerc230 '16

12

只是为了添加一个很棒的@dezinezync答案:

迅捷3+

collectionView.collectionViewLayout.invalidateLayout() // or reloadData()
DispatchQueue.main.async {
    // your stuff here executing after collectionView has been layouted
}

@nikas我们必须添加的视图确实出现了!
SARATH SASI

1
不一定,您可以在最终加载布局时从任何要执行操作的地方调用它。
nikans '17

我在插入项目后试图使我的collectionView最终滚动时出现滚动闪烁,此问题已解决,谢谢。
Skoua

6

像这样做:

       UIView.animateWithDuration(0.0, animations: { [weak self] in
                guard let strongSelf = self else { return }

                strongSelf.collectionView.reloadData()

            }, completion: { [weak self] (finished) in
                guard let strongSelf = self else { return }

                // Do whatever is needed, reload is finished here
                // e.g. scrollToItemAtIndexPath
                let newIndexPath = NSIndexPath(forItem: 1, inSection: 0)
                strongSelf.collectionView.scrollToItemAtIndexPath(newIndexPath, atScrollPosition: UICollectionViewScrollPosition.Left, animated: false)
        })

4

尝试在reloadData()调用之后立即通过layoutIfNeeded()强制进行同步布局传递。似乎适用于iOS 12上的UICollectionView和UITableView。

collectionView.reloadData()
collectionView.layoutIfNeeded() 

// cellForItem/sizeForItem calls should be complete
completion?()

谢啦!很长一段时间以来,我一直在寻找解决该问题的方法。
Trzy Gracje

3

作为dezinezync回答,你需要的是派遣到主队列后的代码块reloadData由一个UITableViewUICollectionView,然后这个块将细胞后执行离队

为了使使用时更直观,我将使用如下扩展名:

extension UICollectionView {
    func reloadData(_ completion: @escaping () -> Void) {
        reloadData()
        DispatchQueue.main.async { completion() }
    }
}

它也可以实现UITableView


3

使用RxSwift / RxCocoa的另一种方法:

        collectionView.rx.observe(CGSize.self, "contentSize")
            .subscribe(onNext: { size in
                print(size as Any)
            })
            .disposed(by: disposeBag)

1
我喜欢这个 performBatchUpdates在我的情况下不起作用,这个做到了。干杯!
佐尔坦

@MichałZiobro,可以在某个地方更新代码吗?仅当contentSize更改时才应调用该方法?因此,除非您在每个滚动条上都进行更改,否则它应该起作用!
阮钦(Chinh Nguyen),

1

这对我有用:

__weak typeof(self) wself= self;
[self.contentCollectionView performBatchUpdates:^{
    [wself.contentCollectionView reloadData];
} completion:^(BOOL finished) {
    [wself pageViewCurrentIndexDidChanged:self.contentCollectionView];
}];

2
废话……这还没有完成。
Magoo

1

我使用了以下方法:在收集视图加载到用户可见之前,需要对所有可见单元格执行一些操作:

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    if shouldPerformBatch {
        self.collectionView.performBatchUpdates(nil) { completed in
            self.modifyVisibleCells()
        }
    }
}

请注意,在浏览集合视图时将调用此方法,因此,为了避免这种开销,我添加了:

private var souldPerformAction: Bool = true

在动作本身中:

private func modifyVisibleCells() {
    if self.shouldPerformAction {
        // perform action
        ...
        ...
    }
    self.shouldPerformAction = false
}

该动作仍将执行多次,以初始状态下的可见单元数为准。但在所有这些调用中,您将拥有相同数量的可见单元格(全部)。用户开始与集合视图进行交互之后,布尔标志将阻止其再次运行。


1

重新加载集合视图后,我只是执行以下操作来执行任何操作。您甚至可以在API响应中使用此代码。

self.collectionView.reloadData()

DispatchQueue.main.async {
   // Do Task after collection view is reloaded                
}

1

到目前为止,我发现的最佳解决方案是使用CATransaction它来处理完成。

迅捷5

CATransaction.begin()
CATransaction.setCompletionBlock {
    // UICollectionView is ready
}

collectionView.reloadData()

CATransaction.commit()

0

Def这样做:

//Subclass UICollectionView
class MyCollectionView: UICollectionView {

    //Store a completion block as a property
    var completion: (() -> Void)?

    //Make a custom funciton to reload data with a completion handle
    func reloadData(completion: @escaping() -> Void) {
        //Set the completion handle to the stored property
        self.completion = completion
        //Call super
        super.reloadData()
    }

    //Override layoutSubviews
    override func layoutSubviews() {
        //Call super
        super.layoutSubviews()
        //Call the completion
        self.completion?()
        //Set the completion to nil so it is reset and doesn't keep gettign called
        self.completion = nil
    }

}

然后像这样在VC中调用

let collection = MyCollectionView()

self.collection.reloadData(completion: {

})

确保您正在使用子类!


0

这项工作对我来说:


- (void)viewDidLoad {
    [super viewDidLoad];

    int ScrollToIndex = 4;

    [self.UICollectionView performBatchUpdates:^{}
                                    completion:^(BOOL finished) {
                                             NSIndexPath *indexPath = [NSIndexPath indexPathForItem:ScrollToIndex inSection:0];
                                             [self.UICollectionView scrollToItemAtIndexPath:indexPath atScrollPosition:UICollectionViewScrollPositionCenteredHorizontally animated:NO];
                                  }];

}


0

只需在批处理更新中重新加载collectionView,然后在布尔值“ finish”的帮助下检查完成块是否完成。

self.collectionView.performBatchUpdates({
        self.collectionView.reloadData()
    }) { (finish) in
        if finish{
            // Do your stuff here!
        }
    }

0

以下是对我有用的唯一方法。

extension UICollectionView {
    func reloadData(_ completion: (() -> Void)? = nil) {
        reloadData()
        guard let completion = completion else { return }
        layoutIfNeeded()
        completion()
    }
}

-1

这就是我解决Swift 3.0问题的方式:

override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)

    if !self.collectionView.visibleCells.isEmpty {
        // stuff
    }
}

这行不通。VisibleCells将在第一个单元格加载后立即具有内容...问题是我们想知道何时最后一个单元格已加载。
Travis M.

-9

试试这个:

- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
    return _Items.count;
}

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
    UICollectionViewCell *cell;
    //Some cell stuff here...

    if(indexPath.row == _Items.count-1){
       //THIS IS THE LAST CELL, SO TABLE IS LOADED! DO STUFF!
    }

    return cell;
}

2
这是不正确的,只有在该单元格可见的情况下,cellFor才会被调用,在这种情况下,最后一个可见项将不等于项的总数……
Jirune

-10

你可以这样...

  - (void)reloadMyCollectionView{

       [myCollectionView reload];
       [self performSelector:@selector(myStuff) withObject:nil afterDelay:0.0];

   }

  - (void)myStuff{
     // Do your stuff here. This will method will get called once your collection view get loaded.

    }

尽管此代码可以回答问题,但是提供有关为什么和/或如何回答问题的其他上下文将大大提高其长期价值。请编辑您的答案以添加一些说明。
Toby Speight 2016年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.