UINavigationBar with customized background - how to make rightBarButton visible - objective-c

I've customized background of navigation bar in my RootViewController (just part of code)
[navBar insertSubview:customBack atIndex:0];
I push detailViewController and add activity indicator as a rightBarButtonItem
UIActivityIndicatorView *actInd = [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(0, 0, 20, 20)];
actInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray;
self.activityIndicator = actInd;
[actInd release];
UIBarButtonItem * barButton = [[UIBarButtonItem alloc] initWithCustomView:self.activityIndicator];
[self.navigationItem setRightBarButtonItem:barButton];
[barButton release];
The problem is that the indicator is not visible but without customized background it works OK.

You can better change your navigationBar Background I think. Create a subclass of UINavigationBar and add:
- (void)drawRect:(CGRect)rect {
UIImage * image = [UIImage imageNamed:#"MyNavigationBarBackground.png"];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextScaleCTM(ctx, 1.0, -1.0); // Otherwise the image is drawn upside-down
CGContextDrawTiledImage(ctx, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage);
}
I do that and I never add a problem with any button :)

Zoleas has the right idea. If you don't or can't use a subclass, I think your problem is that you are adding your subview at index 0 so it is on top of the other views in the navigation bar, it is probably hiding your buttons.

set rightBarButtonItem with following way
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Exit" style:UIBarButtonItemStylePlain target:self action:#selector(cancel)];
Try this and also check the position of background

The activity indicator is there its just not being activated. Try animating it like this:
[actInd startAnimating];
Or if you want to keep it visible set the hideWhenStopped property like this:
actInd.hidesWhenStopped = NO;

Did you try to change the origin from the UIActivityIndicatorView?
Or creating it inside an UIView and add this UIView to the UIBarButtonItem.
I think the problem can be the frame from the element.

By this way you can resolve the problem:
Create activity indicator and add in a view1
Now this view1 add or create navigation bar with the help of this view1. Now when view1 is visible automatically you will be able to see the loading indicator.
Just wild try on this.

Related

Common UIButton or UIbarbutton to navigationbar

How can we add common UIButton or UIBarButton or any UI object so that it appears in all navigationbars in the application.
The below code will work perfectly;
UIImage* image3 = [UIImage imageNamed:#"info.png"];
CGRect frameimg = CGRectMake(0, 0, image3.size.width*4, image3.size.height);
UIButton *someButton = [[UIButton alloc] initWithFrame:frameimg];
//[someButton setBackgroundImage:image3 forState:UIControlStateNormal];
[someButton setTitle:#"Category" forState:UIControlStateNormal];
[someButton addTarget:self action:#selector(popUpPicker:)
forControlEvents:UIControlEventTouchUpInside];
[someButton setShowsTouchWhenHighlighted:YES];
UIBarButtonItem *mailbutton =[[UIBarButtonItem alloc] initWithCustomView:someButton];
self.navigationItem.rightBarButtonItem=mailbutton;
self.navigationItem.leftBarButtonItem = nil;
For these, you have to use category and then you have to call it in your every view controller to have the one code for every navigationabr button action.
There is no magic formula. One way or another, you have to add the same "UIButton or UIBarButton or any UI object" to the navigationItem of every UIViewController whose view will appear under this navigation controller's navigation bar.
You can reduce the amount of repeated code by creating this object in a single place, but the act of saying
self.navigationItem.rightBarButtonItem = thisItem
or whatever it is you want to do with the bar button item, will have to be performed separately and explicitly for every view controller.

customize UIBarButtonItem to have no border?

I'm trying to create a UIBarButtonItem that looks like the "list" button on the iPod.app
so I have 2 questions:
How can I set the icon to look like the iPod's one? I saw it in other apps, but couldn't find it on xcode. (the icon that appears on the right side of the navigation bar on the "now playing" screen)
how can I set the UIBarButtonItem's image to show without it's default border?
Try adding custom view with button you want as a subview to your bar;
UIView* myView = [[UIView alloc] initWithFrame:myBar.bounds];
UIbutton* myBtn = [[UIButton alloc] initWithFrame:CGRectMake(x,y,width,height)];
[myBtn setImage:myImage forState:UIControlStateNormal];
[myView addSubview:myBtn];
[myBar addSubview:myView];

Custom View with UILabel in it appears black

I have created a custom UIView that has some set of UILabels in it. I add those UILabels inside the custom view inside its drawRect method.
But the whole custom view appears black on the screen. I have not set any background color for the custom UIView. How Do I fix this?
I tried setting background color to ClearColor but it still looks black.
I tried setting opaque property of the custom view to false and the view obviously disappeared.
Please help.
don't do that in drawRect: method which is intended to draw in the graphic context. If you want to add some specific subviews do it in an init / initWithFrame method.
For me the best way is to create a custom uiviewcontroller subclass and initialize it using a xib (nib) file. Working at controller level is a good practice.
UIView *newView = [[UIView alloc] initWithFrame:CGRectMake(10,0,320,35)];
newView.backgroundColor=[UIColor clearColor];
UILabel *mytext = [[UILabel alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 28.0)];
mytext.backgroundColor = [UIColor clearColor];
mytext.textColor = [UIColor blackColor];
mytext.editable = NO;
mytext.text = #"Your label";
[newView addSubview:mytext];
[mytext release];
[self.view addSubview:newView];
[newView release];
Just incase someone stumbles upon this thread like I did in 2021. Check to see if you have accidentally toggled 'dark mode'. It will show similar visual 'issues' to the question above.

UIProgressView+Activity Indicator View on a UITableView during data upload

I have a uitableview.
I saw this "effect" and I would replay it:
When the user taps on a button to upload some data on a server, I would a gray (transparent) view with a progress bar (maybe with also an activity indicator) appears on the table.
The table is disabled and can be viewed through the gray view (that is the gray transparent view covers all the table).
How can I achieve this?
Have I create a view with a progressive view on it and then put it in the same xib of the table, disabling it properly programmatically? Or?
Lay a large black UIView over top of the UITableView with an alpha value of 0.5. Then put a spinner (UIActivityIndicatorView) on top of that.
Something like this:
UIView *view = [[UIView alloc] init];
view.frame = myTableView.frame;
// save this view somewhere
UIActivityIndicatorView *ac = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
CGRect frame = view.frame;
ac.center = CGPointMake(frame.size.width/2, frame.size.height/2);
[view addSubview:ac];
[ac startAnimating];
[ac release];
[myTableView addSubview:view];
[view release];
Then remove it later with [view removeFromSuperview]

How to create backBarButtomItem with custom view for a UINavigationController

I have a UINavigationController into which I push several views. Inside viewDidLoad for one of these views I want to set the self.navigationItem.backBarButtonItem to a custom view (based on a custom image). I don't know why, but it doesn't seem to work. Instead, I get the standard "back" button.
UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 63, 30)];
[backButton setImage:[UIImage imageNamed:#"back_OFF.png"] forState:UIControlStateNormal];
[backButton setImage:[UIImage imageNamed:#"back_ON.png"] forState:UIControlStateSelected];
UIBarButtonItem *backButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
self.navigationItem.backBarButtonItem = backButtonItem;
[backButtonItem release];
[backButton release];
I tested with a standard title and it worked. What is wrong with the above code ?
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Prout" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
Thanks for any help on this.
As of iOS5 we have an excellent new way of customizing the appearance of almost any control using the UIAppearance protocol, i.e. [UIBarButtonItem appearance]. The appearance proxy allows you to create application wide changes to the look of controls. Below is an example of a custom back button created with the appearance proxy.
Use the example code below to create a back button with custom images for normal and highlighted states. Call the following method from you appDelegate's application:didFinishLaunchingWithOptions:
- (void) customizeAppearance {
UIImage *i1 = [[UIImage imageNamed:#"custom_backButton_30px"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 6)];
UIImage *i2 = [[UIImage imageNamed:#"custom_backButton_24px"]
resizableImageWithCapInsets:UIEdgeInsetsMake(0, 15, 0, 6)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:i1
forState:UIControlStateNormal
barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:i2
forState:UIControlStateNormal
barMetrics:UIBarMetricsLandscapePhone];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:i1
forState:UIControlStateHighlighted
barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:i2
forState:UIControlStateHighlighted
barMetrics:UIBarMetricsLandscapePhone];
}
This is just a quick example. Normally you would want to have separate images for normal and highlighted (pressed) state.
If you are interested in customizing the appearance of other controls, some good examples can be found here: http://ios.biomsoft.com/2011/10/13/user-interface-customization-in-ios-5/
I'm fairly certain that the backBarButtonItem is a read-only property. Instead of modifying the backBarButtonItem, try setting a custom leftBarButtonItem and hide the backBarButtonItem:
self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithTitle:#"Prout" style:UIBarButtonItemStyleDone target:nil action:nil] autorelease];
self.navigationItem.hidesBackButton = YES;
You will also need to make sure you hook up the custom button to call the back action on the UINavigationBar.
I never been able to create a proper UIBarButtonItem with custom view and setBackBarButtonItem.
Here's the solution i found : let net UINavigationControllerDelegate handles everything! The trick here is to call the popViewControllerAnimated: method of the viewController.navigationController so you don't have to create any custom method.
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if([navigationController.viewControllers count ] > 1) {
UIView *backButtonView = [[UIView alloc] initWithFrame:CGRectMake(0,0,70,35)];
UIButton *myBackButton = [[UIButton buttonWithType:UIButtonTypeCustom] retain];
[myBackButton setFrame:CGRectMake(0,0,70,35)];
[myBackButton setImage:[UIImage imageNamed:#"back.png"] forState:UIControlStateNormal];
[myBackButton setEnabled:YES];
[myBackButton addTarget:viewController.navigationController action:#selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside];
[backButtonView addSubview:myBackButton];
[myBackButton release];
UIBarButtonItem* backButton = [[UIBarButtonItem alloc] initWithCustomView:backButtonView];
viewController.navigationItem.leftBarButtonItem = backButton;
[backButtonView release];
[backButton release];
}
}
If your end goal is to simply replace the image used for the back button, you can use a new method on UIBarButtonItem available in iOS 5.0:
setBackButtonBackgroundImage:forState:barMetrics:
Apple Docs:
http://developer.apple.com/library/ios/#documentation/UIKit/Reference/UIBarButtonItem_Class/Reference/Reference.html
Here's a simple example that sets a custom background image for all back buttons in your app:
UIImage *toolbarBackButtonBackgroundPortrait = [[UIImage imageNamed:#"toolbarBackButtonPortrait"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 17, 0, 6)];
UIImage *toolbarBackButtonBackgroundLandscape = [[UIImage imageNamed:#"toolbarBackButtonLandscape"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 17, 0, 6)];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:toolbarBackButtonBackgroundPortrait forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
[[UIBarButtonItem appearance] setBackButtonBackgroundImage:toolbarBackButtonBackgroundLandscape forState:UIControlStateNormal barMetrics:UIBarMetricsLandscapePhone];
I would be willing to bet that this is a bug on Apple's part as I am running into the exact same problem. The reason being is that I can get a custom UIBarButtonItem to appear, but only when I don't try to use the initWithCustomView: method. Per the API, the navigation controller checks the following things when a new view controller is pushed:
If the new top-level view controller has a custom left bar button item, that item is displayed. To specify a custom left bar button item, set the leftBarButtonItem property of the view controller’s navigation item.
If the top-level view controller does not have a custom left bar button item, but the navigation item of the previous view controller has a valid item in its backBarButtonItem property, the navigation bar displays that item.
If a custom bar button item is not specified by either of the view controllers, a default back button is used and its title is set to the value of the title property of the previous view controller—that is, the view controller one level down on the stack. (If there is only one view controller on the navigation stack, no back button is displayed.)
My case (as well as yours) is 2. I specify code exactly the same as yours (i.e., creating a UIButton, setting its image properties for various states, creating a UIBarButtonItem, initializing it with the UIButton, then setting my current view controller's backBarButtonItem property to the UIBarButtonItem); however, when I later push my view controller, nothing at all is displayed on the left-hand side of my navigation controller. Strangely, I can click where the "Back" button should be, and it pops the view controller.
Interestingly, if I create a UIBarButtonItem using the initWithTitle:style:target:action: method instead of the initWithCustomView: method, it does show a custom button with a custom title. Also, as Travis mentioned, using the leftBarButtonItem property instead works just fine. I'd rather adhere to the sanctioned logic, however, by specifying the "Back" button for the current view controller -- to be displayed later when a new view controller is pushed -- instead of creating a left button for the next view controller, which, arguably, should have no concern for anything pertaining to the view controller that came before it. :-\
Set the backBarButtonItem before pushing the viewController with the navigationController. Setting the backBarButtonItem in viewDidLoad doesn't work.
Say I have MyTableViewController. When the user taps a particular row I want to push AnotherViewController using the navigationController. The code looks something like this:
// in MyTableViewController's tableView:didSelectRowAtIndexPath method...
UIBarButtonItem *backButton = [[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:#"yourImage.png"]
style:UIBarButtonItemStyleBordered
target:nil
action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
[self.navigationController pushViewController:anotherViewController];
When anotherViewController is displayed the back button in the navigation bar will have #"yourImage.png" and the default back button style (rectangular arrow). Also note it's fine to pass nil as target. The button behaves like the back button.
Even though is already answered this worked for me:
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"backArrow.png"] style:UIBarButtonItemStyleBordered target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
[backButton release];
BTW: even in iOS4 initializing my back button with initWithCustomView: didn't work for me. ;(
I too have been having problems with customView on a navigationItem.backBarButtonItem. I suspect it's probably just b0rked in the SDK.
While the workarounds outlined here do work, I've come up with a solution which is a little more elegant: it still uses navigationItem.leftBarButtonItem, but takes care of it for you automagically (no more need for 'child' view controllers to need to know anything about their 'parent' and/or to manually set navigationItem.leftBarButtonItem).
First up, have some class be a UINavigationControllerDelegate for the UINavigationController whose back button you're interested in. Then, in this class, set up something like the following willShowViewController delegate method:
-(void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated{
// reference to view controller stack
NSArray *viewControllers = [ navigationController viewControllers ];
if( [ viewControllers count ] > 1 ){
// the view controller we'll be linking to
UIViewController *backViewController = [ viewControllers objectAtIndex: [ viewControllers count ] - 2 ];
// create custom UIBarButtonItem
UIBarButtonItem *leftButton = [[ UIBarButtonItem alloc ] initWithCustomView: someCustomView ];
// set it as the leftBarButtonItem on the incoming viewcontroller
viewController.navigationItem.leftBarButtonItem = leftButton;
// tidy up
[ leftButton release ];
}
}
I had some further problems with this; it seems that UIBarButtonItem.action and UIBarButtonItem.target don't work when it's a navigationItem.leftBarButtonItem. So, you're left with a custom back button that doesn't actually go back. I'll leave responding to touches in your custom view as an exercise for the reader (I used a UIButton), but you'll need add this method to your delegate class:
-(void)onDummyBackButtonTapped{
[ someNavigationController popViewControllerAnimated: YES ];
}
and hook it up to fire when your custom view is tapped.
backBarButtonItem is not a read-only
property. I'm not sure why it behaves
so strangely, and the above is a valid
(if less-than-ideal) workaround.
It behaves strangely because setting a vc's backBarButtonItem doesn't change anything about the appearance of the vc's navigation item - instead, it changes the button that points BACK to the vc. See updating the navigation bar from Apple FMI.
That said I haven't had a whole lot of luck getting it to work myself. If you look around this site, you'll find some threads that suggest placing code very similar to what you already have immediately before the call to push a new view on the stack. I've had some luck there, but unfortunately not when it comes to using a custom image.
The navigationController's backBarButtonItem is set on the item whose title you're trying to affect.
i.e. in Page 1's view controller, say, viewdidLoad:
self.title = #"Page 1 of 4";
self.navigationItem.backBarButtonItem =
[[[UIBarButtonItem alloc] initWithTitle:#"Page 1"
style:UIBarButtonItemStyleBordered
target:nil
action:nil] autorelease];
You would not override this in Page 2.
Documentation for UINavigationItem : backBarButtonItem makes this clear:
When this item is the back item of the
navigation bar—when it is the next
item below the top item—it may be
represented as a back button on the
navigation bar. Use this property to
specify the back button. The target
and action of the back bar button item
you set should be nil. The default
value is a bar button item displaying
the navigation item’s title.
This is how I create a custom square back button with an arrow instead of the usual text.
I simply setup a delegate for my UINavigationController. I use the app delegate for that because the window root view controller is the UINavigationController i want to control.
So AppDelegate.m (ARC):
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
((UINavigationController*)_window.rootViewController).delegate = self;
return YES;
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
if(navigationController.viewControllers.count > 1) {
UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setImage:[UIImage imageNamed:#"back-arrow.png"] forState:UIControlStateNormal];
[button setBackgroundColor:[UIColor grayColor]];
button.frame = CGRectMake(0, 0, 44, 44);
viewController.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:button];
[button addEventHandler:^(id sender) {
[navigationController popViewControllerAnimated:YES];
} forControlEvents:UIControlEventTouchUpInside];
}
}
I'm using BlocksKit to catch the button tap event. It's very convenient for stuff like this but you can also use the regular addTarget:action:forControlEvents: method
I think I found the solution for this.
Simply set the button on the navigation item on the previous controller (The one that you want to go back to)
So if I have for example a root controller
and I push a second controller and want to customize the back button then I should do the following:
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc]
initWithImage:[UIImage imageNamed:#"ico.png"] style:UIBarButtonItemStyleBordered
target:nil action:nil] autorelease];
Where self is the root view controller and not the second one.
My Logic:
Create a custom button (aka a custom view subclass)
Initialize a barbutton item with the custom button/view
Add an action that allows us to "go back" to our previous view controller
Set the left bar button item to this custom bar button item you created
Hide the back bar button item of the view controller you're pushing to
Step 3 was important. I figured the cleanest way to simulate the "go back" was to just utilize UINavigationController's method (popViewControllerAnimated:). So, I just add that action to the navigationController of the viewController I'm pushing (viewControllerToPush) like so:
[navItemButton addTarget:viewControllerToPush.navigationController action:#selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside];
Example:
UIViewController *viewControllerToPush = [[UIViewController alloc] init];
UIImage *navImage = [UIImage imageNamed:#"your_back_button_image"];
UIButton *navItemButton = [UIButton buttonWithType:UIButtonTypeCustom];
[navItemButton setImage:navImage forState:UIControlStateNormal];
[navItemButton setImage:navImage forState:UIControlStateHighlighted];
[navItemButton setFrame:CGRectMake(0, 0, navImage.size.width, navImage.size.height)];
[navItemButton addTarget:viewControllerToPush.navigationController action:#selector(popViewControllerAnimated:) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *barButtonItem = [[UIBarButtonItem alloc] initWithCustomView:navItemButton];
viewControllerToPush.navigationItem.leftBarButtonItem = barButtonItem;
viewControllerToPush.navigationItem.hidesBackButton = YES;
[self.navigationController pushViewController:viewControllerToPush animated:YES];
You can use leftBarButtonItem instead of back button item. And to remove the default back button item set it to nil like follows;
navigationController?.navigationBar.backIndicatorImage = nil
navigationController?.navigationBar.backIndicatorTransitionMaskImage = nil
let button = UIButton.init(type: .custom)
button.imageView?.contentMode = UIViewContentMode.scaleAspectFit
button.setImage(UIImage.init(named: "top_back"), for: UIControlState.normal)
button.frame = CGRect.init(x: 0, y: 0, width: 75, height: 50)
button.addTarget(self, action: #selector(handleBackButton), for: .touchUpInside)
let barButton = UIBarButtonItem.init(customView: button)
self.navigationItem.leftBarButtonItem = barButton