UINavigationBar, back button, and dismissModalViewController - objective-c

I have an app that is navigation based, so all views default to have a top nav bar. I have reached a page where a back button is not displayed by default for whatever reason and I was required to add one programatically. Unfortunately, the back button does not dismiss the modal view as expected.
I load the view in question through:
-(IBAction) linkPress:(id)sender
{
potentialUrl = [[NSURL alloc] initWithString:((Button*)sender).emailContent];
webViewInst = [[WebView alloc] initWithNibName:#"WebView" bundle:nil url:potentialUrl];
NSString *deviceType = [UIDevice currentDevice].model;
if([deviceType isEqualToString:#"iPad"] || [deviceType isEqualToString:#"iPad Simulator"]){
[self presentModalViewController:webViewInst animated:YES];
}
else {
[self.navigationController pushViewController:webViewInst animated:YES];
}
}
I add the back button through:
UIBarButtonItem *MKbackBtn = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStylePlain target:self action:#selector(backButton:)];
[self.navigationItem setLeftBarButtonItem:MKbackBtn];
And the action that the back button should take to remove the view and return to the previous view:
-(IBAction)backButton:(id)sender
{
UIViewController* parent = [self parentViewController];
if(parent==nil) {
parent = [self presentingViewController];
}
[parent dismissModalViewControllerAnimated:YES];
}
If another set of eyes could go over these bits of code and try to discern what mistake I have made, that would be greatly appreciated! I am more than willing to provide more information/code as well.
Thanks!

You're calling dismissModalViewControllerAnimated but based on your code above there's a chance that it's not presented as modal and is instead pushed onto the navStack, in which case dismissModalViewControllerAnimated wouldn't actually dismiss it. Instead you would need to do popViewController etc. You should be casing around the presentation means. Can you confirm that this isn't part of the problem?
Also, off the top of my head I think you would call [self dismissModalViewController...] rather than parent.

Displaying a view controller modally does not include it in the navigation controller's stack. You have to provide your own UI mechanism to dismiss the modal view. It looks to me like your solution to dismiss the modal view controller should mostly work--although I think all you need is this one line in backButton::
[self dismissModalViewControllerAnimated:YES];

Related

Pop controller after back bar button is pressed

I have a UINavigationController ans a chain of 3 simple controllers. Each one has a button. When press a button a next controller is Pushed. ViewController1 -> ViewController2 -> ViewController3. When I push a back button on the 3rd view i want to move to the first view. Using of backBarButtonItem is obligatory. Here is the code for second controller:
#import "ViewController2.h"
static BOOL isBackButtonPressed;
#implementation ViewController2
- (void)viewDidLoad {
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"back from 3" style:UIBarButtonItemStyleBordered target:nil action:nil];
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
if (isBackButtonPressed) {
[self.navigationController popViewControllerAnimated:YES];
} else {
isBackButtonPressed = YES;
}
[super viewWillAppear:animated];
}
#end
But when I press back button on the third view I return to the second view instead of the first view. Could you help me to return to the first view pressing back button on the third view.
I tried suggestions from answers but they don't help.
Adding a selector to backBarButtonItem doesn't help because it is never called.
Adding a [self.navigationController popToRootViewControllerAnimated:YES] in viewWillDisappear methos also doesn't work. I don't know why. I think that the actual problem is how backBarButtonItem works.
Any other suggestions?
The behaviour I try to achieve exists in the calendar on iPhone. When you rotate iPhone to landscape you get to the weeek view. Then go to the event details, and rotate to the portrait. When you press back button you will get to a day view not to a week view, so a controller with weekview is skipped.
After countless number of tries my solution was simply not use backBarButtonItem! As whatever i do it always goes to previous viewController instead of calling its selector
Instead I use only leftBarButtonItem for navigation, as it guarantees calling my action.
Here an example
UIButton *backButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 27, 22)];
[backButton setImage:[UIImage imageNamed:#"backbutton"] forState:UIControlStateNormal];
[backButton addTarget:self action:#selector(backButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
This certainly calls backButtonPressed action.. This works both for IOS 6 and 7
No need to register a new selector for the back button, just do:
-(void)viewWillDisappear{
if ( [self.navigationController.viewControllers containsObject:self] )
//It means that the view controller was popped (back button pressed or whatever)
//so we'll just pop one more view controller
[self.navigationController popViewControllerAnimated:NO];
}
in your ViewController3 viewWillDisappear method
Try using this in your third view controller, this way you check if you have pressed the back button and directly pop to the root view controller which as you stated is your first view controller.
-(void) viewWillDisappear:(BOOL)animated {
if ([self.navigationController.viewControllers indexOfObject:self]==NSNotFound) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
[super viewWillDisappear:animated];
}
I had the same problem as you beofre and fixed it like this:
You can capture the back button on the ViewController3 and before poping the view, remove ViewController2 from the navigation stack like this:
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"back from 3" style:UIBarButtonItemStyleBordered target:self action:#selector(customBackPressed:)];
}
-(void)customBackPressed:(id)sender {
NSMutableArray *allViewControllers = [NSMutableArray arrayWithArray: navigationController.viewControllers];
for (UIViewController *vc in viewControllers)
{
// If vc is ViewController2 type, remove it
}
navigationController.viewControllers = allViewControllers;
[self.navigationController popViewControllerAnimated:YES];
}
Because ViewController2 is not in the stack anymore, it will jump to ViewController1.
Also, if ViewController1 is the root view controller, you can just do:
[self.navigationController popToRootViewControllerAnimated:YES];

ABPersonViewController in Modal

I would like to display ABPersonViewController as a modal instead of pushing it on the navigation stack. I've got this working but to keep a done button present I've had to use an NSTimer to add the button every 0.25 seconds because the done button may be removed when the view appears and is always removed when the app enters the forground. This is a pretty lame hack so I'm wondering if anyone has a better idea :)
I made a subclass of ABPersonViewController that adds the done button and starts the timer on view did load and invalidates it when the view is deallocated.
Here is what my code looks like to show the modal:
- (IBAction)showContactModal:(id)sender{
CNABPersonViewController *personViewController = [[CNABPersonViewController alloc] init];
personViewController.displayedPerson = self.contact.record;
personViewController.addressBook = [[CNAddressBookManager sharedManager] addressBook];
personViewController.viewDelegate = self;
personViewController.shouldShowLinkedPeople = YES;
UINavigationController *navigationController =
[[UINavigationController alloc] initWithRootViewController:personViewController];
navigationController.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:navigationController animated:YES completion:nil];
}
I had success in doing it like this. Insert this line to add a button to the navigation bar:
personViewController.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:#"Test" style:UIBarButtonItemStylePlain target:self action:#selector(_yourAddressBookAction)];
If this does not solve your problem, please show us the code that you had the issue with.

Present UIPopoverController from inside a modally presented view controller

Is it impossible to present a popover controller from a view controller that's presented as UIModalPresentationFormsheet? or am I missing something?
This code works fine on a non-modal view controller, and displays the popover correctly.(WEPopoverController is a custom implementation from here.)
GenericDataTableViewController *genericDataController = [[GenericDataTableViewController alloc] initWithNibName:#"GenericDataTableViewController" bundle:[NSBundle mainBundle]];
genericDataController.dataSource = [NSMutableArray arrayWithObjects:#"asli", nil];
genericDataController.delegate = self;
genericDataController.contentSizeForViewInPopover = CGSizeMake(300, 46 * [genericDataController.dataSource count]);
self.popoverController = [[[WEPopoverController alloc] initWithContentViewController:genericDataController] autorelease];
self.popoverController.delegate = self;
[self.popoverController presentPopoverFromRect:((UIButton *)sender).frame
inView:self.view
permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
[genericDataController release];
But when I put this inside the modal view controller, this presents the popover below the modal controller, i.e. on the view controller that presents the modal one. So it cannot be seen by the user.
How can I solve this?
The answer is explained in the comments of the question but I'm summarizing it here for a cleaner Q&A.
There aren't any restrictions about displaying a popover controller from inside a form sheet, if your popover controller is UIPopoverController. My problem was due to the implementation of WEPopoverController.
So, sacrifice the visual experience and continue with the good old UIPopoverController.

xcode adding buttons to navigation bar

I am making a simple app to display drink details, and now I am trying to add a view that allows the user to input their own drink. I already created a view to display the details, and now I am just passing the view into another controller to make the add drink view. Problem is, when I try to add a "cancel" and "save" button, it doesn't appear, although the code complies without any errors. I have attached code as reference.
This is the code that makes the new view, when the add button is pressed. (I made an add button that works, and it pulls up the nav bar)
- (IBAction)addButtonPressed:(id)sender {
AddDrinkViewController *addViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"DetailSecond"];
UINavigationController *addNavController = [[UINavigationController alloc] initWithRootViewController:addViewController];
[self presentModalViewController:addNavController animated:YES];
NSLog(#"Add button pressed!");
This is the code from the addviewcontroller implementation file:
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemCancel target:self action:#selector(cancel:)];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSave target:self action:#selector(save:)];
}
- (IBAction)save:(id)sender {
NSLog(#"Save Pressed");
[self dismissModalViewControllerAnimated:YES];
}
- (IBAction)cancel:(id)sender{
NSLog(#"Cancel Pressed");
[self dismissModalViewControllerAnimated:YES];
}
I have imported the header from the addview into the root controller, so I don't think that is the problem, do any of you guys see anything that's wrong?
Just change the line
[self presentModalViewController:addNavController animated:YES];
to
[self presentViewController:navigationController animated:YES completion:nil];
and see the magic. I also tested the code
My advice to you is to create a template for the view before you run through any code in the XIB file of your app. Rather than trying to set each button after allocating a brand new view, setting a new one in the XIB before-hand allows you to link each element with the app and make sure it looks just right before you debug.
Simply go into your "[Your-App-Name]viewController.xib" and drag a view from the objects library to the pane on the left. From here add each of your elements and position them where you want on the view. Now in the "[Your-App-Name]viewController.h" file, add IBOutlets for each element that you need to change, and add IBActions for each of the buttons. Also create an IBOutlet for the new view.
IBOutlet UIView* addDrinkView;
Back in the XIB file, use files owner to link each outlet to each element and each method to each button. Make sure you link the IBOutlet
Now in your "[Your-App-Name]viewController.m" file, you can define each button method and all you need to do to access the new view and dismiss it are the following:
-(IBAction)openAddView
{
[self setView:addDrinkView];
}
-(IBAction)saveButtonPressed
{
[self setView:view];
//save code goes here
}
-(IBAction)cancelButtonPressed
{
[self setView:view];
//cancel code goes here
}
This should be much easier than trying to position everything in code.
Hope this helps!

Presenting a Modal View Controller hides the Navigation Bar

I have a navigation based app with a navigation bar, but there are a few instances where instead of pushing a view controller onto the stack, I need to present the view controller modally. The problem is that when I dismiss the modal view controller, everything functions as expected except that the navigation bar is hidden and the (parent view) has been resized, which is the expected behavior according to the docs. So I figured I could simply call a built-in method to unhide the navigation bar. I have already tried
[self.navigationController setNavigationBarHidden:NO];
as well as the animated version without success.
The documentation talks about this in the method
presentModalViewController: animated:
in the discussion section where it says,
On iPhone and iPod touch devices, the view of modalViewController is always presented full screen" and "Sets the modalViewController property to the specified view controller. Resizes its view and attaches it to the view hierarchy."However, the docs didn't clue me in as to how to undo this process after dismissing a modal view.
Has anyone else experienced this and found a solution?
Edit: I am having this same problem, so instead of asking my own question I am sponsoring a bounty on this one. This is my specific situation:
In my case, I am presenting an Image Picker in a Modal View Controller, over a Navigation Controller:
-(void) chooseImage {
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
imagepicker = [[UIImagePickerController alloc] init];
imagepicker.allowsEditing = NO;
imagepicker.delegate = self;
imagepicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
imagepicker.navigationBar.opaque = true;
imagepicker.wantsFullScreenLayout = NO;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
if (self.view.window != nil) {
popoverController = [[UIPopoverController alloc] initWithContentViewController:imagepicker];
[popoverController presentPopoverFromBarButtonItem:reset permittedArrowDirections:UIPopoverArrowDirectionDown animated:YES];
} else {}
} else {
[self.navigationController presentModalViewController:imagepicker animated:YES];
}
}
}
-(void) imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.popoverController dismissPopoverAnimated:true];
} else {
[self.navigationController dismissModalViewControllerAnimated:YES];
}
//Save the image
}
-(void) imagePickerControllerDidCancel:(UIImagePickerController *)picker {
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
[self.popoverController dismissPopoverAnimated:true];
} else {
[self.navigationController dismissModalViewControllerAnimated:YES];
}
}
Make sure you a presenting AND dismissing the modalViewController from the UINavigationController, like so:
// show
[self.navigationController presentModalViewController:vc animated:YES];
// dismiss
[self.navigationController dismissModalViewControllerAnimated:YES];
If your view controller is actually on the UINavigationController's stack then this is the correct way to handle the presentation and dismissal of the modal view controller. If your UINavigationBar is still hidden, there is something else funky going on and we would need to see your code to determine what is happening.
Edit
I copied your code into an app of mine and the UIImagePickerController successfully presented and dismissed and my UINavigationController's UINavigationBar was still there. I truly believe that the problem lays elsewhere in your architecture. If you upload a zip w/ an example project I will take a look.
Simply try following code it will work
SettingsViewController *settings = [[SettingsViewController alloc] init];
UINavigationController *navcont = [[UINavigationController alloc] initWithRootViewController:settings];
[self presentModalViewController:navcont animated:YES];
[settings release];
[navcont release];
One need to present the navigation controller in order to have navigation bar on the presented controller
I think I've seen this behavior when presenting a view controller on the wrong VC. Are you calling presentModalViewController on the navigation controller or the individual VC?
Try calling it from the navigationController if you aren't already.
[self.navigationController presentModalViewController:myVC animated:YES];
If you present a controller as model, View controller will appear to total view.
If you want to access the navigation controller properties over the model view, You need to create another navigation controller reference and it continues as previous.
This may be useful for you.
Check this out. This is Apple's Documentation under UIViewController Class Reference:
It clearly mentions that modal view always presents in full screen mode, so it is obvious that navigation bar will be hidden. So put the seperate navigation bar on modal view to navigate back.
presentModalViewController:animated:
Presents a modal view managed by the given view controller to the user.
- (void)presentModalViewController:(UIViewController *)modalViewController animated:(BOOL)animated
Parameters
modalViewController
The view controller that manages the modal view.
animated
If YES, animates the view as it’s presented; otherwise, does not.
Discussion
On iPhone and iPod touch devices, the view of modalViewController is always presented full screen. On iPad, the presentation depends on the value in the modalPresentationStyle property.
Sets the modalViewController property to the specified view controller. Resizes its view and attaches it to the view hierarchy. The view is animated according to the transition style specified in the modalTransitionStyle property of the controller in the modalViewController parameter.
Availability
Available in iOS 2.0 and later.
Hope this helps you understand that hiding the whole view along with navigation controller is default behaviour for modal view so try putting a seperate navigation bar in modal view to navigate.
You can check it further on this link
http://developer.apple.com/library/ios/#documentation/uikit/reference/UIViewController_Class/Reference/Reference.html
AddContactVC *addController =[self.storyboard instantiateViewControllerWithIdentifier:#"AddContactVC"];
UINavigationController *navigationController = [[UINavigationController alloc]initWithRootViewController:addController];
[self presentViewController:navigationController animated:YES completion: nil];
working for me shows navigation bar
Emphatic and Devin –
As I started reading through the Apple docs to get familiar with the problem, I noticed that the method you're using, presentModalViewController:animated:, appears to be deprecated in favor of presentViewController:animated:completion:. Perhaps you should try to use that method instead.
For your convenience, take a look for yourself:
presentModalViewController:animated: reference
I'll try to put together a quick test program to see whether what I've said above is actually true. But give it a shot – maybe it'll help!
Xcode has a template that does pretty close to what you're doing. from the results, i don't think you should be attempting to perform [self.navigationController presentModalViewController:vc] and [self.navigationController dismissModalViewControllerAnimated:] , but rather simply [self presentModalViewController:] and [self dismissModalViewControllerAnimated:] .
to see how the template does this for yourself, you can use the new project wizard in xcode 4.3 . perhaps it will provide some guidance:
from that choice, choose Next, then give your test project a name, choose "Universal", turn off automatic reference counting, hit next, save where you want it.
now, click on the target and switch the deployment target to 4.3 (or 4.0 if you prefer) for your testing purposes, and switch to your device or the iOS 4.3 simulator .
finally, substitute the following code in applicationDidFinishLaunching:withOptions: in the created AppDelegate.m:
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
// Override point for customization after application launch.
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
self.mainViewController = [[[MainViewController alloc] initWithNibName:#"MainViewController_iPhone"
bundle:nil] autorelease];
} else {
self.mainViewController = [[[MainViewController alloc] initWithNibName:#"MainViewController_iPad"
bundle:nil] autorelease];
}
UINavigationController* navigationController
= [[UINavigationController alloc] initWithRootViewController:self.mainViewController];
self.window.rootViewController = navigationController;
[self.window makeKeyAndVisible];
return YES;
now, when i run this, it doesn't hide the navigationBar. and in the created MainViewController.m from the template, you'll see how it presents the modal view controller and dismisses it from the controller itself and not from the navigation controller. for good measure, to make the template code more like your own, go into MainViewController.m and delete the line that sets the modal view controller transition style ...
(of course, in iOS 5, with storyboards, the same thing can all be accomplished with modal segues ... which is how i've done this for apps that i'm not supporting for pre-5.0 that present a modalViewController in this fashion.)
One of the best solution it to use this Category MaryPopin
https://github.com/Backelite/MaryPopin