Question

I have a custom cell that is loaded at the bottom of my collection view. Its only job is to display an activity indicator view - which happens while the app is making a new work call.

So I added it to the cell like so:

BBLoaderCell *loaderCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"LoaderCell" forIndexPath:indexPath];


UIActivityIndicatorView * activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
activityIndicator.hidesWhenStopped = YES;
activityIndicator.hidden = NO;
activityIndicator.center = loaderCell.imageView.center;
activityIndicator.tag = 10;

[loaderCell.imageView addSubview:activityIndicator];

    [activityIndicator startAnimating];
    return loaderCell;

This shows an activity indicator in the last cell of my view - however it does not spin. Any ideas?

Solution Found

As per comments - it was a threading issue. I also took the suggestion to move the code to the custom nib file for the cell.

here is the refactored code:

BBLoaderCell *loaderCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"LoaderCell" forIndexPath:indexPath];
    loaderCell.backgroundColor = [UIColor clearColor];

     if (self.loadingMore == NO){
        dispatch_async(dispatch_get_main_queue(), ^{
            activityIndicator.hidden = YES;
            [loaderCell.spinner stopAnimating];
        });
    }
     else if (self.loadingMore == YES){
    dispatch_async(dispatch_get_main_queue(), ^{
        activityIndicator.hidden = NO;
        [loaderCell.spinner startAnimating];
    });
    }

    return loaderCell;

This is working as I need it to.

Thanks guys!

Was it helpful?

Solution 2

I think this might have something to do with threading (eg. the UI on main thread not being ready to animate the indicator). You could try something like the following:

[activityIndicator performSelector:@selector(startAnimating:) withObject:nil afterDelay:1];

This is just a possible suggestion though, you should try different variations and check which one forces the animation onto the main thread correctly and in a timely matter. Another variation:

dispatch_async(dispatch_get_main_queue(), ^{
    [activityIndicator startAnimating];
});

OTHER TIPS

You most likely call reloadData more than once in a very short time. Maybe already in the next frame. The activity indicator hides when stops by default. This is what happens:

  1. At first reloadData it shows the spinner
  2. A second reloadData reloads also the cell and the spinner hides itself
  3. Now the spinner is hidden
  4. You scroll around but the cell get's reused. The spinner is still hidden

You can set activityIndicator.hidesWhenStopped = false. The spinner should now be always visible. But it's not spinning.

Adding this will resume the spinning.

override func prepareForReuse() {
   activityIndicator.startAnimating()
}

Another issue might be the color. Maybe the spinner has the same color as the background. Try to set the color of the spinner like this:

activityIndicator.color = .red

You can check if the spinner is there by using the "Debug View Hierarchy" Button Debug View Hierarchie

It will show you the wireframe of the current visible screen Wireframe

A neater solution would be to start the animation in the willDisplay delegate method:

public func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
  (cell as? BBLoaderCell)?.activityIndicatorView.startAnimating()
}

Since on iOS 10 or later, prefetching is enabled by default, there could be just 1 cellForItemAt invocation even when the cell is scrolled in and out of the screen multiple times. And when the cell is scrolled out of the screen it seems that stopAnimating() is called (isAnimating also becomes false). So using an async delay inside cellForItemAt may fail after the cell enters the screen the second time. It is still true on iOS 11

Since willDisplay will be called whenever the cell enters the screen, we can re-start the animation there reliably

In my case i was using an activityIndicator on a customCell for a tableView and I had the same problem, even after writting down activityIndicator.startAnimating() the loading was not moving.

After hours of trying i came out with a solution.

override func prepareForReuse() {
       activityIndicator.startAnimating()
}

Just use the method prepareForReuse() show when you reuse the customCell the activityIndicator is animating

If subclass your UIActivityIndicatorView and put a breakpoint in the stopAnimation method you can see that on cell dequeue a private method is called that removes all animations. Which causes the indicator to be hidden or just stopped (if hidesWhenStopped is set to false).

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top