This one is tricky. I have a subclass of UINavigationController that overrides pop/push and present/dismiss methods. Here I customise the behaviour to set the correct size if the UINavigationController subclass is contained in a popover. Nothing too fancy, but I do it this way to don't write subclasses of all my ViewControllers and use Autolayout.
However, the completion blocks of the presentViewController:animated:completion: and dismissViewControllerAnimated:completion: are not being executed. And this is the weird part: the exact same code on iPhone works correctly, but on iPad is not executing the blocks. Here is a code sample.
#interface SBNavigationController : UINavigationController
#end
#implementation SBNavigationController
- (void) presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion
{
if ([viewControllerToPresent isKindOfClass:[UINavigationController class]])
{
UINavigationController *nav = (UINavigationController *) viewControllerToPresent;
[nav.topViewController setContentSizeForViewInPopover:kFullSizePopover];
} else
{
[viewControllerToPresent setContentSizeForViewInPopover:kFullSizePopover];
}
viewControllerToPresent.modalPresentationStyle = UIModalPresentationCurrentContext;
[super presentViewController:viewControllerToPresent animated:flag completion:completion];
}
- (void)dismissViewControllerAnimated:(BOOL)flag completion:(void (^)(void))completion ;
{
[super dismissViewControllerAnimated:flag completion:completion];
}
#end
And the code using it is this:
#implementation SBInviteFBContactViewController
...
- (void) createInviteByMailViewController
{
SBInviteMailViewController *mailInvite = [[SBInviteMailViewController alloc] initWithDelegate:self userInfo:_userInfo];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:mailInvite];
[self.navigationController presentViewController:navController
animated:YES
completion:^{
NSLog(#"presentViewController:");
}];
}
#pragma mark SBInviteMailProtocol
- (void) invitedMailContacts:(NSArray *)contacts;
{
[self.navigationController dismissViewControllerAnimated:YES
completion:^{
NSLog(#"animation Ended");
if (contacts) {
[self.delegate invitedMailContact:contacts];
[self popViewControllerAnimated:YES];
}
}];
}
...
#end
Any ideas?
This seems to be a huge bug. Please report it to Apple (and I am about to do the same). I found my way here because I just discovered the same bug myself, and did a google search to see if anyone else was talking about it.
I've created a very small demonstration project, whose architecture is like this:
ViewController - the main view controller
Its view contains a button Tap Me.
PopoverViewController - presented in popover
When you tap Tap Me in the main ViewController, it creates a UIPopoverController with this vc, PopoverViewController, as its content view controller; its view, too, contains a button Tap Me.
PopoverViewController2 - presented "modally" in same popover
PopoverViewController2 has its modalPresentationStyle set to UIModalPresentationCurrentContext so it can appear inside the popover. When you tap Tap Me in the popover, PopoverViewController calls presentViewController:....
Here's the code:
- (IBAction)doTapMe:(id)sender {
NSLog(#"about to present view controller");
[self presentViewController:[PopoverViewController2 new] animated:YES completion:^{
NSLog(#"in completion handler"); // never called!
}];
NSLog(#"did present view controller");
}
The log reads "about to present view controller" and "did present view controller", but "in completion handler" never appears, even though the "modal" view controller's view appears in the popover just fine.
(Moreover, changing to animated:NO not only doesn't fix it, it causes a visual glitch.)
The UIModalPrsentationCurrentContext style is only available if you are compiling against iOS 3.2 or greater. Can't imagine that is the issue though.
The docs for UIModalPrsentationCurrentContext also say:
When presenting a view controller in a popover, this presentation style is supported only if the transition style is UIModalTransitionStyleCoverVertical. Attempting to use a different transition style triggers an exception. However, you may use other transition styles (except the partial curl transition) if the parent view controller is not in a popover.
This is a strange one.
Any chance you're running a different version of iOS on the iPhone and the iPad?
Related
I want to recreate the search UI shown in the iOS 7/8 calendar app. Presenting the search UI modally isn't a problem. I use UISearchController and modally present it just like the UICatalog sample code shows which gives me a nice drop down animation. The issue comes when trying to push a view controller from the results view controller. It isn't wrapped in a navigation controller so I can't push onto it. If I do wrap it in a navigation controller then I don't get the default drop down animation when I present the UISearchController. Any ideas?
EDIT:
I got it to push by wrapping my results view controller in a nav controller. However the search bar is still present after pushing the new VC onto the stack.
EDIT (2):
DTS from Apple said that the calendar app uses a non-standard method to push from search results. Instead they recommend removing focus from the search controller then pushing and returning focus on pop. This is similar to the way search in the settings app works I imagine.
Apple has gotten very clever there, but it's not a push, even though it looks like one.
They're using a custom transition (similar to what a navigation controller would do) to slide in a view controller which is embedded in a navigation controller.
You can spot the difference by slowly edge-swiping that detail view back and letting the previous view start to appear. Notice how the top navigation slides off to the right along with the details, instead of its bar buttons and title transitioning in-place?
Update:
The problem that you're seeing is that the search controller is presented above your navigation controller. As you discovered, even if you push a view controller onto a navigation controller's stack, the navigation bar is still beneath the search controller's presentation, so the search bar obscures any (pushed view controller's) navigation bar.
If you want to show results on top of the search controller without dismissing it, you'll need to present your own modal navigation view controller.
Unfortunately, there's no transition style which will let you present your navigation controller the same way the built-in push animation behaves.
As I can see, there are three effects that need to be duplicated.
The underlying content dims, as the presented view appears.
The presented view has a shadow.
The underlying content's navigation completely animates off-screen, but its content partially animates.
I've reproduced the general effect within an interactive custom modal transition. It generally mimic's Calendar's animation, but there are some differences (not shown), such as the keyboard (re)appearing too soon.
The modal controller that's presented is a navigation controller. I wired up a back button and edge swipe gesture to (interactively) dismiss it.
Here are the steps that are involved:
In your Storyboard, you would change the Segue type from Show Detail to Present Modally.
You can leave Presentation and Transition set to Default, as they'll need to be overridden in code.
In Xcode, add a new NavigationControllerDelegate file to your project.
NavigationControllerDelegate.h:
#interface NavigationControllerDelegate : NSObject <UINavigationControllerDelegate>
NavigationControllerDelegate.m:
#interface NavigationControllerDelegate () <UIViewControllerTransitioningDelegate>
#property (nonatomic, weak) IBOutlet UINavigationController *navigationController;
#property (nonatomic, strong) UIPercentDrivenInteractiveTransition* interactionController;
#end
- (void)awakeFromNib
{
UIScreenEdgePanGestureRecognizer *panGestureRecognizer = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:#selector(handlePan:)];
panGestureRecognizer.edges = UIRectEdgeLeft;
[self.navigationController.view addGestureRecognizer:panGestureRecognizer];
}
#pragma mark - Actions
- (void)handlePan:(UIScreenEdgePanGestureRecognizer *)gestureRecognizer
{
UIView *view = self.navigationController.view;
if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
{
if (!self.interactionController)
{
self.interactionController = [UIPercentDrivenInteractiveTransition new];
[self.navigationController dismissViewControllerAnimated:YES completion:nil];
}
}
else if (gestureRecognizer.state == UIGestureRecognizerStateChanged)
{
CGFloat percent = [gestureRecognizer translationInView:view].x / CGRectGetWidth(view.bounds);
[self.interactionController updateInteractiveTransition:percent];
}
else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
{
CGFloat percent = [gestureRecognizer translationInView:view].x / CGRectGetWidth(view.bounds);
if (percent > 0.5 || [gestureRecognizer velocityInView:view].x > 50)
{
[self.interactionController finishInteractiveTransition];
}
else
{
[self.interactionController cancelInteractiveTransition];
}
self.interactionController = nil;
}
}
#pragma mark - <UIViewControllerAnimatedTransitioning>
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)__unused presented presentingController:(UIViewController *)__unused presenting sourceController:(UIViewController *)__unused source
{
TransitionAnimator *animator = [TransitionAnimator new];
animator.appearing = YES;
return animator;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)__unused dismissed
{
TransitionAnimator *animator = [TransitionAnimator new];
return animator;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)__unused animator
{
return nil;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)__unused animator
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-conditional-omitted-operand"
return self.interactionController ?: nil;
#pragma clang diagnostic pop
}
The delegate will provide the controller with its animator, interaction controller, and manage the screen edge pan gesture to dismiss the modal presentation.
In Storyboard, drag an Object (yellow cube) from the object library to the modal navigation controller. Set its class to ourNavigationControllerDelegate, and wire up its delegate and navigationController outlets to the storyboard's modal navigation controller.
In prepareForSegue from your search results controller, you'll need to set the modal navigation controller's transitioning delegate and modal presentation style.
navigationController.transitioningDelegate = (id<UIViewControllerTransitioningDelegate>)navigationController.delegate;
navigationController.modalPresentationStyle = UIModalPresentationCustom;
The custom animation that the modal presentation performs is handled by transition animator.
In Xcode, add a new TransitionAnimator file to your project.
TransitionAnimator.h:
#interface TransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>
#property (nonatomic, assign, getter = isAppearing) BOOL appearing;
TransitionAnimator.m:
#implementation TransitionAnimator
#synthesize appearing = _appearing;
#pragma mark - <UIViewControllerAnimatedTransitioning>
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.3;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
// Custom animation code goes here
}
The animation code is too long to provide within an answer, but it's available in a sample project which I've shared on GitHub.
Having said this, the code, as it stands, was more of a fun exercise. Apple has had years to refine and support all their transitions. If you adopt this custom animation, you may find cases (such as the visible keyboard) where the animation doesn't do what Apple's does. You'll have to decide whether you want to invest the time to improve the code to properly handle those cases.
I know this thread is old, but there seems to be a much simpler approach to getting the desired behavior.
The important thing to realize is the UISearchController is presented from the source controller, which is a view controller inside the navigation controller. If you inspect the view hierarchy, you see that the search controller, unlike regular modal presentations, isn't presented as a direct child of the window, but rather as a subview of the navigation controller.
So the general structure is
UINavigationController
MyRootViewController
UISearchViewController (presented pseudo-"modally")
MyContentController
Essentially you just need to get from the MyContentController up to the MyRootViewController, so you can access its navigationController property. In my tableView:didSelectRowAtIndexPath: method of my search content controller, I simply use the following to access my root view controller.
UINavigationController *navigationController = nil;
if ([self.parentViewController isKindOfClass:[UISearchController class]]) {
navigationController = self.parentViewController.presentingViewController.navigationController;
}
From there you can easily push something onto the navigation controller, and the animation is exactly what you'd expect.
EDIT: an alternate solution that doesn't rely on a UIWindow. I think the effect is very similar to the calendar app.
#interface SearchResultsController () <UINavigationControllerDelegate>
#end
#implementation SearchResultsController
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// this will be the UINavigationController that provides the push animation.
// its rootViewController is a placeholder that exists so we can actually push and pop
UIViewController* rootVC = [UIViewController new]; // this is the placeholder
rootVC.view.backgroundColor = [UIColor clearColor];
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController: rootVC];
nc.modalPresentationStyle = UIModalPresentationCustom;
nc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[UIView transitionWithView: self.view.window
duration: 0.25
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent
animations: ^{
[self.parentViewController presentViewController: nc animated: NO completion: ^{
UIViewController* resultDetailViewController = [UIViewController alloc];
resultDetailViewController.title = #"Result Detail";
resultDetailViewController.view.backgroundColor = [UIColor whiteColor];
[nc pushViewController: resultDetailViewController animated: YES];
}];
}
completion:^(BOOL finished) {
nc.delegate = self;
}];
}
- (void) navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// pop to root? then dismiss our window.
if ( navigationController.viewControllers[0] == viewController )
{
[UIView transitionWithView: self.view.window
duration: [CATransaction animationDuration]
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowAnimatedContent
animations: ^{
[self.parentViewController dismissViewControllerAnimated: YES completion: nil];
}
completion: nil];
}
}
#end
ORIGINAL solution:
Here's my solution. I start out using the same technique you discovered in the UICatalog example for showing the search controller:
- (IBAction)search:(id)sender
{
SearchResultsController* searchResultsController = [self.storyboard instantiateViewControllerWithIdentifier: #"SearchResultsViewController"];
self.searchController = [[UISearchController alloc] initWithSearchResultsController:searchResultsController];
self.searchController.hidesNavigationBarDuringPresentation = NO;
[self presentViewController:self.searchController animated:YES completion: nil];
}
In my example, SearchResultsController is a UITableViewController-derived class. When a search result is tapped it creates a new UIWindow with a root UINavigationController and pushes the result-detail view controller to that. It monitors for the UINavigationController popping to root so it can dismiss the special UIWindow.
Now, the UIWindow isn't strictly required. I used it because it helps keep the SearchViewController visible during the push/pop transition. Instead, you could just present the UINavigationController from the UISearchController (and dismiss it from the navigationController:didShowViewController: delegate method). But modally-presented view controllers present on an opaque view by default, hiding what's underneath. You could address this by writing a custom transition that would be applied as the UINavigationController's transitioningDelegate.
#interface SearchResultsController () <UINavigationControllerDelegate>
#end
#implementation SearchResultsController
{
UIWindow* _overlayWindow;
}
- (void) tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// this will be the UINavigationController that provides the push animation.
// its rootViewController is a placeholder that exists so we can actually push and pop
UINavigationController* nc = [[UINavigationController alloc] initWithRootViewController: [UIViewController new]];
// the overlay window
_overlayWindow = [[UIWindow alloc] initWithFrame: self.view.window.frame];
_overlayWindow.rootViewController = nc;
_overlayWindow.windowLevel = self.view.window.windowLevel+1; // appear over us
_overlayWindow.backgroundColor = [UIColor clearColor];
[_overlayWindow makeKeyAndVisible];
// get this into the next run loop cycle:
dispatch_async(dispatch_get_main_queue(), ^{
UIViewController* resultDetailViewController = [UIViewController alloc];
resultDetailViewController.title = #"Result Detail";
resultDetailViewController.view.backgroundColor = [UIColor whiteColor];
[nc pushViewController: resultDetailViewController animated: YES];
// start looking for popping-to-root:
nc.delegate = self;
});
}
- (void) navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
// pop to root? then dismiss our window.
if ( navigationController.viewControllers[0] == viewController )
{
[_overlayWindow resignKeyWindow];
_overlayWindow = nil;
}
}
#end
As you present a viewController the navigationController becomes unavailable. So you have to dismiss your modal first and then push another viewController.
UISearchController must be rootViewController of a UINavigationController and then you present navigation controller as modal.
EDIT
After some more digging around it has become apparent that if i try to call a segue from the view controller class after a fast application switch to/from Safari i get an exception stating that the segue does not exist. It definitely does, in the VC that is loaded on screen, and i have checked the spelling and it's 100% correct.
What makes it even weirder is that if i call that exact same method from an IBAction it works fine.
Here's the code i am using:
-(void)goToPhotoPage{
[self performSegueWithIdentifier:#"twitterAuthComplete" sender:self];
}
If i call this when the app resumes i get an exception, however if i call it from this IBAction, attached to a UIButton, with doing nothing different the segue works as expected and no exception.
- (IBAction)twitterToPhoto:(id)sender
{
[self goToPhotoPage];
}
Arrrrgrghrhhgrgrg!
ORIGINAL QUESTION
I am working on an iPad application that uploads user photos to Facebook/Twitter.
As part of the process i have to jump to Safari to do OAuth for twitter, the app then jumps back via a specific URL and get's the tokens e.t.c
However for some reason when the application re-awakes i cannot trigger any segue's or manually load any VC's.
I have a success block in my AppDelegae which get's called when the upload is complete which calls a method in the VC to close a spinner and segue to the success view.
The spinner stops as expected, but no matter what i try i cannot get the segue to work.
Everything is hooked up, the segue has an ID and i get no error or exceptions in the console, just nothing happens. The only way i can get code triggered segue to work after this point is to use a user trigger one connect to a UIButton, after that one complete they start to work again.
Here is the code with callback in my AppDelegate:
- (void)postToTwitter:(NSString*)postText image:(UIImage*)image
{
NSData *data = UIImageJPEGRepresentation(image, 0.5);
[_twitter postStatusUpdate:postText
mediaDataArray:#[data]
possiblySensitive:nil
inReplyToStatusID:nil
latitude:nil
longitude:nil
placeID:nil
displayCoordinates:nil
uploadProgressBlock:^(NSInteger bytesWritten, NSInteger totalBytesWritten, NSInteger totalBytesExpectedToWrite) {
} successBlock:^(NSDictionary *status) {
ViewController * vc = [[ViewController alloc]init];
[vc triggerSuccess];
} errorBlock:^(NSError *error) {
ViewController * vc = [[ViewController alloc]init];
[vc uploadFail:error];
}];
}
in the VC i have tried the following:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
}
As well as:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
In a moment of pure desperation i even tried:
- (void)triggerSuccess
{
[self segueToSuccess];
}
- (void)segueToSuccess
{
[self hideMessage];
//[self.navigationController performSegueWithIdentifier:#"approveSegue" sender:self];
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
[self performSelector:#selector(segueToSuccessPart2) withObject:nil afterDelay:0.25];
}
- (void)segueToSuccessPart2
{
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"done"];
[self.navigationController pushViewController:controller animated:YES];
}
I've obviously missed something, and i'm guessing it's to do with the application going into the background and the VC being "unloaded" from memory, and needing re-instatiating, but i'm not sure where to begin with that.
Any help would be greatly appreciated as i'm about to throw the computer out of the window . .
Thanks
Gareth
Edit 1
After doing some more troubleshooting it appears that during this call in the VC:
[self dismissViewControllerAnimated:NO completion:nil];
[self.navigationController popToRootViewControllerAnimated:NO];
self.navigationController is nil . . .
I'm guessing that is likely the cause of the issues i'm seeing?
Edit 2
So as suggested by Kelin, i am trying to make sure i retain the Navigation Controller.
I have done the following in AppDelegte:
#property (nonatomic, strong) UINavigationController *navController;
and
#synthesize navController;
But when i try to access this from the VC using the below code, it's still nil . .
AppDelegate *appDelegate = [[UIApplication sharedApplication]delegate];
UINavigationController *navController = appDelegate.navController;
[self dismissViewControllerAnimated:NO completion:nil];
[navController popToRootViewControllerAnimated:NO];
Edit 3
I have also added this to the AppDelegate, which means the Navigation Controller is no longer nil. But still nothing happens . . .
if (!self.navController)
{
self.navController = [[UINavigationController alloc]initWithNibName:#"Main" bundle:nil];
}
The problem is you trying to perform segue from storyboard using programmatically created View Controller.
You use -performSegueWithIdentifier: sender: completely wrong. In this method "sender" is a button, or any other control which action initialises segue, but not the view controller to be pushed.
Usually segue is created with storyboard. But if you really need to create it manually, call:
-[UIStoryboardSegue initWithIdentifier:source:destination:]
where source and destination are view controllers.
Read this: https://developer.apple.com/library/ios/recipes/xcode_help-interface_builder/articles-storyboard/StoryboardSegue.html
or this (about custom segues):
You can not do both dismiss and pop together either you have to pop or dismiss the view controller and
Every ViewController have it's own NavigationController which is default point out to navigation controller you added to AppDelegate.
So, You also can access it by self.navigationController (Edit 3)
Also check whether you initialised navigation controller in
AppDelegate
self.navController = [[UINavigationController alloc] initWithRootViewController:firstVC];
I have an app with several views, each view is its own view controller. I am switching views by using the following method.
TableViewSelect *tableview = [[TableViewSelect alloc] initWithNibName:nil bundle:nil];
[self presentViewController: tableview animated:YES completion:NULL];
I have been told in another question that this would cause the new view to be displayed over the previous view but without deallocating the memory. So as user flips though views, the memory is always growing. i.e. Memory leak. Can anyone tell me how to switch views where the memory is deallocated when leaving the view.
Thanks
At some point, it would be advisable to do:
//in TableViewSelect class on some action
[self dismissViewControllerAnimated:YES completion:nil];
Plus... depending on your flow, but say you have the following classes:
AppDelegate
ViewController
FirstVC
SecondVC
ThirdVC
Say:
AppDelegate's rootViewController is ViewController
ViewController presents FirstVC
FirstVC presents SecondVC
So now... you're on SecondVC and need to show FirstVC again, then in this case, to conserve memory, you'll need to dismiss SecondVC.
But... if you've something even remotely like:
FirstVC -> SecondVC -> ThirdVC (back to) -> FirstVC
then you're better off with a UINavigationController because this seems like a potential memory hog.
If you only want to display one view at a time, you could do something like this...
#property (nonatomic, strong) UIVIewController *currentViewController;
- (void)presentViewController:(UIViewController *)viewControllerToPresent animated: (BOOL)animated completion:(void (^)(void))completion {
if (self.currentViewController != nil) {
[self.currentViewController dismissViewControllerAnimated:animated completion:^{
self.currentViewController = viewControllerToPresent;
[super presentViewController:viewControllerToPresent animated:animated completion:completion];
}];
} else {
self.currentViewController = viewControllerToPresent;
[super presentViewController:viewControllerToPresent animated:animated completion:completion];
}
}
I have now been stuck on this problem for more then 2 weeks! In my project, I have 1 single ViewController(slide) I want to enable both landscape and portrait in. The rest of all controllers/views(slides) I want to enable portrait-mode only.
The tricky part is, the "ViewController" I am referring to is connected to both NavigationControllers and TabBarControllers. See the scheme below where the ViewController I want to enable both landscape/portrait is named: ReferredViewController.
TabBarController ----> NavigationController ----> FristViewController --(push event)--> ReferredViewController
So far I have tried to make a CATEGORY for both NavigationControllers and TabBarControllers. But since my NavigationControllers and TabBarControllers are placed at the very start of the project this will set the rules for the whole project. My ReferredViewController is placed at the end or in the middle of the projects "storyboard". I have tried to set the rules by code aswell for the single ReferredViewController without any success.
My best shot is to change the event between FirstViewController and ReferredViewController from "push" to "modal". ReferredViewController can then rotate both portrait/landscape and the rest of the project is locked in portrait. BUT, as you may know all navigations (NavigationBar) will be lost and the user will become stuck at that single slide.
So I am trying to enable the NavigationBar with the following code example in the ReferredViewController.m file:
ShowTaskViewController *detailViewController = [[ShowTaskViewController alloc] init];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:detailViewController];
navController.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self.navigationController presentModalViewController:navController animated:YES completion:nil];
[navController release];
[detailViewController release];
But ofc nothing happens and I am back to square one again :O. FML!
In this line:
[self.navigationController presentModalViewController:navController
animated:YES
completion:nil];
you are conflating two UIViewController instance methods:
- (void)presentViewController:(UIViewController *)viewControllerToPresent
animated:(BOOL)flag
completion:(void (^)(void))completion
- (void)presentModalViewController:(UIViewController *)modalViewController
animated:(BOOL)animated
The first of these is now the standard, the second method was deprecated in ios6.
Also the presenting view controller should be self (the ReferredViewController), not self's navigationController.
Your presented view controller can dismiss itself thus
[[self presentingViewController] dismissViewControllerAnimated:YES
completion:(void (^)(void))completion];
But take a look at fibnochi's answer, it may be a better way for you to achieve your result.
you have to over ride UITaBarController because it is you base view controller. I have done this for my navigation controller. Tell me if this helped.
#interface UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation;
- (BOOL) shouldAutorotate;
- (NSUInteger) supportedInterfaceOrientations;
#end
#implementation UINavigationController (Autorotation)
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return YES;
}
return (toInterfaceOrientation == UIInterfaceOrientationPortrait);
}
-(BOOL) shouldAutorotate{
return YES;
}
-(NSUInteger) supportedInterfaceOrientations{
if ([self.visibleViewController isKindOfClass:[MWPhotoBrowser class]] || [self.visibleViewController isKindOfClass:[ZoomPictureViewController class]]) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskPortrait;
}
I am using a class inherited from UINavigationController present as a modal view, in the navigation bar I have a button 'Done' which will dismiss the modal view when user tap on it. Everything behave normal except the dealloc() in ImagePickerController, GroupPickerController (which is initialized as root view) not get called when I dismiss the modal view. This cause the leak of the memory.
Here is the code use it:
ImagePickerController *picker = [[ImagePickerController alloc] initWithRootViewController:nil];
// don't show animation since this may cause the screen flash with white background.
[self presentModalViewController:picker animated:NO];
picker.navigationBar.barStyle = UIBarStyleBlack;
[picker release];
Here is what's inside ImagePickerController, which is a UINavigationController:
- (id)initWithRootViewController:(UIViewController *)root {
GroupPickerController *controller = [[GroupPickerController alloc] initWithNibName:nil bundle:nil];
self = [super initWithRootViewController:controller];
[controller release];
if (self) {
self.modalPresentationStyle = UIModalPresentationPageSheet;
}
return self;
}
- (void)dealloc {
[super dealloc];
}
-(void) dismiss
{
[self.navigationController setViewControllers:nil];
[self dismissModalViewControllerAnimated:YES];
}
Here is the code in GroupPickerController, it response to a button in the navigation bar, to dismiss the modal view:
...
#pragma mark - button actions
- (void)done {
[self.parent dismiss];
}
I tried to manually remove the views from NavigationController, seemed not no effect at all...
[self.navigationController setViewControllers:nil];
Thanks for the help!
UPDATED:
Please disregard this question, apparently it's a mistake. :(
Finally get the problem solved... not change any of the code, but a rebuild the project. :(
First of all, you should not be subclassing UINavigationController:
This class is not intended for subclassing.
What does this line do?
controller.parent = self;
If the controller retains the parent-property, you have a retain cycle which would cause the issue you are describing. Remember that all view controllers in the UINavigationController stack can access the navigation controller with the -navigationController property.
There's is a difference between a UIViewController begin dismissed and released.
When you dismiss it, it can be released at any moment, but not necessarily immediately.
Are you sure you have a memory leak ? Maybe the picker is released a few seconds after the dimiss.
If you really have a memory leak, that means there is another place where you picker is retained.