Presenting a view controller triggers UIApplicationDidChangeStatusBarOrientationNotification - ios

I've got a universal app that supports both iPad and iPhone, and on the iPhone it supports UIInterfaceOrientationPortraitUpsideDown.
When I run the app on an iPhone, then rotate the device to UIInterfaceOrientationPortraitUpsideDown, then modally present a view controller like this:
UIViewController* vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
UINavigationController* navigationController = [[UINavigationController alloc]
initWithRootViewController:vc];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentViewController:navigationController animated:YES completion:nil];
Very unexpectedly, this triggers UIApplicationDidChangeStatusBarOrientationNotification.
OK, so maybe this is because the modal view controller does not support the upside-down portrait orientation. Let's make a custom subclass of UIViewController that overrides supportedInterfaceOrientations:
- (NSUInteger) supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
But no difference, presenting the custom VC still triggers the notification. I also noticed that the notifications is not triggered on iPad, only on iPhone. Finally, the behaviour is the same in iOS 8 and iOS 7 (specifically I am testing on iOS 8.1 and iOS 7.1, but I doubt that this makes any difference).
Question: Is it normal that this notification is sent when a modal VC is presented? Why does it happen only for UIInterfaceOrientationPortraitUpsideDown, but not for the regular portrait or any of the two landscape orientations? And why does it happen only on iPhone, not on iPad?
Note: I can provide a more complete, minimal running example on request.

The problem was, indeed, that the modally presented view controller did not support UIInterfaceOrientationPortraitUpsideDown. My idea to add an override of supportedInterfaceOrientations was correct, but where I went wrong was that I added the override to the custom subclass of UIViewController - but this is not the presented view controller!
The presented view controller, as my code snippet shows, is the UINavigationController instance. One correct solution to my problem therefore is to create a subclass of UINavigationController and add supportedInterfaceOrientations to that subclass.
Another correct, but in my case more elegant solution than subclassing is to make use of the UINavigationControllerDelegate protocol.
Step 1: Modify the code snippet from my question:
UIViewController* vc = [[UIViewController alloc] initWithNibName:nil bundle:nil];
UINavigationController* navigationController = [[UINavigationController alloc]
initWithRootViewController:vc];
navigationController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
// --- THE NEXT LINE IS NEW ---
navigationController.delegate = self;
[self presentViewController:navigationController animated:YES completion:nil];
Step 2: Make sure that the class that contains the presenting code (= the above code snippet) adopts the UINavigationControllerDelegate protocol.
Step 3 (yes, there is a step 3): Add this protocol method to the newly designated delegate class:
- (NSUInteger) navigationControllerSupportedInterfaceOrientations:(UINavigationController*)navigationController
{
return UIInterfaceOrientationMaskAll;
}
If you have many places where you present view controllers you will probably want to have some shared object that adopts UINavigationControllerDelegate, but that's part of your application design.

Related

Trouble presenting view controller from within VC within UITabViewController

My app is built around a UITabBarController and within the first View Controller I am attempting to present a "settings view" for a user but for some reason if this settings view gets presented more than once all buttons stop functioning and the app shits down completely.
Within FirstViewController.m I have:
- (IBAction)showSettings:(id)sender
{
UIViewController *settingsView = [self.storyboard instantiateViewControllerWithIdentifier:#"SettingsViewController"];
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:settingsView];
[self.navigationController presentViewController:nav animated:YES completion:nil];
}
which is attached to a UIButton labeled "settings".
Within SettingsViewController.m I have:
- (IBAction)done
{
NSLog(#"DONE");
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
which is attached to a UIButton labeled "done" in the top left corner of SettingsViewController.
The first time I press "settings" then "done" it works without a problem but if I ever press the "settings" button again the "done" button loses functionality completely (completely unclickable) and the user is forced to restart the app in order to continue using it.
My console prints this when the app loads which I believe outlines the problem:
Unbalanced calls to begin/end appearance transitions for
<UITabBarController: 0x7f97f5f1e780>
But if I try to make an instance of "TabBarController" and present the view that way I just get an error that I am attempting to present a VC not within the hierarchy...
I am just not sure what the appropriate way to present/dismiss a view controller within a UITabBarController is but as of right not my app is lacking basic functionality. I don't see why FirstViewController can't just present the view normally.
Any help is greatly appreciated, this is a very annoying Bug and I wasn't able to find a workable solution anywhere else online. Let me know if I should provide more info/code
Edit: Changed typo UITabViewController to UITabBarController
I don't understand why you are instantiating a Nav controller.
Simply show modally. it should work.
Do you need a "go back" look and feel?
If you already have a nav controller in your hierarchy you can push settings.
anyway: when you do:
UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:settingsView];
[self.navigationController presentViewController:nav animated:YES completion:nil];
in first line you create a LOCAL variable
in second you use self. self.navigationController
1) who holds "nav"?
2) If you already have a nav controller (as self.navigationController shows.. or is NIL?) why use "nav"?
It seems you set the dismiss to the navigation controller and not the settings view controller. Try using self.dismiss instead of self.navigationcontroller.dismiss
You to define root view controller for navigation push and pop both
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UINavigationController *navController;
self.navController = [[UINavigationController alloc] initWithRootViewController: objcReg];
self.navController.navigationBarHidden =YES;
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] ;
self.window.rootViewController = self.navController;
[self.window makeKeyAndVisible];
//[self.navigationController presentViewController:objectofViewCantroller animated:YES completion:NULL];
//[self.navigationController popViewControllerAnimated:YES];

Storyboard - Open UIViewController as UIPopoverController

For my iPad version of the app I want to open my UIViewController as a UIPopoverController from the click of a button. So, the real code for view controller opening is below. How can I easily convert such code into opening a UIViewController as a UIPopoverController?
Code:
UIStoryboard *sb = [UIStoryboard storyboardWithName:[[NSBundle mainBundle].infoDictionary objectForKey:#"UIMainStoryboardFile"] bundle:[NSBundle mainBundle]];
UIViewController *aboutView = [sb instantiateViewControllerWithIdentifier:#"AboutViewController"];
aboutView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:aboutView animated:YES completion:NULL];
Use something like:
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
UIPopoverController *poc = [[UIPopoverController alloc] initWithContentViewController:aboutView];
[poc presentPopoverFromRect:CGRectMake(somePoint.x, somePointY, 1.0, 1.0) inView:self.view permittedArrowDirections: UIPopoverArrowDirectionAny animated:YES];
} else {
aboutView.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self presentViewController:aboutView animated:YES completion:NULL];
}
You can also present the popover from a button and you also can control the direction in which the popover will be presented. If you use UIPopoverArrowDirectionAny, iOS will make a smart decision for the popover to be visible.
You should also keep a strong reference to your popover and only present it while it is nil to make sure the popover is only present once. That means if the popover is dismissed, set the property holding it to nil.
A common solution is to have two storyboards, one for iPhone, one for iPad. That way you can use the popover segue in the iPad storyboard and the modal segue in the iPhone storyboard. You can readily arrange your Info.plist so that the correct storyboard loads automatically at launch time. You will still need some conditional code, though, since your code will respond differently to having a presented view controller than having a popover.

Dismiss UIViewController after presenting another one

I'm developing a single view iOS 5.0+ app and I'm going to navigate throw my ViewControllers this way:
SearchViewController* search =
[[SearchViewController alloc] initWithNibName:#"SearchViewController"
bundle:nil];
[self presentViewController:search
animated:NO
completion:nil];
My question is, if I'm opening SearchViewController from HomeViewController, is HomeViewController dismissed after SearchViewController is shown?
I have a lot of UIViewControllers and I don't know if all of them will be on memory while user is navigating between them.
If You want to Present Only one Viewcontroller you can try like,
SearchViewController* search =
[[SearchViewController alloc] initWithNibName:#"SearchViewController"
bundle:nil];
[self dismissViewControllerAnimated:NO completion:^{
[self presentViewController:search
animated:NO
completion:nil];
}];
When you present a ViewController from another ViewController, they never get released from memory. To release them from memory you need to explicitly dismiss them.
The method presentViewController:animated:completion: sets the
presentedViewController property to the specified view controller,
resizes that view controller’s view and then adds the view to the view
hierarchy.
So you see you are getting a stack of ViewControllers and adding a View on top of another.

iOS view controller memory not released after it's been dismissed

When the user clicks a button it presents a new tab bar view controller with two view controllers. Here's how I do that
ACLevelDownloadController *dvc = [[ACLevelDownloadController alloc] initWithNibName:#"ACLevelDownloadController" bundle:[NSBundle mainBundle]];
ACInstalledLevelsController *ivc = [[ACInstalledLevelsController alloc] initWithNibName:#"ACInstalledLevelsController" bundle:[NSBundle mainBundle]];
UITabBarController *control = [[UITabBarController alloc] init];
control.viewControllers = #[dvc, ivc];
dvc.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFeatured tag:0];
ivc.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemDownloads tag:1];
[self presentViewController:control animated:YES completion:nil];
this works fine. I dismiss that view controller with a dismiss method in both the ACLevelDownloadController and ACInstalledLevelsController. That also works fine. What's strange is that the memory usage goes up when I present the view controller
but it never goes back down. If I present it again, it goes up even more
I'm using ARC. Why is the memory that the view controllers use not being released after they are dismissed?
EDIT
The way they are dismissed is both ACLevelDownloadController and ACInstalledLevelsController have IBActions hooked up that call this method when they are clicked
- (void)dismiss:(id)sender{
[self dismissViewControllerAnimated:YES completion:nil];
}
What we can observe from the memory usage graph is that the tabViewController is not being dismissed properly and it builds up in the stack. While dismissing you have to allow the viewController which presented the tabViewController to dismiss it. It is its responsibility to dismiss. Also keep weak references for Outlets and assign any strong references to nil** in viewWillDisapper: . You can present a viewController modally as a temporary interruption to obtain important information from the user. If its not the case here, you can remove presenting modally. Check this link. Hope this helps :)

UITableViewController subView rotation

I'm new to stackoverflow and to objective-C programming. I have searched for the issue described below, but I'm not able to find a working solution.
My application is a simple offline browsing app, with navigation structure.
In the appDelegate I load the RootViewController (UITableViewController) in one of the following ways:
Solution A
[window addSubview:navigationController.view];
[window makeKeyAndVisible];
return YES;
Solution B
RootViewController* rootviewcontroller = [[RootViewController alloc] initWithNibName:#"RootViewController" bundle:nil];
navigationController = [[UINavigationController alloc] initWithRootViewController:rootviewcontroller];
The rootViewController simply push some views, i.e.
TipAndTrickViewController *mTipAndTrick = [[TipAndTrickViewController alloc]initWithNibName:#"TipAndTrickViewController" bundle:nil];
[self.navigationController pushViewController:mTipAndTrick animated:YES];
In the deeper view I present a detail modalView (UIViewController).
What I want is to enable autorotate only in this last view. The portait orientation is the desired for all the previoues wiews. The last view implements in the right way:
shouldAutorotateToInterfaceOrientation:interfaceOrientation
shouldAutorotate
willRotateToInterfaceOrientation:toInterfaceOrientation
duration:duration
willAnimateRotationToInterfaceOrientation:interfaceOrientation
duration:duration
Overriding
shouldAutorotate
shouldAutorotateToInterfaceOrientation:interfaceOrientation
making them returning NO/YES in the rootViewController and setting the allowed orientation in the desired way, using
supportedInterfaceOrientations
(both in rootViewCOntroller and in the last view), I get those results:
if I use Solution A all the views don't rotate.
if I use Solution B all the views always rotate.
What I'm doing in the wrong way?
Thank you in advance for your help
Add these to your viewController and let me know if it works or not .
// iOS5 Rotation
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
// iOS6 Rotation
- (NSUInteger)supportedInterfaceOrientations
{
return UIInterfaceOrientationMaskAll;
}
- (BOOL)shouldAutorotate
{
return YES;
}

Resources