I have an iOS 7 app where I am setting a custom back button like this:
UIImage *backButtonImage = [UIImage imageNamed:#"back-button"];
UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
[backButton setImage:backButtonImage forState:UIControlStateNormal];
backButton.frame = CGRectMake(0, 0, 20, 20);
[backButton addTarget:self
action:#selector(popViewController)
forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *backBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
viewController.navigationItem.leftBarButtonItem = backBarButtonItem;
But this disables the iOS 7 "swipe left to right" gesture to navigate to the previous controller. Does anyone know how I can set a custom button and still keep this gesture enabled?
EDIT:
I tried to set the viewController.navigationItem.backBarButtonItem instead, but this doesn't seem to show my custom image.
IMPORTANT:
This is a hack. I would recommend taking a look at this answer.
Calling the following line after assigning the leftBarButtonItem worked for me:
self.navigationController.interactivePopGestureRecognizer.delegate = self;
Edit:
This does not work if called in init methods. It should be called in viewDidLoad or similar methods.
Use the backIndicatorImage and backIndicatorTransitionMaskImage properties of the UINavigationBar if at all possible. Setting these on an a UIAppearanceProxy can easily modify behavior across your application. The wrinkle is that you can only set those on ios 7, but that works out because you can only use the pop gesture on ios 7 anyway. Your normal ios 6 styling can remain intact.
UINavigationBar* appearanceNavigationBar = [UINavigationBar appearance];
//the appearanceProxy returns NO, so ask the class directly
if ([[UINavigationBar class] instancesRespondToSelector:#selector(setBackIndicatorImage:)])
{
appearanceNavigationBar.backIndicatorImage = [UIImage imageNamed:#"back"];
appearanceNavigationBar.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
//sets back button color
appearanceNavigationBar.tintColor = [UIColor whiteColor];
}else{
//do ios 6 customization
}
Trying to manipulate the interactivePopGestureRecognizer's delegate will lead to a lot of issues.
I saw this solution http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/ which subclasses UINavigationController. Its a better solution as it handles the case where you swipe before the controller is in place - which causes a crash.
In addition to this I noticed if you do a swipe on the root view controller (after pushing on one, and back again) the UI becomes unresponsive (also same problem in answer above).
So the code in the subclassed UINavigationController should look like so:
#implementation NavigationController
- (void)viewDidLoad {
[super viewDidLoad];
__weak NavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Hijack the push method to disable the gesture
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.interactivePopGestureRecognizer.enabled = NO;
}
[super pushViewController:viewController animated:animated];
}
#pragma mark - UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animate {
// Enable the gesture again once the new controller is shown
self.interactivePopGestureRecognizer.enabled = ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] && [self.viewControllers count] > 1);
}
#end
I use
[[UINavigationBar appearance] setBackIndicatorImage:[UIImage imageNamed:#"nav_back.png"]];
[[UINavigationBar appearance] setBackIndicatorTransitionMaskImage:[UIImage imageNamed:#"nav_back.png"]];
[UIBarButtonItem.appearance setBackButtonTitlePositionAdjustment:UIOffsetMake(0, -64) forBarMetrics:UIBarMetricsDefault];
Here is swift3 version of Nick H247's answer
class NavigationController: UINavigationController {
override func viewDidLoad() {
super.viewDidLoad()
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.delegate = self
delegate = self
}
}
override func pushViewController(_ viewController: UIViewController, animated: Bool) {
if responds(to: #selector(getter: interactivePopGestureRecognizer)) {
interactivePopGestureRecognizer?.isEnabled = false
}
super.pushViewController(viewController, animated: animated)
}
}
extension NavigationController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
interactivePopGestureRecognizer?.isEnabled = (responds(to: #selector(getter: interactivePopGestureRecognizer)) && viewControllers.count > 1)
}
}
extension NavigationController: UIGestureRecognizerDelegate {}
I also hide the back button, replacing it with a custom leftBarItem.
Removing interactivePopGestureRecognizer delegate after push action worked for me:
[self.navigationController pushViewController:vcToPush animated:YES];
// Enabling iOS 7 screen-edge-pan-gesture for pop action
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.delegate = nil;
}
navigationController.interactivePopGestureRecognizer.delegate = (id<UIGestureRecognizerDelegate>)self;
This is from http://stuartkhall.com/posts/ios-7-development-tips-tricks-hacks, but it causes several bugs:
Push another viewController into the navigationController when swiping in from the left edge of the screen;
Or, swipe in from the left edge of the screen when the topViewController is popping up from the navigationController;
e.g. When the rootViewController of navigationController is showing, swipe in from the left edge of the screen, and tap something(QUICKLY) to push anotherViewController into the navigationController, then
The rootViewController does not respond any touch event;
The anotherViewController will not be shown;
Swipe from the edge of the screen again, the anotherViewController will be shown;
Tap the custom back button to pop the anotherViewController, crash!
So you must implement UIGestureRecognizerDelegate method in self.navigationController.interactivePopGestureRecognizer.delegate like this:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if (gestureRecognizer == navigationController.interactivePopGestureRecognizer) {
return !navigationController.<#TODO: isPushAnimating#> && [navigationController.viewControllers count] > 1;
}
return YES;
}
Try self.navigationController.interactivePopGestureRecognizer.enabled = YES;
I did not write this, but the following blog helped a lot and solved my issues with custom navigation button:
http://keighl.com/post/ios7-interactive-pop-gesture-custom-back-button/
In summary, he implements a custom UINavigationController that uses the pop gesture delegate. Very clean and portable!
Code:
#interface CBNavigationController : UINavigationController <UINavigationControllerDelegate, UIGestureRecognizerDelegate>
#end
#implementation CBNavigationController
- (void)viewDidLoad
{
__weak CBNavigationController *weakSelf = self;
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
self.interactivePopGestureRecognizer.delegate = weakSelf;
self.delegate = weakSelf;
}
}
// Hijack the push method to disable the gesture
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
{
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = NO;
[super pushViewController:viewController animated:animated];
}
#pragma mark UINavigationControllerDelegate
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
// Enable the gesture again once the new controller is shown
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)])
self.interactivePopGestureRecognizer.enabled = YES;
}
Edit. Added fix for problems when a user tries to swipe left on a root view controller:
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
if ([self respondsToSelector:#selector(interactivePopGestureRecognizer)] &&
self.topViewController == [self.viewControllers firstObject] &&
gestureRecognizer == self.interactivePopGestureRecognizer) {
return NO;
}
return YES;
}
RootView
override func viewDidAppear(_ animated: Bool) {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = false
}
ChildView
override func viewDidLoad() {
self.navigationController?.interactivePopGestureRecognizer?.isEnabled = true
self.navigationController?.interactivePopGestureRecognizer?.delegate = self
}
extension ChildViewController: UIGestureRecognizerDelegate {}
Use this logic to keep enable or disable the swipe gesture..
- (void)navigationController:(UINavigationController *)navigationController
didShowViewController:(UIViewController *)viewController
animated:(BOOL)animate
{
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)])
{
if (self.navigationController.viewControllers.count > 1)
{
self.navigationController.interactivePopGestureRecognizer.enabled = YES;
}
else
{
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
}
}
}
I had a similar problem where I was assigning the current view controller as the delegate for the interactive pop gesture, but would break the gesture on any views pushed, or views underneath the view in the nav stack. The way I solved this was to set the delegate in -viewDidAppear, then set it to nil in -viewWillDisappear. That allowed my other views to work correctly.
Imagine we are using Apple's default master/detail project template, where master is a table view controller and tapping on it will show the detail view controller.
We want to customize the back button that appears in the detail view controller. This is how to customize the image, image color, text, text color, and font of the back button.
To change the image, image color, text color, or font globally, place the following in a location that is called before any of your view controllers are created (e.g. application:didFinishLaunchingWithOptions: is a good place).
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UINavigationBar* navigationBarAppearance = [UINavigationBar appearance];
// change the back button, using default tint color
navigationBarAppearance.backIndicatorImage = [UIImage imageNamed:#"back"];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the back button, using the color inside the original image
navigationBarAppearance.backIndicatorImage = [[UIImage imageNamed:#"back"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
navigationBarAppearance.backIndicatorTransitionMaskImage = [UIImage imageNamed:#"back"];
// change the tint color of everything in a navigation bar
navigationBarAppearance.tintColor = [UIColor greenColor];
// change the font in all toolbar buttons
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[[UIBarButtonItem appearance] setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
return YES;
}
Note, you can use appearanceWhenContainedIn: to have more control over which view controllers are affected by these changes, but keep in mind that you can't pass [DetailViewController class], because it is contained inside a UINavigationController, not your DetailViewController. This means you will need to subclass UINavigationController if you want more control over what is affected.
To customize the text or the font/color of a specific back button item, you must do so in the MasterViewController (not the DetailViewController!). This seems unintuitive because the button appears on the DetailViewController. However once you understand that the way to customize it is by setting a property on a navigationItem, it begins to make more sense.
- (void)viewDidLoad { // MASTER view controller
[super viewDidLoad];
UIBarButtonItem *buttonItem = [[UIBarButtonItem alloc] initWithTitle:#"Testing"
style:UIBarButtonItemStylePlain
target:nil
action:nil];
NSDictionary *barButtonTitleTextAttributes =
#{
NSFontAttributeName: [UIFont fontWithName:#"HelveticaNeue-Light" size:12.0],
NSForegroundColorAttributeName: [UIColor purpleColor]
};
[buttonItem setTitleTextAttributes:barButtonTitleTextAttributes forState:UIControlStateNormal];
self.navigationItem.backBarButtonItem = buttonItem;
}
Note: attempting to set the titleTextAttributes after setting self.navigationItem.backBarButtonItem doesn't seem to work, so they must be set before you assign the value to this property.
Create a class 'TTNavigationViewController' which is subclass of 'UINavigationController' and make your existing navigation controller of this class either in storyboard/class, Example code in class -
class TTNavigationViewController: UINavigationController, UIGestureRecognizerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.setNavigationBarHidden(true, animated: false)
// enable slide-back
if self.responds(to: #selector(getter: UINavigationController.interactivePopGestureRecognizer)) {
self.interactivePopGestureRecognizer?.isEnabled = true
self.interactivePopGestureRecognizer?.delegate = self
}
}
func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}}
Related
I have the code below that hides and shows the navigational bar. It is hidden when the first view loads and then hidden when the "children" get called. Trouble is that I cannot find the event/action to trigger it to hide again when they get back to the root view....
I have a "test" button on the root page that manually does the action but it is not pretty and I want it to be automatic.
-(void)hideBar
{
self.navController.navigationBarHidden = YES;
}
-(void)showBar
{
self.navController.navigationBarHidden = NO;
}
The nicest solution I have found is to do the following in the first view controller.
Objective-C
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated:animated];
[super viewWillDisappear:animated];
}
Swift
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: animated)
super.viewWillDisappear(animated)
}
This will cause the navigation bar to animate in from the left (together with the next view) when you push the next UIViewController on the stack, and animate away to the left (together with the old view), when you press the back button on the UINavigationBar.
Please note also that these are not delegate methods, you are overriding UIViewController's implementation of these methods, and according to the documentation you must call the super's implementation somewhere in your implementation.
Another approach I found is to set a delegate for the NavigationController:
navigationController.delegate = self;
and use setNavigationBarHidden in navigationController:willShowViewController:animated:
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated
{
// Hide the nav bar if going home.
BOOL hide = viewController != homeViewController;
[navigationController setNavigationBarHidden:hide animated:animated];
}
Easy way to customize the behavior for each ViewController all in one place.
One slight tweak I had to make on the other answers is to only unhide the bar in viewWillDisappear if the reason it is disappearing is due to a navigation item being pushed on it. This is because the view can disappear for other reasons.
So I only unhide the bar if this view is no longer the topmost view:
- (void) viewWillDisappear:(BOOL)animated
{
if (self.navigationController.topViewController != self)
{
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
[super viewWillDisappear:animated];
}
I would put the code in the viewWillAppear delegate on each view being shown:
Like this where you need to hide it:
- (void)viewWillAppear:(BOOL)animated
{
[yourObject hideBar];
}
Like this where you need to show it:
- (void)viewWillAppear:(BOOL)animated
{
[yourObject showBar];
}
The currently accepted answer does not match the intended behavior described in the question. The question asks for the navigation bar to be hidden on the root view controller, but visible everywhere else, but the accepted answer hides the navigation bar on a particular view controller. What happens when another instance of the first view controller is pushed onto the stack? It will hide the navigation bar even though we are not looking at the root view controller.
Instead, #Chad M.'s strategy of using the UINavigationControllerDelegate is a good one, and here is a more complete solution. Steps:
Subclass UINavigationController
Implement the -navigationController:willShowViewController:animated method to show or hide the navigation bar based on whether it is showing the root view controller
Override the initialization methods to set the UINavigationController subclass as its own delegate
Complete code for this solution can be found in this Gist. Here's the navigationController:willShowViewController:animated implementation:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
/* Hide navigation bar if root controller */
if ([viewController isEqual:[self.viewControllers firstObject]]) {
[self setNavigationBarHidden:YES animated:animated];
} else {
[self setNavigationBarHidden:NO animated:animated];
}
}
in Swift 3:
override func viewWillAppear(_ animated: Bool) {
navigationController?.navigationBar.isHidden = true
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
if (navigationController?.topViewController != self) {
navigationController?.navigationBar.isHidden = false
}
super.viewWillDisappear(animated)
}
Give my credit to #chad-m 's answer.
Here is the Swift version:
Create a new file MyNavigationController.swift
import UIKit
class MyNavigationController: UINavigationController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.delegate = self
}
func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
if viewController == self.viewControllers.first {
self.setNavigationBarHidden(true, animated: animated)
} else {
self.setNavigationBarHidden(false, animated: animated)
}
}
}
Set your UINavigationController's class in StoryBoard to MyNavigationController
That's it!
Difference between chad-m's answer and mine:
Inherit from UINavigationController, so you won't pollute your rootViewController.
use self.viewControllers.first rather than homeViewController, so you won't do this 100 times for your 100 UINavigationControllers in 1 StoryBoard.
After multiple trials here is how I got it working for what I wanted.
This is what I was trying.
- I have a view with a image. and I wanted to have the image go full screen.
- I have a navigation controller with a tabBar too. So i need to hide that too.
- Also, my main requirement was not just hiding, but having a fading effect too while showing and hiding.
This is how I got it working.
Step 1 - I have a image and user taps on that image once. I capture that gesture and push it into the new imageViewController, its in the imageViewController, I want to have full screen image.
- (void)handleSingleTap:(UIGestureRecognizer *)gestureRecognizer {
NSLog(#"Single tap");
ImageViewController *imageViewController =
[[ImageViewController alloc] initWithNibName:#"ImageViewController" bundle:nil];
godImageViewController.imgName = // pass the image.
godImageViewController.hidesBottomBarWhenPushed=YES;// This is important to note.
[self.navigationController pushViewController:godImageViewController animated:YES];
// If I remove the line below, then I get this error. [CALayer retain]: message sent to deallocated instance .
// [godImageViewController release];
}
Step 2 - All these steps below are in the ImageViewController
Step 2.1 - In ViewDidLoad, show the navBar
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
NSLog(#"viewDidLoad");
[[self navigationController] setNavigationBarHidden:NO animated:YES];
}
Step 2.2 - In viewDidAppear, set up a timer task with delay ( I have it set for 1 sec delay). And after the delay, add fading effect. I am using alpha to use fading.
- (void)viewDidAppear:(BOOL)animated
{
NSLog(#"viewDidAppear");
myTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(fadeScreen) userInfo:nil repeats:NO];
}
- (void)fadeScreen
{
[UIView beginAnimations:nil context:nil]; // begins animation block
[UIView setAnimationDuration:1.95]; // sets animation duration
self.navigationController.navigationBar.alpha = 0.0; // Fades the alpha channel of this view to "0.0" over the animationDuration of "0.75" seconds
[UIView commitAnimations]; // commits the animation block. This Block is done.
}
step 2.3 - Under viewWillAppear, add singleTap gesture to the image and make the navBar translucent.
- (void) viewWillAppear:(BOOL)animated
{
NSLog(#"viewWillAppear");
NSString *path = [[NSBundle mainBundle] pathForResource:self.imgName ofType:#"png"];
UIImage *theImage = [UIImage imageWithContentsOfFile:path];
self.imgView.image = theImage;
// add tap gestures
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
[self.imgView addGestureRecognizer:singleTap];
[singleTap release];
// to make the image go full screen
self.navigationController.navigationBar.translucent=YES;
}
- (void)handleTap:(UIGestureRecognizer *)gestureRecognizer
{
NSLog(#"Handle Single tap");
[self finishedFading];
// fade again. You can choose to skip this can add a bool, if you want to fade again when user taps again.
myTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:#selector(fadeScreen) userInfo:nil repeats:NO];
}
Step 3 - Finally in viewWillDisappear, make sure to put all the stuff back
- (void)viewWillDisappear: (BOOL)animated
{
self.hidesBottomBarWhenPushed = NO;
self.navigationController.navigationBar.translucent=NO;
if (self.navigationController.topViewController != self)
{
[self.navigationController setNavigationBarHidden:NO animated:animated];
}
[super viewWillDisappear:animated];
}
In case anyone still having trouble with the fast backswipe cancelled bug as #fabb commented in the accepted answer.
I manage to fix this by overriding viewDidLayoutSubviews, in addition to viewWillAppear/viewWillDisappear as shown below:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
//*** This is required to fix navigation bar forever disappear on fast backswipe bug.
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.navigationController?.setNavigationBarHidden(false, animated: false)
}
In my case, I notice that it is because the root view controller (where nav is hidden) and the pushed view controller (nav is shown) has different status bar styles (e.g. dark and light). The moment you start the backswipe to pop the view controller, there will be additional status bar colour animation. If you release your finger in order to cancel the interactive pop, while the status bar animation is not finished, the navigation bar is forever gone!
However, this bug doesn't occur if status bar styles of both view controllers are the same.
If what you want is to hide the navigation bar completely in the controller, a much cleaner solution is to, in the root controller, have something like:
#implementation MainViewController
- (void)viewDidLoad {
self.navigationController.navigationBarHidden=YES;
//...extra code on view load
}
When you push a child view in the controller, the Navigation Bar will remain hidden; if you want to display it just in the child, you'll add the code for displaying it(self.navigationController.navigationBarHidden=NO;) in the viewWillAppear callback, and similarly the code for hiding it on viewWillDisappear
The simplest implementation may be to just have each view controller specify whether its navigation bar is hidden or not in its viewWillAppear:animated: method. The same approach works well for hiding/showing the toolbar as well:
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setToolbarHidden:YES/NO animated:animated];
[super viewWillAppear:animated];
}
Hiding navigation bar only on first page can be achieved through storyboard as well. On storyboard, goto Navigation Controller Scene->Navigation Bar. And select 'Hidden' property from the Attributes inspector. This will hide navigation bar starting from first viewcontroller until its made visible for the required viewcontroller.
Navigation bar can be set back to visible in ViewController's ViewWillAppear callback.
-(void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:animated];
[super viewWillAppear:animated];
}
Swift 4:
In the view controller you want to hide the navigation bar from.
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(true, animated: animated)
super.viewWillAppear(animated)
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.setNavigationBarHidden(false, animated: animated)
super.viewWillDisappear(animated)
}
By implement this code in your ViewController you can get this effect
Actually the trick is , hide the navigationBar when that Controller is launched
- (void)viewWillAppear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:YES animated:YES];
[super viewWillAppear:animated];
}
and unhide the navigation bar when user leave that page do this is viewWillDisappear
- (void)viewWillDisappear:(BOOL)animated {
[self.navigationController setNavigationBarHidden:NO animated:YES];
[super viewWillDisappear:animated];
}
I have a pageview which contains 4 pages. The pageView is using a navigation bar from previous UIViewController. I want set my UIPageView button in navigation bar to go to a new UIView. there are few problems. When i use performSegueWithIdentifier("CrankSetViewController", sender: sender) it give me a black screen. How to change to new UIView programatically without getting black screen?
In Swift -
pageViewController.setViewControllers(startingViewControllers,
direction: UIPageViewControllerNavigationDirection.Forward,
animated: true,
completion: nil)
you can set this in #IBAction
You can programmatically set the currently displayed view controller with a transition animation using setViewControllers:direction:animated:completion: on your page view controller.
Here is an example that presents view controllers with random background colors. You can adjust this to use your specific view controllers.
- (void)viewDidLoad
{
[super viewDidLoad];
self.pvc = [[UIPageViewController alloc]initWithTransitionStyle:UIPageViewControllerTransitionStyleScroll navigationOrientation:UIPageViewControllerNavigationOrientationHorizontal options:nil];
self.pvc.view.frame = CGRectInset(self.view.bounds, 200, 200);
[self.view addSubview:self.pvc.view];
[self.pvc setViewControllers:#[[self randomVC]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
-(UIViewController*)randomVC
{
UIViewController *vc = [[UIViewController alloc] init];
UIColor *color = [UIColor colorWithRed:arc4random_uniform(255)/255.0 green:arc4random_uniform(255)/255.0 blue:arc4random_uniform(255)/255.0 alpha:1];
vc.view.backgroundColor = color;
return vc;
}
- (IBAction)previousButtonPressed:(id)sender {
[self.pvc setViewControllers:#[[self randomVC]] direction:UIPageViewControllerNavigationDirectionReverse animated:YES completion:nil];
}
- (IBAction)nextButtonPressed:(id)sender {
[self.pvc setViewControllers:#[[self randomVC]] direction:UIPageViewControllerNavigationDirectionForward animated:YES completion:nil];
}
Hope it will help you...
I have made a custom navigation bar, same for all the views by implementing the method below -:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// viewController.navigationItem.rightBarButtonItem = cancelButton;
// -- Adding INFO button to Navigation bar --
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc]
initWithTitle:#"i"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(showInfo)];
infoButton.tag = 10;
self.navCntrl.topViewController.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:infoButton, nil];
self.navCntrl.navigationBar.tintColor = [UIColor colorWithRed:45/255.0 green:77/255.0 blue:67/255.0 alpha:1];
// NSLog(#"Inside implemented method");
}
of UINavigationControllerDelegate.
In the above method I have added a right button to the navigation Item. Now I want to hide this right button in a particular view. How can I achieve this ?
Thanks.
Try to use this one
self.navigationItem.rightBarButtonItem = nil;
self.navigationItem.rightBarButtonItem.enabled = NO;
In viewDidLoad, try
self.navigationItem.rightBarButtonItem = nil;
And in viewWillDisappear, don't forget to put it back.
use this
self.navigationItem.rightBarButtonItem = nil;
A nice way to do it would be to have your view controllers implement a protocol. You pick the name, but it can be something like CustomNavigationCustomization, and have a single method:
#protocol CustomNavigationCustomization
- (BOOL)shouldShowRightButton;
#end
Then, you could change your method to something like this:
- (void)navigationController:(UINavigationController *)navigationController
willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
BOOL shouldShowRightButton = YES;
if ([viewController conformsToProtocol:#protocol(CustomNavigationCustomization)) {
UIViewController <CustomNavigationCustomization> *customizableViewController =
(UIViewController <CustomNavigationCustomization>)viewController;
shouldShowRightButton = [customizableViewController shouldShowRightButton];
}
if (shouldShowRightButton) {
// viewController.navigationItem.rightBarButtonItem = cancelButton;
// -- Adding INFO button to Navigation bar --
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc] initWithTitle:#"i"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(showInfo)];
infoButton.tag = 10;
self.navCntrl.topViewController.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:infoButton, nil];
self.navCntrl.navigationBar.tintColor = [UIColor colorWithRed:45/255.0 green:77/255.0 blue:67/255.0 alpha:1];
// NSLog(#"Inside implemented method");
}
}
Note that the method in the navigation controller delegate is very defensive: it checks if your view controller conforms to the protocol, and only then it invokes the method. This way, you don't need to conform to the protocol in most of your view controllers, only in those you wish to customise.
Just check if the viewController pushed of tf the type where you do not want the righ bar button:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Replace the YourViewController with the type of the viewcontroller
// you want not the have the right bar button.
if ([viewController isKindOfClass:[YourViewController class]]) {
return;
}
// viewController.navigationItem.rightBarButtonItem = cancelButton;
// -- Adding INFO button to Navigation bar --
UIBarButtonItem *infoButton = [[UIBarButtonItem alloc]
initWithTitle:#"i"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(showInfo)];
infoButton.tag = 10;
self.navCntrl.topViewController.navigationItem.rightBarButtonItems = [NSArray arrayWithObjects:infoButton, nil];
self.navCntrl.navigationBar.tintColor = [UIColor colorWithRed:45/255.0 green:77/255.0 blue:67/255.0 alpha:1];
// NSLog(#"Inside implemented method");
}
For those that are still looking for an answer, this code worked for me in AppDelegate.m
- (BOOL)application:(UIApplication *)applicationdidFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
// Get rid of the edit button in UITabBarController's moreNavigationController
tabBarController.customizableViewControllers = nil;
...
}
Here is my code. I want to put a back button on the opening rootviewController.
- (void)selectSurah:(id)sender {
SurahTableViewController * surahTableViewController = [[SurahTableViewController alloc] initWithNibName:#"SurahTableViewController" bundle:nil];
surahTableViewController.navigationItem.title=#"Surah";
surahTableViewController.navigationItem.backBarButtonItem.title=#"Back";
UINavigationController *aNavigationController = [[UINavigationController alloc] initWithRootViewController:surahTableViewController];
[self presentModalViewController:aNavigationController animated:YES];
}
I don't believe it's possible to pop the root view controller off the navigation stack, but you can fake it with a UIButton added as the custom view of a UIBarButtonItem:
UIButton *b = [[UIButton alloc]initWithButtonType:UIButtonTypeCustom];
[b setImage:[UIImage imageNamed:#"BackImage.png"] forState:UIControlStateNormal];
[b addTarget:self action:#selector(back:) forControlEvents:UIControlEventTouchUpInside];
self.leftBarButtonItem = [[UIBarButtonItem alloc]initWithCustomView:b];
A suitable PSD of iOS UI elements can be found here.
Faizan,
Helium3 comment makes sense.
I suppose that your button is needed to dismiss the controller presented modally, is it true? Correct if I'm wrong.
If so, you could just create a new UIBarButtonItem and set is a left (or right) button for the UINavigationController navigationItem. To not break encapsulation create it in the viewDidLoad method for your SurahTableViewController controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// make attention to memory leak if you don't use ARC!!!
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Close"
style:UIBarButtonItemStyleBordered
target:self
action:#selector(close:)];
}
-(void)close:(id)sender
{
// to dismiss use dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion
// dismissModalViewControllerAnimated: is deprecated
[self dismissViewControllerAnimated:YES completion:^{ NSLog(#"controller dismissed"); }];
}
Since the SurahTableViewController is a root view controller in a navigation controller you can't go back to the root because you're already there. Since you've presented it modally from something else, you need to put a button on the nav bar that has an IBAction which calls:
[self dismissModalViewControllerAnimated:YES];
Appearance and behavior of a back button in a UINavigationController relies on interaction between a stack of UINavigationControllers. Putting a back button on the first controller breaks this convention, there's nothing to go back to, which is why your code isn't working.
You'll need to manually add UIBarButtonItem to the title bar code like:
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(back:)];
If you truly want it to look like a back button, you'll need to manually create the UIBarButtonItem with an image that mirrors the back button.
Another suggestion though, as it looks like you are attempting to use a back button to dismiss a modal view controller, I'd stick with something more conventional like a "Close" or "Done" button to close the modal view controller. A back button is really more appropriate for navigating a stack of UINavigationControllers.
Here's the answer I think everyone was looking for.
Simply provide an empty UIViewController as the rootViewController of your UINavigationController subclass in a custom initializer. Then push your actual rootViewController without animation before presenting:
init(backButtonRootViewController: UIViewController) {
super.init(rootViewController: UIViewController())
interactivePopGestureRecognizer?.isEnabled = false
pushViewController(backButtonRootViewController, animated: false)
}
You'll also want to disable the interactivePopGestureRecognizer to prevent the user from swiping back to the empty rootViewController.
Then in extend your subclass to implement the shouldPop method of UINavigationBarDelegate and dismiss the navigation controller when the back button is pressed on the rootViewController:
func navigationBar(_ navigationBar: UINavigationBar, shouldPop item: UINavigationItem) -> Bool {
if children.count >= 2 && children[1] == topViewController {
presentingViewController?.dismiss(animated: true, completion: nil)
return false
} else {
return true
}
}
Works like a charm!
For Swift, try something like:
override func viewDidLoad() {
super.viewDidLoad();
self.navigationItem.hidesBackButton = true;
let backButton = UIBarButtonItem(
image: UIImage(named: "my_back_asset"), style: .plain,
target: self, action: #selector(self.onBack));
self.navigationItem.leftBarButtonItem = backButton;
}
#objc private func onBack() {
let controller = self.navigationController?.popViewController(animated: true);
if controller == nil {
// Handle pressing back when on root here.
}
}
Note to change "my_back_asset" to your own back-button image.
I have a UINavigationController (to use like a wizard page) which I create programmatically and I need to display a "Cancel" button to cancel the process in any UIViewController.
Creating the UINavigationController:
FirstVC *firstVC = [[[FirstVC alloc] initWithNibName:#"FirstPage" bundle:nil] autorelease];
firstVC.delegate = self;
navigationController = [[UINavigationController alloc] initWithRootViewController:firstVC];
[self.view addSubview:navigationController.view];
Adding Cancel Button:
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(cancelRequestNewLeave:)];
navigationController.topViewController.navigationItem.rightBarButtonItem = cancelButton;
[cancelButton release];
But when I push a second page to UINavigationController the cancel button is not shown on the UINavigationBar. If I go back to first page, the cancel button is there. So, apparently the button is added only for the first view. I believe this is because I'm not subclassing UINavigationController, because I need to use it in a subview. But I don't know how to set the rightBarButtonItem in a UINavigationController which is created programmatically.
navigationController.topViewController.navigationItem.rightBarButtonItem = cancelButton;
Can someone shed a light on this?
Thanks in advance.
The navigation item is per view controller. The navigation bar draws its contents from the navigation item of the view controller whose view it's currently framing, which corresponds to the view controller at the top of the navigation controller's stack.
You basically need each view controller to stick a cancel button in its navigation item. You can do any of the following:
Copy-paste the code into all relevant view controllers.
Move the code into a utility function or class and call that.
Create a common superclass for all relevant view controllers that handles setting up the cancel button for its subclasses.
You can also subclass UINavigationcontroller and overide few methods like this:
- (id)initWithRootViewController:(UIViewController *)rootViewController {
self = [super initWithRootViewController:rootViewController];
if (self) {
[self setCloseButtonToController:rootViewController];
}
return self;
}
- (void)dismissController {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)setCloseButtonToController:(UIViewController *)viewController {
UIBarButtonItem *closeItem = [[UIBarButtonItem alloc] initWithTitle:#"Close" style:UIBarButtonItemStylePlain target:self action:#selector(dismissController)];
[viewController.navigationItem setRightBarButtonItem:closeItem];
}
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated {
[super pushViewController:viewController animated:animated];
[self setCloseButtonToController:viewController];
}
You can instead adopt the UINavigationControllerDelegate protocol in the class which creates the UINavigationController instance. You can also create the cancelButton in advance and then implement navigationController:willShowViewController:animated: like this,
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
viewController.navigationItem.rightBarButtonItem = cancelButton;
}
You will have to remember to create and hold the cancelButton and not release it. This will also mean cancelRequestNewLeave: will have to be a method in class that creates the UINavigationController instance which is what it is right now I guess.
create CommonViewController
create FirstViewController (extends from CommonViewController)
create SecondeViewController (extends from CommonViewController)
add function common functions in the CommonViewController
like that
CommonViewController.h
#interface CommonViewController : UIViewController
-(void) initializeCartBarButton;
#end
CommonViewController.m
#import "CommonViewController.h"
#interface CommonViewController ()
#end
#implementation CommonViewController
-(void) initializeCartBarButton {
UIBarButtonItem *cartBarButton = [[UIBarButtonItem alloc] init];
cartBarButton.title = #"cart";
[cartBarButton setTarget: self];
[cartBarButton setAction: #selector(goToCart:)];
self.navigationItem.rightBarButtonItem = cartBarButton;
}
- (IBAction) goToCart:(id)sender {
NSLog(#"");
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
FirstViewController.h
#import <UIKit/UIKit.h>
#import "CommonViewController.h"
#interface FirstViewController : CommonViewController
#end
FirstViewController.m
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initializeCartBarButton];
}
#end
SecondViewController.h
#import <UIKit/UIKit.h>
#import "CommonViewController.h"
#interface SecondViewController : CommonViewController
#end
SecondViewController.m
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self initializeCartBarButton];
}
#end
note: you can add the code of initializeCartBarButton in the viewDidLoad of CommonViewController and delete this fuction from CommonViewController and from child class's
This is how I did it with UINavigationController subclass that is capable of dismissing every viewController pushed into it.
class CustomNavigationController: UINavigationController, UINavigationControllerDelegate{
//TODO: Use when we have more right bar button types.
var rightBarButtonType: RightBarButtonType = .Close
enum RightBarButtonType{
case Close
}
override func viewDidLoad() {
super.viewDidLoad()
self.delegate = self
}
// MARK: Private Functions
private func addRightBarButtonTo(viewController: UIViewController){
let barButtonItem: UIBarButtonItem!
switch self.rightBarButtonType {
case .Close:
barButtonItem = UIBarButtonItem(image: UIImage(named: "ic_close_white"), style: .Done, target: self, action: #selector(CustomNavigationController.dismiss(_:)))
}
viewController.navigationItem.rightBarButtonItem = barButtonItem
}
// MARK: UINavigationController Delegate
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
self.addRightBarButtonTo(viewController)
}
#objc func dismiss(sender: AnyObject){
self.presentingViewController?.dismissViewControllerAnimated(true, completion: nil)
}
}
You'll need to add the button in every view controller. You cannot do it by setting one once or sharing one between view controllers (in a sensible fashion). A good place to add the button is in the viewDidLoad method of your view controllers. You can create one basic UIViewConteoller subclass for them if you feel this gets to repetitive.
You can add a custom 'Cancel' UIButton directly to the NavigationBar's view instead of using the UIBarButtonItem.
UIButton *cancelButton = [UIButton buttonWithType:UIButtonTypeCustom];
cancelButton.imageView = // Some custom image
cancelButton.frame = CGRectMake(...); // Something far to the right.
[self.navigationController.navigationBar addSubview: cancelButton];
The normal way to do this is to add that cancel button to the navigationItem of every single view controller in your navigation stack. The above approach can make it simpler by allowing you to write less code, but it is a tiny bit of a hack.
Add this code in your rootview viewDidLoad method and implement the cancelMethod in rootview controller.This will be available in all the view controllers. you can adjust the button location by changing button frame.For orientation change you have manually adjust the location of button.
UIButton *btnCancel = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btnCancel addTarget:self
action:#selector(cancelMethod)
forControlEvents:UIControlEventTouchDown];
[btnCancel setBackgroundImage:[UIImage imageNamed:#"image"]
forState:UIControlStateNormal];
btnCancel.frame = CGRectMake(280, 27, 45, 25);
[self.navigationController.view addSubview: btnCancel];