Simple question: in the main view controller of my app (which is in a navigation controller), I am customizing the nav bar with something like this:
self.navigationItem.titleView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"titleImage"]];
UIButton *menuButton = [[UIButton alloc] init];
[menuButton setImage:[UIImage imageNamed:#"menuIcon"] forState:UIControlStateNormal];
[menuButton setFrame:CGRectMake(0, 0, 34, 34)];
UIBarButtonItem *menuItem = [[UIBarButtonItem alloc] initWithCustomView:menuButton];
self.navigationItem.rightBarButtonItem = menuItem;
I want these elements - the title view and the right bar button - to remain consistent throughout the app as I push and pop new view controllers onto and off of my navigation controller.
Of course, I could just set my custom items up in viewDidLoad of every view controller that is pushed onto my navigation stack, but this means that during the animation between two view controllers, my custom items are animated in and out, which is not as clean as I would like.
Any suggestions on how I would go about maintaining those custom elements on my nav bar when switching from vc to vc? Thanks!
You could create your own navigation item class that subclasses UIButton and set all the appearance in that class, then just set your navigation items as those custom UIButtons.
Something like this:
In CustomButton.h
#import <UIKit/UIKit.h>
#interface CustomButton : UIButton
#end
Then in CustomButton.m
#implementation CustomButton
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
//Set images etc.
[menuButton setImage:[UIImage imageNamed:#"menuIcon"] forState:UIControlStateNormal];
[menuButton setFrame:CGRectMake(0, 0, 34, 34)];
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Then in your viewController:
#import CustomButton.h
- (void)viewDidLoad
{
CustomButton *button = [[CustomButton alloc]init];
self.navigationItem.leftBarButton = [[UIBarButtonItem alloc] initWithCustomView:button];
}
Related
In my iOS App I have the below code snipped repeating over and over again in every class.
I have tried to cast the method into a NSObject class but I receive errors for the use of "navigationItem".
-(void)customDesign {
//background pattern
self.view.backgroundColor = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"BG-pattern.png"]];
// nav bar
[self.navigationController.navigationBar setBackgroundImage:[UIImage imageNamed:#"top_bar.png"] forBarMetrics:UIBarMetricsDefault];
//back button color #2974c3
[self.navigationController.navigationBar setTintColor:[UIColor colorWithRed:41.0/255.0f green:116.0/255.0f blue:195.0/255.0f alpha:1.0]];
//settings button
UIImage* settingsImage = [UIImage imageNamed:#"ButtonMenu.png"];
CGRect frameimg = CGRectMake(0, 0, settingsImage.size.width, settingsImage.size.height);
UIButton *uiSettingsButton = [[UIButton alloc] initWithFrame:frameimg];
[uiSettingsButton setBackgroundImage:settingsImage forState:UIControlStateNormal];
[uiSettingsButton addTarget:self action:#selector(menuButton) forControlEvents:UIControlEventTouchUpInside];
[uiSettingsButton setShowsTouchWhenHighlighted:YES];
//add buton to navbar
UIBarButtonItem *settingsButton = [[UIBarButtonItem alloc] initWithCustomView:uiSettingsButton];
self.navigationItem.leftBarButtonItem = settingsButton;
}
You should be creating your own subclass of UIViewController or UITableViewController (whichever you're using) and place this code in your viewDidLoad method of that subclass. All the controllers that need this code can then extend your custom class. This will save you the hassle of having to write this code everywhere.
Your custom subclass:
#interface CustomViewController : UIViewController
#end
Your viewDidLoad method in the subclass:
#implementation CustomViewController
- (void)viewDidLoad {
[super viewDidLoad];
// all your custom code here...
}
#end
Extend your custom class for controllers you want to have common stuff done in:
#interface VisuallyChangedViewController : CustomViewController
#end
#implementation VisuallyChangedViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
#end
I have UIView controller class where i have programmatically created a UINAvigationController
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = #"Hmm";
navigationController = [[UINavigationController alloc] init];
//without this instruction, the tableView appears blocked
navigationController.view.frame = CGRectMake(0, 0, 768, 1004); // <-- nav controller should fill the screen
navigationController.navigationBar.barStyle = UIBarStyleDefault;
navigationController.title = #"Hello";
[navigationController.navigationBar setHidden:NO];
[self.view addSubview:navigationController.view];
CGRect frame = CGRectMake(100, 70, 200, 50);
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.frame = frame;
[button setTitle:#"Bejoy" forState:UIControlStateNormal];
[button addTarget:self action:#selector(myButtonClick:) forControlEvents:(UIControlEvents)UIControlEventTouchDown];
[self.view addSubview:button];
}
Now in the button click event
- (void)myButtonClick:(id)sender {
SAPRetailExCustomerSalesOrderDetailViewTVC *customerSalesOrderDetailViewTVC = [[SAPRetailExCustomerSalesOrderDetailViewTVC alloc] initWithNibName:#"SAPRetailExCustomerSalesOrderDetailViewTVC" bundle:nil];
[navigationController pushViewController:customerSalesOrderDetailViewTVC animated:YES];
[customerSalesOrderDetailViewTVC release];
}
Here in the output the view is navigating but the button remains in my view!!!
Also i have to click twice to get the back button in my NavigationController.
Is it because i don't have a title for my navigation bar. How will i set the title here?.
Can someone help me??
Your UIViewcontroller should be the child of the navigation controller and not the other way round. Your UIViewController is not responsible for its own navigation controller.
Use UINavigationController's -initWithRootViewController: where you create your UIViewController object and add the navigation controller's view to the window.
What you are doing is wrong: You add the navigation controller's view to the view controller's one!
I have an application for which I use TabBar template. In one of viewcontrollers I want to add a uinavigationcontroller. I declare it in the .h file;
#import <UIKit/UIKit.h>
#import "AnotherViewController.h"
#interface SecondViewController : UIViewController <UINavigationControllerDelegate> {
UIButton *UIButton *gotoAnotherView;;
AnotherViewController *anotherView;
UINavigationController *navigationController;
}
#property(nonatomic,retain) UIButton *UIButton *gotoAnotherView;;
#property(nonatomic,retain) AnotherViewController *anotherView;
#property(nonatomic,retain) UINavigationController *navigationController;
-(void)buttonPressed:(id)sender;
#end
And here's my .m file
#import "SecondViewController.h"
#implementation SecondViewController
#synthesize navigationController, anotherView, gotoAnotherView;
-(void)buttonPressed:(id)sender {
anotherView = [[AnotherViewController alloc]init];
[navigationController pushViewController:anotherView animated:YES];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
navigationController = [[UINavigationController alloc ]initWithRootViewController:self];
[navigationController.navigationBar setFrame:CGRectMake(0, 0, 320, 44)];
[self.view addSubview:navigationController.navigationBar];
gotoAnotherView = [[UIButton alloc] initWithFrame:CGRectMake(50, 50, 40, 40)]; //kategoributonlari
UIImage *image = [UIImage imageNamed:#"1.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:image];
imageView.frame = CGRectMake(110, 5, 100, 20);
[self.view addSubview:imageView];
[kategori1 setBackgroundImage:image forState:UIControlStateNormal];
[kategori1 addTarget:self
action:#selector(buttonPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:kategori1];
[super viewDidLoad];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)didReceiveMemoryWarning
{
// Releases the view if it doesn't have a superview.
[super didReceiveMemoryWarning];
// Release any cached data, images, etc. that aren't in use.
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
// e.g. self.myOutlet = nil;
}
However I can see from the navigation bar that the navigationcontroller goes one level deeper(back button appears) but the main view remains the same with my gotoAnotherView button.
I think that I might not make the navigationcontroller control the whole view.
Instead of trying to do this in code, edit the XIB for your main window (with the UITabBarController). Drag out a UINavigationController from the Library onto the tab bar. This will create a new bar item for you, with a UINavigationController. Select the UIViewController nested in the new UINavigationController, and on the Identity tab set the Class to your view controller, and on the Attributes tab specify the name of the nib file to load.
You don't need to use IB. You can setup everything in code. First create your view controllers tab1ViewController, tab2ViewController, etc. then create the navigation controller with the root view controllers of tab1ViewController etc. and then add these controllers to the tab bar controller.
Here is a sample:
UINavigationController *tab1NavigationController = [[UINavigationController alloc] initWithRootViewController:tab1ViewController];
UINavigationController *tab2NavigationController = [[UINavigationController alloc] initWithRootViewController:tab2ViewController];
UITabBarController rootViewController = [[UITabBarController alloc] init];
rootViewController.viewControllers = [NSArray arrayWithObjects:tab1NavigationController, tab2NavigationController, nil];
[tab1NavigationController release];
[tab2NavigationController release];
For an application I'm developing, I need to display a custom back button in a navigation bar. I have the button asset as a PNG image, and I'm writing this code:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
backButton.frame = CGRectMake(0, 0, 79, 29.0);
[backButton setImage:[UIImage imageNamed:#"button_back.png"] forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = [[[UIBarButtonItem alloc] initWithCustomView:backButton] autorelease];
}
When I push this view controller, the custom button does not show up, and instead I get the standard back button with the title of this view controller inside.
Things I already tried:
Doubled check that the button backButton is created properly, by adding it to the view hierarchy. It displays properly.
In the same method, changed the title property of the navigationItem and confirmed that it changes (as expected) the content of my back button.
Can anyone spot what I'm doing wrong? Did anyone succeed in using a custom image as the back button on with a UINavigationController?
Starting with iOS 5, this is simple:
[[UIBarButtonItem appearance]
setBackButtonBackgroundImage:[UIImage imageNamed:#"back_button.png"]
forState:UIControlStateNormal barMetrics:UIBarMetricsDefault];
You can place that in your app delegate and it will set the background image to all back buttons in the app (for that control state and bar metrics, of course).
I'm reposting my solution from https://stackoverflow.com/a/16831482/171933:
I create a simple category on UIViewController:
UIViewController+ImageBackButton.h
#import <UIKit/UIKit.h>
#interface UIViewController (ImageBackButton)
- (void)setUpImageBackButton;
#end
UIViewController+ImageBackButton.m
#import "UIViewController+ImageBackButton.h"
#implementation UIViewController (ImageBackButton)
- (void)setUpImageBackButton
{
UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 34, 26)];
[backButton setBackgroundImage:[UIImage imageNamed:#"back_arrow.png"] forState:UIControlStateNormal];
UIBarButtonItem *barBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
[backButton addTarget:self action:#selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.leftBarButtonItem = barBackButtonItem;
self.navigationItem.hidesBackButton = YES;
}
- (void)popCurrentViewController
{
[self.navigationController popViewControllerAnimated:YES];
}
#end
Now all you have to do is #import UIViewController+ImageBackButton.h in either all of your view controllers or in a custom base view controller class that your other view controllers inherit from and implement the viewWillAppear: method:
- (void)viewWillAppear:(BOOL)animated
{
[self setUpImageBackButton];
}
That's all. Now you have an image back button everywhere. Without a border. Enjoy!
The backBarButtonItem property works as intended, but it will always add its standard button shape and color based on the navigation bar tint color.
You can customize the text, but not replace the image.
One workaround, as Andrew Pouliot suggested, is to use leftBarButtonItem instead, but I stuck to the standard button instead.
Isn't the simplest solution just to design it from your storyboard with whatever image or colors you want, and just drag a new action to your controller?
Swift Code
#IBAction func backButton(sender: AnyObject) {
self.navigationController?.popViewControllerAnimated(true)
}
Confusingly backBarButtonItem is not what you're looking for.
It just controls the title on the back button for the next view controller. What you want is to set the leftBarButtonItem to your custom back button.
Johannes Fahrenkrug's Answer works, but the back image would appear at a very wired position.
Here I found a better way to position the image at the right place:
Make Sure You Have a back image with size 24x24(#1x) , I call it backImage
Execute the following code when your app Launch
UINavigationBar.appearance().barTintColor = nil
UINavigationBar.appearance().backIndicatorImage = backImage
UINavigationBar.appearance().backIndicatorTransitionMaskImage = backImage
UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffsetMake(0, -60), forBarMetrics: .Default)
See this answer here: How to create backBarButtomItem with custom view for a UINavigationController
You just need to set the backBarButtonItem property on the navigationController before pushing the viewController. Setting the backBarButtonItem property in the viewController's viewDidLoad method (for example) doesn't work.
I do not think that ViewController itself should know anything about its back button
According to OOP this is the responsibility of containerViewController in which your view controller is inserted, for example UINavigationController.
Subclass your NavigationController and overload in it superClass method like this:
#implementation STONavigationController
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
[super pushViewController:viewController animated:animated];
if ([self.viewControllers indexOfObject:viewController] != NSNotFound &&
[self.viewControllers indexOfObject:viewController] > 0){
UIImage *img = [UIImage imageNamed:#"back-1"];
UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, img.size.width * 2, img.size.height * 2)];
[backButton setBackgroundImage:img forState:UIControlStateNormal];
UIBarButtonItem *barBackButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
[backButton addTarget:self action:#selector(popCurrentViewController) forControlEvents:UIControlEventTouchUpInside];
viewController.navigationItem.leftBarButtonItem = barBackButtonItem;
viewController.navigationItem.hidesBackButton = YES;
}
}
- (void)popCurrentViewController
{
[self popViewControllerAnimated:YES];
}
#end
Simply create a UIBarButtonItem instead of an embedded UIButton in UIBarButtonItem. Works fine!
UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithImage:[UIImage imageNamed:#"button_back.png"] style:UIBarButtonItemStyleBordered target:nil action:nil];
self.navigationItem.backBarButtonItem = backButton;
As #pgb suggested 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
In short, I have a root controller RootController presenting a modal navigation controller showing Controller1, which itself presents a modal navigation controller showing Controller2.
I want to dismiss Controller1 and Controller2 at the same time by sending the root controller a dismissModalViewControllerAnimated: message.
I expected to see an animation of Controller2 being dismissed (or rather its navigation controller) and NOT see Controller1 in the process, taking me back to the root controller, as per the documentation:
If you present several modal view
controllers in succession, and thus
build a stack of modal view
controllers, calling this method on a
view controller lower in the stack
dismisses its immediate child view
controller and all view controllers
above that child on the stack. When
this happens, only the top-most view
is dismissed in an animated fashion;
any intermediate view controllers are
simply removed from the stack. The
top-most view is dismissed using its
modal transition style, which may
differ from the styles used by other
view controllers lower in the stack.
However, Controller2 is being dismissed instantaneously without animation and I can see Controller1 being dismissed (with an animation).
It might be a misunderstanding on my part of the documentation. If it is, could someone help me find a solution?
Here is a sample code that will demonstrate my problem (all superfluous code removed, no memory management, no error handling...):
// AppDelegate.h:
#import <UIKit/UIKit.h>
#import "RootController.h"
#interface AppDelegate : NSObject <UIApplicationDelegate> {
IBOUTLET UIWindow *window;
RootController *rootController;
}
#end
// AppDelegate.m:
#import "AppDelegate.h"
#implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
rootController = [[RootController alloc] init];
[window addSubview:rootController.view];
[window makeKeyAndVisible];
return YES;
}
#end
// RootController.h:
#import <UIKit/UIKit.h>
#import "Controller1.h"
#interface RootController : UIViewController {
Controller1 *controller1;
UINavigationController *controller1navigationController;
UIButton *button;
}
#end
// RootController.m:
#import "RootController.h"
#implementation RootController
- (void)testMe:(id)target {
controller1 = [[Controller1 alloc] init];
controller1navigationController = [[UINavigationController alloc] initWithRootViewController:controller1];
[self presentModalViewController:controller1navigationController animated:YES];
}
- (void)loadView {
[super loadView];
button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[button setTitle:#"Test me" forState:UIControlStateNormal];
button.frame = CGRectMake(50, 200, 220, 50);
[button addTarget:self action:#selector(testMe:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
}
#end
// Controller1.h:
#import <UIKit/UIKit.h>
#import "Controller2.h"
#interface Controller1 : UIViewController {
Controller2 *controller2;
UINavigationController *controller2navigationController;
UIButton *button;
}
#end
// Controller1.m:
#import "Controller1.h"
#implementation Controller1
- (void)testMe:(id)target {
controller2 = [[Controller2 alloc] init];
controller2navigationController = [[UINavigationController alloc] initWithRootViewController:controller2];
[self presentModalViewController:controller2navigationController animated:YES];
}
- (void)loadView {
[super loadView];
button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[button setTitle:#"Test me 1" forState:UIControlStateNormal];
button.frame = CGRectMake(50, 156, 220, 50);
[button addTarget:self action:#selector(testMe:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
self.view.backgroundColor = [UIColor greenColor];
}
#end
// Controller2.h:
#import <UIKit/UIKit.h>
#interface Controller2 : UIViewController {
UIButton *button;
}
#end
// Controller2.m:
#import "Controller2.h"
#implementation Controller2
- (void)testMe:(id)target {
[self.parentViewController.parentViewController.parentViewController dismissModalViewControllerAnimated:YES];
}
- (void)loadView {
[super loadView];
button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[button setTitle:#"Test me 2" forState:UIControlStateNormal];
button.frame = CGRectMake(50, 156, 220, 50);
[button addTarget:self action:#selector(testMe:) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:button];
self.view.backgroundColor = [UIColor redColor];
}
#end
Thanks for helping me find a solution to this problem.
You really shouldn't use such a big stack of viewcontrollers. The problem with this approach is that all those viewcontrollers stay in memory until you dismiss the first one which can cause memory warnings if those controllers get heavy.
If you still want to use that approach I would suggest using the delegate design pattern. So basically add a protocol for the first controller:
#protocol RootControllerDelegate
-(void) dismissModal;
#end
and assign a property to the next controllers
#property (nonatomic, assign) id <RootControllerDelegate> rootdelegate;
then just call [self.rootdelegate dismissModal] where you want them to disappear. This will visually look like only the last controller disappearing animated.
The better way to do this is making a RootViewController and adding the other viewcontrollers as subviews to it where you can remove them from memory when switching, see How does UITabBarController work?