Adding same button to all view controllers in UINavigationController - iphone

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];

Related

How to ask the first view controller to reload its view after changing the values in another view controller?

I am trying to call my view in the second view controller with a new set of values to be changed in the first view controller.
i am using pointers to get my first viewcontroller's view (used a property that points to firstview controller) but i am not seeing any changes in the view.
ideas are appreciated. Thank you
//secondviewcontroller.m
#import "SecondViewController.h"
#import "FirstViewController.h"
#import "Tab.h"
#import "AppDelegate.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
#synthesize changeSizeSlider;
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
self.title = NSLocalizedString(#"second", #"second");
self.tabBarItem.image = [UIImage imageNamed:#"second"];
}
return self;
}
- (IBAction)changeSizeSlider:(UISlider *)sender
{
// Change label to match slider's value
label1.text = [NSString stringWithFormat:#"%g", [changeSize value]];
// Var for changing size
CGFloat changeSizeCont = [changeSize value];
NSLog(#"changeSizeCont = %f", changeSizeCont);
((Tab *)vc.view).rect_width = changeSizeCont;
((Tab *)vc.view).rect_height = changeSizeCont;
// The values here are displayed but i think the view is not reloaded with the nw values
NSLog(#"Current values for VC's view properties:");
NSLog(#"rect_width = %f", ((Tab *)vc.view).rect_width);
NSLog(#"rect_height = %f", ((Tab *)vc.view).rect_height);
}
Calling the firstviewcontroller's view object vc by calling its property in
//secondviewcontroller.h
#property FirstViewController *vc;
//firstviewcontroller.m
#import "FirstViewController.h"
#interface FirstViewController ()
#end
#implementation FirstViewController
- (void)viewWillAppear:(BOOL)animated
{
//NSLog(#">>> %#", NSStringFromSelector(_cmd) );
self.title = NSLocalizedString(#"First", #"First");
self.tabBarItem.image = [UIImage imageNamed:#"first"];
// Get timer
NSTimer *timer = [NSTimer timerWithTimeInterval: 0.5
target: self.view
selector: #selector(setNeedsDisplay)
userInfo: nil
repeats: YES];
// Get runloop, add timer to runloop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode: NSDefaultRunLoopMode];
}
I have the functions that draws the rectangles written in a class.
//appdelegate.m
#import "TabAppDelegate.h"
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "Tab.h"
#implementation TabAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
time_t seed = time(0);
srandom((int)seed);
CGRect bounds = [self.window bounds];
Tab *view = [[Tab alloc] initWithFrame:bounds];
view.backgroundColor = [UIColor whiteColor];
FirstViewController *vc = [[FirstViewController alloc] init];
[vc setView: view];
SecondViewController *vc2 = [[SecondViewController alloc] init];
//[vc2 setView: view];
vc2.vc = vc;
self.tabBarController = [[UITabBarController alloc] init];
self.tabBarController.viewControllers = #[vc, vc2];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
Tab.m has the draw functions that will appear in the firstview and secondview has a nib that handles the changes in firstviewcontroller.
I pretty much think i am not wrong anywhere, i spent almost one week without figuring out this issue.
Appreciate your time guys.
Handling changes, actions, or user interaction in other views is the essential use case of delegates. The best practice is to have the first view controller be the delegate of the second view controller, and then as events happen in the second view controller, it calls certain methods on its delegate to notify it.
Other options for keeping values/state in sync between views are:
Core Data - best used for data
Key-Value Observation - best used for data
NSNotifications sent through NSNotificationCenter - best used for actions
You can also create a block property on your second view controller which the first view controller sets when it pushes it on the stack
typdef void(^CallWhenChanged)(CGSize);
#interface ViewController2
#property(strong, nonatomic) CallWhenChanged callBlock;
#end
Then:
- (IBAction)changeSizeSlider:(UISlider *)sender
{
//do stuff
if( self.callBlock != nil )
{
self.callBlock(CGSizeMake(rect_width,rect_height));
}
}
Delegates as mentioned by jszumski are another option, but I prefer to use delegates when dealing with more than one delegate method.

How do I create a sizeable subView

I'm looking to create a sizeable subview that's draggable like this:
If there is an IBAction that takes you to the next View (SecondViewController) and when it does, there's another IBAction there and when you click on that one, it creates a SubView that's about half of the size of the current screen you're in (SecondViewController) that shows the third view controller that would be created? Also, how would you make that subView draggable? Thank you for helping.
Sorry, just to be clear, you want your second view controller to have a button that when tapped, adds your third view controller taking up half the screen at the bottom?
If this is the case then you can do this with the new view controller containers in iOS5.
Ok, so you have three view controllers. For the sake of this lets say your class are called FirstViewController, SecondViewController and ThirdViewController.
I assume from what you say that you already have you instance of FirstViewController with a button, that moves you on to an instance of SecondViewController, and that the issue is then getting SecondViewController to add an instance of ThirdViewController to the bottom half of the screen when a button is pressed.
The .m file for SecondViewController will need to do something like this:
#import "ThirdViewController.h"
#interface SecondViewController ()
#property (retain) ThirdViewController *thirdViewConroller;
- (void)buttonTap;
#end
#implementation SecondViewController
#synthesize thirdViewConroller = _thirdViewConroller;
- (void)dealloc {
self.thirdViewConroller = nil;
[super dealloc];
}
- (void)loadView {
UIButton *button = [UIButton buttonWithType:UIButtonTypeRoundedRect];
button.titleLabel.text = #"Show third controller";
[button addTarget:self action:#selector(buttonTap) forControlEvents:UIControlEventTouchUpInside];
button.frame = // Some CGRect of where you want the button to be
[self.view addSubview:button];
}
- (void)buttonTap {
// When the button is tapped, create an instance of your ThirdViewController and add it
self.thirdViewConroller = [[ThirdViewController alloc] initWithFrame:/* Some CGRect where you want the controller to be */ ];
[self.thirdViewConroller willMoveToParentViewController:self];
[self addChildViewController:self.thirdViewConroller];
[self.thirdViewConroller didMoveToParentViewController:self];
}
#end
This should give you a button on your second controller, that will create and add you third controller. Make sure yo us till have all the standard methods that you had before, this should be in addition to what you have.
In your interface for ThirdViewController:
#interface ThirdViewController : UIViewController <NSObject>
- (id)initWithFrame:(CGRect)frame;
#end
Then in the implementation of your ThirdViewController:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithNibName:nil bundle:nil];
if (self) {
self.view.frame = frame;
// Do your init stuff here
}
return self;
}
It should then handle adding the views and such forth.
Make sure your thirdViewController class has a valid initWithFrame: initialiser method.
This should do the trick, if you need any further help let me know :)

self.view insertSubView .... not supposed to be self.view, but what?

I have a UIViewController which shows different UIViewController sub-classes. In the main UIViewController.m, I have a sub-class called 'Home' load on app start.
Now, which the Home view loaded, I have a button which I want to use to switch to another view called 'PreGameInfo'. I'm trying to use the code below:
- (IBAction)showPreGameInfo:(id)sender {
[self.view insertSubview:preGameInfo.view atIndex:0];
}
It doesn't work, and I know it's because the 'self.view' needs to refer to the main UIViewController rather than the self of the 'Home' view. Does anyone know how to insertSubView to the main UIViewController when using a UIButton whilst in a SubView???
Thank you!
You can use a delagate. It very easy
So implement this in your information view controller;
In the InformationViewController.h
#protocol InformationViewControllerDelegate;
#interface InformationViewController : UIViewController {
id <InformationViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id <InformationViewControllerDelegate> delegate;
- (IBAction)returnBack:(id)sender;
#end
#protocol InformationViewControllerDelegate
- (void)InformationViewControllerDidFinish:(InformationViewController *)controller;
#end
in the InformationViewController.m
- (IBAction)returnBack:(id)sender {
[self.delegate InformationViewControllerDidFinish:self];
}
And use the delegate in any view controller you need it like this :
In the HomeViewController.h
#import "InformationViewController.h"
#interface HomeViewController : UIViewController <InformationViewControllerDelegate> {
}
Write the method to change the view from Home view to Information view
- (IBAction)goToInformationView:(id)sender;
In the HomeViewController.m
- (IBAction)goToInformationView:(id)sender {
InformationViewController *controller = [[InformationViewController alloc] initWithNibName:nil bundle:nil];
controller.delegate = self;
// You can chose the transition you want here (they are 4 see UIModalTransitionStyle)
controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentModalViewController:controller animated:YES];
[controller release];
}
And the last but not least the delegate method it inform the HomeViewController when the InformationViewController had finished
- (void)InformationViewControllerDidFinish:(InformationViewController *)controller {
[self dismissModalViewControllerAnimated:YES];
}
I hope it helps
- (IBAction)showPreGameInfo:(id)sender {
[superview insertSubview:preGameInfo.view atIndex:0];
}
Does this code work?
[self.parentViewController.view addSubview:myView];
Just do it in the modalView:
[self presentModalViewController:preGameInfo animated:YES]
or you can do something like this...
This line will add your new view to windows rootViewControllers view
[self.view.window.rootViewController.view addSubview:preGameInfo.view];

Use modal view as "normal" view

I want to use a modal view (UIViewController) as a "normal" view, which can be pushed on the navigation controller stack. Normally, a modal view is presented like this:
LoginViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[self.navigationController presentModalViewController:navController animated:YES];
[myView release];
myView = nil;
[navController release];
navController = nil;
But I want to do something like this:
[[self navigationController] pushViewController:myView animated:YES];
The problem is that my modal view has a right and a left button. So I would have to check how the view is loaded and present the buttons in another way. The idea behind this is to have the back button. So I can use the same modal view a few times.
Edit:
#petert:
Now I followed your example. My issue is that I'm using a UINavigationBar for the modal view. To get this UINavigationBar I create a navigation controller. I'm using the navigation bar because I have my buttons in it. So checking if parentViewController is of type UINavigationController does not work for me. I'm always getting a modal view. Here is how I do it:
// load modal view
MyViewController *myView = [[MyViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:myView];
[[self navigationController] presentModalViewController:navController animated:YES];
[navController release];
navController = nil;
[myView release];
myView = nil;
// load as normal view
MyViewController *myView = [[MyViewController alloc] init];
[[self navigationController] pushViewController:myView animated:YES];
Good tips in this StackOverflow answer.
I prefer to use UIViewController's property:
#property(nonatomic, readonly) UIViewController *parentViewController
in a view controller's subclass:
Look at the value of the controller's parentViewController property. If it's an instance of UINavigationController, then you're in the navigation stack. If you're being displayed modally, it'll be an instance of your last view controller.
So in -viewDidLoad for example:
- (void)viewDidLoad
{
if ([self.parentViewController isKindOfClass:[UINavigationController class]])
{
// navigation controller
self.title = #"...";
}
else
{
// modal
self.title = #"Modal";
// add cancel and done buttons now...
}
}
Or, a pretty simple solution would be to customize your init method to your MyViewController class to encode your intent for the view controller.
Add the following to the MyViewController header:
#interface MyViewController : UIViewController
{
BOOL modal;
}
- (id)initForModal:(BOOL)isModal;
#end
Now in the implementation file:
#interface MyViewController ()
#property (nonatomic) BOOL modal;
#end
#implementation MyViewController
#synthesize modal;
- (id)initForModal:(BOOL)isModal;
{
if (self = [super initWithNibName:#"MyViewController" bundle:nil])
{
self.modal = isModal;
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if (self.modal)
{
// add cancel and done buttons …
}
else
{
// assuming we're presented from a navigation view …
}
}
Now to use this modally:
// load modal view
MyViewController *myView = [[MyViewController alloc] initForModal:YES];
Or not modally:
// load as normal view
MyViewController *myView = [[MyViewController alloc] initForModal:NO];
I'm assuming you're creating the view controller(s) from NIBs, but as always see the View Controller Progamming Guide for iOS and especially the section titled "Defining a Custom View Controller Class".
For clarification: myView isn't modal. You just present it as a modal one.
If you just push it into a UINavigationController hierarchy it will behave like a "normal" one.
You can't push the same view controller onto the navigation stack several times. Just once.
Also see this for how to customize the view:
SO modal question

UITabBarController does not appear in view

I have a UIView that is pushing a UITableViewController that is contained inside of a UITabBarController.
#import <UIKit/UIKit.h>
#interface ARViewController : UITableViewController<UITabBarControllerDelegate> {
IBOutlet UITabBarController* tabBarController;
}
#property(nonatomic,retain)IBOutlet UITabBarController* tabBarController;
#end
Here is my implementation
- (void)viewDidLoad {
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
self.clearsSelectionOnViewWillAppear = NO;
self.title = #"AR";
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
tabBarController.delegate = self;
[self.view addSubview:tabBarController.view];
}
My UITabBarController is referenced to Files Owner. Why is it not appearing?
UITabBarController is always used as 'root controller'. Maybe you can try to use a root controller to contain a tableview. And it's not a common method to add a tabbar as a subview to a tableview.