Question

I have added a refresh UIBarButtonItem to my navigation bar on my iPhone app. When the user taps the button I'd like the refresh button to change to the animated activity indicator and once the operation (in this case a download) is complete switch the activity indicator back to the refresh button.

I have added the refresh button using IB. Then on the button tap I create a new activity indicator and keep an pointer to the original refresh button. Like so:

refreshButtonItem = self.navigationItem.leftBarButtonItem;
if (activityButtonItem == nil)
{
    activityIndicator = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20,20)];
    activityButtonItem = [[UIBarButtonItem alloc]initWithCustomView:activityIndicator];

}
self.navigationItem.leftBarButtonItem = activityButtonItem;
[activityIndicator startAnimating];

So far, so good. Problem is that when my download finishes and I try to re-add the refresh button (using the following):

[activityIndicator stopAnimating];
self.navigationItem.leftBarButtonItem = refreshButtonItem;

I get the following error:
[UIBarButtonItem retain]: message sent to deallocated instance

I'm not explicitly calling release.

A) When/where is this being deallocated

B) Is there a better way to achieve what I'm looking for?

Was it helpful?

Solution

When you assign the activityButtonItem to leftBarButtonItem, the item that leftBarButtonItem used to point to is released. The leftBarButtonItem (and all properties with the retain option) are implemented similarly to this:

- (void)leftBarButtonItem:(UIBarButtonItem *)newItem {
  if (newItem != self.leftBarButtonItem) {
    [self.leftBarButtonItem release];
    leftBarButtonItem = [newItem retain];
  }
}

If you want to use the refreshButtonItem after reassigning the leftBarButtonItem, change your first line to:

refreshButtonItem = [self.navigationItem.leftBarButtonItem retain];

OTHER TIPS

Since iOS 5 with the introduction of ARC you no longer need to do retain.

Solution can be obtained as @cagreen explained while refreshButtonItem can be stored as class property, as well as loadingButton and loadingView.

In your interface declare:

@property (strong, nonatomic) UIBarButtonItem *refreshButton;
@property (strong, nonatomic) UIBarButtonItem *loadingButton;
@property (strong, nonatomic) UIActivityIndicatorView *loadingView;

Init loadingButton and loadingView in your viewDidLoad method:

self.loadingView = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
self.loadingButton = [[UIBarButtonItem alloc] initWithCustomView:self.loadingView];

Then to show the loading spinner you can simply do:

 // Shows loading button
- (void)showLoadingView {

    // Keep reference to right bar button
    if (self.navigationItem.rightBarButtonItem) {
        self.refreshButton = self.navigationItem.rightBarButtonItem;
    }

    // Start animating and assign loading button to right bar button
    [self.loadingView startAnimating];
    self.navigationItem.rightBarButtonItem = self.loadingButton;
}

And to hide:

 // Hides loading button
- (void)hideLoadingView {
    [self.loadingView stopAnimating];
    self.navigationItem.rightBarButtonItem = self.refreshButton;
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top