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?
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];
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;
}
Related
I have a class that subclasses ABNewPersonViewController. As I understand, when the Done button in the navigation bar is clicked, the delegate method
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
gets called. But before entering the delegate method, the changes will be saved to address book.
What I need is, as soon as the save button gets pressed. I need to load a UIView with 2 buttons asking the user,
whether he wants the changes and
whether he should abort the changes made,
But this should be done before the changes are reflected in the address book. And only on the click of the first button in the UIView, that the changes should be saved in the address book.
On the click of the 2nd button, the view should disappear and I should return to the view controller class from where the UIView is loaded.
My question is, how will I load the view on save button click, before the changes are reflected in the address book
I have created a custom save button
UIBarButtonItem *okBtn = self.navigationItem.rightBarButtonItem;
UIBarButtonItem *saveBtn = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:okBtn.target action:okBtn.action];
self.navigationItem.rightBarButtonItem =saveBtn;
[saveBtn release];
On the save button action, the control goes to the delegate method
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person` .
I want the control go to my custom method, where I can load my UIView before the edits are saved in the address book.
Edit:
When I load the ABNewPersonViewController
ABPersonViewController *displayVcardViewController = (ABPersonViewController*)[self.navigationController visibleViewController];
ABRecordRef person = displayVcardViewController.displayedPerson;
EditAddressNewPersonDetailViewController *newVCardViewController = [[EditAddressNewPersonDetailViewController alloc] init];
newVCardViewController.displayedPerson = person;
newVCardViewController.newPersonViewDelegate = self;
newVCardViewController.isEditingMode = YES;
[self.navigationController setToolbarHidden:YES];
[self.navigationController pushViewController:newVCardViewController animated:YES];
[newVCardViewController release];
Isn't this strong reference already or else Where should I include the strong reference.
On
- (void)actionSave:(UIBarButtonItem *)sender {
if([[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil]) {
[self.myView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview:self.myView];
UIActionSheet * action = [[UIActionSheet alloc]initWithTitle:#""
delegate:self
cancelButtonTitle:#"Do"
destructiveButtonTitle:#"Cancel"
otherButtonTitles: nil];
action.tag = 101;
[action showInView:self.view];
[action release];
}
}
I am loading a UIView with UIAlertView over it.
Update: Starting with iOS 7.0, ABNewPersonViewController is not subclassable anymore and this won't work.
First, keep a reference to the default rightBarButtonItem before overriding it.
If you're subclassing ABNewPersonViewController, your viewDidLoad would look like:
- (void)viewDidLoad {
[super viewDidLoad];
// Store the old button item into a custom property
// #property (nonatomic, retain) UIBarButtonItem *defaultRightBarButtonItem;
self.defaultRightBarButtonItem = self.navigationItem.rightBarButtonItem;
UIBarButtonItem *saveBtn = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self
action:#selector(actionSave:)];
self.navigationItem.rightBarButtonItem = saveBtn;
[saveBtn release];
}
And you call the default action on the default target in your custom action method:
- (void)actionSave:(UIBarButtonItem *)sender {
// Do what you want to do before the data is saved
// ....
// ....
// Trigger the default action
[self.defaultRightBarButtonItem.target
performSelector:self.defaultRightBarButtonItem.action
withObject:self.defaultRightBarButtonItem.target];
}
When pressing infobutton it is not displaying ModalView
UIBarButtonItem *infoItem = [[UIBarButtonItem alloc]
initWithTitle:#"Info"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(displayModalView:)];
- (void)displayModalView
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.viewController = [[Infoviewcontroller alloc] init];
UINavigationController *navigationController=[[UINavigationController alloc] init];
navigationController.navigationBar.tintColor = [UIColor brownColor];
[navigationController pushViewController:_viewController animated:YES];
[_window addSubview:navigationController.view];
self.window.rootViewController = self.viewController;
[self.window makeKeyAndVisible];
}
Anyone can help me please what is the problem in this.
Thanks a lot in advance for helping me out
In your question you didn't specify how you created your objects (the toolBar and the buttons on it), are you creating them from Xcode by dragging and dropping or from pure code, therefore I will try to point out the common issues for both cases.
First, I am assuming that you are using Xcode and dragging the components that you like. In this case you need to create in the .h file an Outlet that will be linked to the button on the bar as follows:
#interface yourViewController : UIViewController
{
UIBarButtonItem *barButton;
}
#property (nonatomic, retain) IBOutlet UIBarButtonItem *barButton;
- (void) barButtonPress;
Notice that I added a function that will handle the bar button press. Now you need to link this Outlet to the bar button item, simply in Xcode in the Connection Inspector where it says New Referencing Outlet drag to the File's Owner box (the yellow cube).
Now in the viewDidLoad add the following:
[barButton setTarget:self];
[barButton setAction:#selector(barButtonPress)];
This code will link your bar button to the function that you want to be called when you press it. Now for the view that you like to view Modal, I assume that you already #import it also in the .h file, lets call it MyViewModal.
Inside the function that will be called when you press the bar button:
- (void) barButtonPress
{
MyViewModal *myViewModal = [[MyViewModal alloc] initWithNibName:#"MyViewModal" bundle:nil];
[self presentModalViewController:myViewModal animated:YES];
}
That's all, it will be displayed in Modal View. Keep in mind the allocating the new view is done based on your needs, here I did the simplest case just for illustration.
UPDATE: If not using Xcode
If you are not using Xcode then you should have a toolbar already defined say it is named myToolBar. To add buttoms to the tool bar we use the myToolbar.items way therefore we need to prepare the buttons with their targets before adding them. Here is a workflow:
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeInfoLight];
[infoButton addTarget:self action:#selector(barButtonPress) forControlEvents:UIControlEventAllEvents]; //same function as above
UIBarButtonItem *btn = [[[UIBarButtonItem alloc] initWithCustomView:infoButton] autorelease];
myTool.items = [NSArray arrayWithObjects:btn, nil];
This should do it for you.
I am currently adding a UISegmentedControl to the toolbar in the navigation controller programmatically (as seen below).
This approach works fine, I have my UISegmentedControl, it fires the selector that I have setup no problems.
Problem is - I would like to use the selectedIndex of this control in order to query my data model and present a different view of data for each 'segment' - but I am having trouble getting the selectedIndex.
In my travels I have been consulting the 'Top Songs' example code provided by Apple.
In this code they build up their interface via UISegmentedControl object in the view controller and IB. In doing so they can access the UISegmentedControl's selectedIndex. I am adding mine programmactically and do not have this freedom.
'SHOULD' I have a UISegmentedControl defined in my view controller? If so, if I want to continue building my menu programmactically, how do I go about accessing the information from the control buried within the navigation controller's UIToolBar?
I'm clearly missing something basic. Any assistance is always greatly appreciated :)
[self.navigationController setToolbarHidden:NO];
// Set up the edit and add buttons.
self.navigationItem.leftBarButtonItem = self.editButtonItem;
UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(insertNewObject)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
NSArray *tabitems = [NSArray arrayWithObjects:#"ONE", #"TWO", #"THREE", #"FOUR", nil];
UISegmentedControl *tabs = [[UISegmentedControl alloc] initWithItems:tabitems];
[tabs addTarget:self
action:#selector(pickedSegment:)
forControlEvents:UIControlEventValueChanged];
tabs.segmentedControlStyle = UISegmentedControlStyleBar;
tabs.frame = CGRectMake(60, 8, 180, 30);
tabs.selectedSegmentIndex = 0;
//[self.navigationController.navigationBar addSubview:tabs];
[self.navigationController.toolbar addSubview:tabs];
[tabs release];
You need to have tabs defined in your .h file -
#interface YourViewController : UIViewController
....
UISementedControl *tabs;
....
#end
....
#property (nonatomic, retain) UISegmentedControl *tabs;
Then, after the [tabs release]; line, you should still be able to access the object, as it is a retained property - access the selectedItemIndex as normal.
I have an UITableViewController which I would like to add UIToolbar to with one button. In the
- (void)viewDidLoad;
method of UITableViewController I have:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *button = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self
action:#selector(pressButton1:)];
self.navigationItem.title = #"Some title";
self.navigationItem.leftBarButtonItem = button;
}
Unfortunately I don't see the toolbar when I run my app.
Any hints? Should I do something more?
The navigationItem property of a view controller is useless if that controller is not displayed inside a UINavigationController.
If your view controller is inside a navigation controller I don't know what the problem is.
Otherwise you can use an UINavigationItem but you need to create a UINavigationBar yourself.
Either in the Interface Builder (add a UINavigationBar and add a UINavigationItem, then connect the UINavigationItem to a property outlet declared your view controller (you don't need to connect the Bar).
Or in your code :
UIBarButtonItem *item = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
target:self action:#selector(pressButton1:)];
UINavigationItem* navItem = [[UINavigationItem alloc] init];
navItem.rightBarButtonItem = item;
navItem.title = #"Your title";
naviBar = [[UINavigationBar alloc] init];
naviBar.items = [NSArray arrayWithObject:navItem];
naviBar.frame = CGRectMake(0.0, 0.0, self.view.frame.size.width, 44.0);
[self.view addSubview:naviBar];
[navItem release];
Your method requires an autorelease:
- (void)viewDidLoad {
[super viewDidLoad];
UIBarButtonItem *button = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:#selector(pressButton1:)] autorelease];
self.navigationItem.title = #"Some title";
self.navigationItem.leftBarButtonItem = button;
}
There's nothing wrong with your code per se. Your question states you want to add an UIToolBar to your view? Really? Or do you just want to add a button to the NavigationItem for UITableView?
If you don't have to use a UITableViewController and are not using a UINavigationController in your app already, you can set your view controller up as a regular UIViewController with a toolbar and tableview.
To do this in IB, drag out a UIViewController object and add a toolbar and tableview. Hook up outlets for both and set the delegate and datasource of the tableview to Files Owner. Add any other toolbar items or buttons and give them outlets and methods if you need them for buttons, etc. In your ViewController.h file, make sure you sign it up to conform to the UITableViewDataSource and UITabBarDelegate:
#interface ViewController : UIViewController <UITableViewDataSource, UITabBarDelegate>
From there, just build out your tableview delegate and datasource methods like you normally would, and write your button action methods for any buttons you added to the toolbar.
You just didn't show the toolbar. It is hidden by default. To fix it, you just put this line of code:
self.navigationController.toolbarHidden = NO;
I tried it and it worked. Just make sure that you put in the implementation file's viewDidLoad method.
I am working on an iPhone's view which composed 3 elements, UITextView, UIToolBar with an UIBarButtonItem.
The goal is, I want UIBarButtonItem change its style from 'edit' (UIBarButtonSystemItemEdit) to 'Done' (UIBarButtonSystemItemDone) and update new selector to new method.
First of all, I have tried following code but it doesn't work:
Could you help me on this idea?
There is a builtin bar button with this behaviour, you get it via the editButtonItem property of a UIViewContoller. Tabbing that button will change the view controller it came from into editing mode, and toggle the button into a done button.
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
If you have added the button through IB then make sure to set the identifier to Custom
Also allocate a button in the .h with appropriate IBOutlet and Property
Synthesize the button in .m
Then in your code do the following:
// Set to done
editButton.style = UIBarButtonItemStyleDone;
editButton.title = #"Done";
// Set back to edit
editButton.style = UIBarButtonItemStyleBordered;
editButton.title = #"Edit";
to change the button the Done button use this
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleDone];
to change the button to Edit button use this
[self.navigationItem.rightBarButtonItem setStyle:UIBarButtonItemStyleBordered];
I ended up doing something like this. Unfortunately, setting the title directly did not work, for some reason it was nil and would not let me set it to a different value. The self.editButton comes from an IBOutlet with the target and actions set. This code uses ARC. I hope this helps someone.
NSString *title = app.settings.editing
? NSLocalizedString(#"Done", #"")
: NSLocalizedString(#"Edit", #"");
UIBarButtonItemStyle style = app.settings.editing
? UIBarButtonItemStyleDone
: UIBarButtonItemStyleBordered;
UIBarButtonItem *editButton
= [[UIBarButtonItem alloc] initWithTitle:title
style:style
target:self.editButton.target
action:self.editButton.action];
self.navigationItem.rightBarButtonItem = editButton;