Use UINavigationController as modal view, and view are not released after dismiss - ios

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.

Related

Issue while pushing ABNewPersonViewController

I am using AddressBookUI Framework for Adding contact, when I tried to pushing this view controller then cancel and done button not working properly, I don't want to present it
Here is my code
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
[self.navigationController pushViewController:abnpvc animated:YES];
I am also tried add as subview rather then pushing it but when I am adding as subview then it was not added
As per comment i have tried like
UINavigationController *navigation = [[UINavigationController alloc] initWithRootViewController:abnpvc];
[self presentViewController:navigation animated:YES completion:nil];
Can anyone help me out why properly not working ?
You can implement that too considering also the other answers and the deprecations to ABNewPersonViewController in iOS 9.
As per your remarks:
cancel and done button not working properly
They are working if you have included the ABNewPersonViewControllerDelegate on interface like this:
#interface ViewController () <ABNewPersonViewControllerDelegate>
Pushing the viewController on navigation stack like this:
ABNewPersonViewController *controller = [[ABNewPersonViewController alloc] init];
controller.newPersonViewDelegate = self;
[self.navigationController pushViewController:controller animated:YES];
And by conforming to the protocol by implementing this method:
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonView didCompleteWithNewPerson:(nullable ABRecordRef)person {
// Trick to go back to your view by popping it from the navigation stack when done or cancel button is pressed
[self.navigationController popViewControllerAnimated:YES];
}
The tricky line is to pop the newPersonController from the navigation stack when either Done or Cancel button are pressed.
Enjoy it
Why can't you just do it as the docs say?
It is recommended that you present a new-person view controller modally.
Use
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
[self presentViewController:abnpvc animated:YES completion:nil];
That should work fine.
Edit
On second thought, did you set your delegate correctly and do the implementations get called? I suspect they are not implemented or the delegate is not set correctly.
Apple guideline(IMPORTANT) :: New-person view controllers must be used with a navigation controller in order to function properly. It is recommended that you present a new-person view controller modally.
Add Delegate
#interface ViewController () <ABNewPersonViewControllerDelegate>
Pushing the viewController
ABNewPersonViewController *abnpvc = [[ABNewPersonViewController alloc] init];
[abnpvc setNewPersonViewDelegate: self];
UINavigationController *navController = [[UINavigationController alloc] initWithRootViewController:abnpvc];
[self presentModalViewController:navController animated:YES];
And Now Add Delegate Method
#pragma mark ABNewPersonViewControllerDelegate methods
- (void)newPersonViewController:(ABNewPersonViewController *)newPersonViewController didCompleteWithNewPerson:(ABRecordRef)person
{
[self dismissViewControllerAnimated:YES completion:NULL];
}
That will work fine.

App crashes while Modal view controller closes

I want to display the modalViewController from the 2nd screen of the app, and when I dismiss the controller it should navigate to the 1st screen. The below code works fine in iPhone 4, 5 and iPod Touch but NOT in iPAD. The objective is when I dismiss the modalViewController it shouldn't go back to the second screen, but it should display the first screen.
ShareEmail *shareEmail = [[ShareEmail alloc] initWithNibName:[NSString stringWithFormat:#"%#",xibShareEmail] bundle:nil];
shareEmail.fromWer = #"ownPer";
[self presentModalViewController:shareEmail animated:NO];
[shareEmail release];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
In share email class
[self dismissModalViewControllerAnimated:YES];
You need to retain your shareEmail view controller - make a synthesized retained property
YourClass.h:
#property (nonatomic, retain) ShareEmail *shareEmailViewController;
YourClass.m:
#synthesize shareEmailViewController;
Then display your modal view controller by:
ShareEmail *shareEmail = [[ShareEmail alloc] initWithNibName:[NSString stringWithFormat:#"%#",xibShareEmail] bundle:nil];
self.shareEmailViewController = shareEmail;
[self presentModalViewController:shareEmail animated:NO];
[shareEmail release];
The retained property will keep the view controller from being deallocated while in use. Then when you dismiss it later you can do:
[self.shareEmailViewController dismissModalViewControllerAnimated:YES];
self.shareEmailViewController = nil;
which will release the retained property and free the memory after you're done with it.
Something interesting is happening. You are presenting a view controller from self, and then you're having the navigationController perform its backwards navigation. At this point, the self I was talking about earlier disappears.
Remove the popViewController method from your presentViewController method.
Also, you'll need to use the ^completion handler method. That's where you should put your navigation controller pop code.
-(void)present {
ShareEmail *email = [[ShareEmail...
//You'll need to get a weak reference to `self` in `email`
email.modalDelegate = self; //you need to make a property in ShareEmail
[self presentViewController:email animated:YES completion:nil];
[email release];
}
-(void)dismiss {
[self dismissViewControllerAnimated:YES completion:nil];
[self.navigationController popToViewController:[self.navigationController.viewControllers objectAtIndex:1] animated:YES];
}
//in ShareEmail.m
[modalDelegate dismiss];

UINavigationController not executing completionBlocks when presenting ViewController

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?

ios presentModalViewController with two views

I'm confused. I have a navigation controller with a BarItem which opens a first view. After some work is done, I want this view to disappear and I want a second view to open.
root view: navigation controller
first view: activity indicator, where some data is put together
second view: MFMailComposeViewController
In the root view, the BarItem runs these lines to open the first view:
IndicatorViewController *indicator = [[IndicatorViewController alloc] initWithNibName:#"IndicatorViewController" bundle:nil];
indicator.view.backgroundColor = [UIColor clearColor];
self.modalPresentationStyle = UIModalPresentationCurrentContext;
[self presentModalViewController:indicator animated:YES];
The first view (IndicatorViewController) does some work and finally runs
[self dismissModalViewControllerAnimated:YES];
This works fine. But - how do I open the second view?
I tried this:
I open the second view. After closing the second view, my first view pops up again (since it is still there) and get's dismissed at this point. This code is placed in the first view:
- (void) viewDidAppear:(BOOL)animated {
static BOOL firstTime = YES;
if (firstTime) {
//do stuff that takes some time (that's why I show the indicator)
MailViewController *controller = [[MailViewController alloc] init];
if (controller)
[self presentModalViewController:controller animated:YES];
firstTime = NO;
} else {
[self dismissModalViewControllerAnimated:YES];
}
}
Since the first view pops up again, the user can see the indicator one more time, after the second view is closed - and that is not what I want.
What am I missing here? What would be the better way to do this?
I would do something like this. Make a navigationController, and make the first view as the root controller. Then do something like this:
FirstView.m
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController setNavigationBarHidden:YES];
}
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
Then in the second view:
SecondView.m
- (void) nextView { // however you get to your next view, button/action/etc.
UIViewController *screen = [self.storyboard instantiateViewControllerWithIdentifier:#"yourIdentifier"];
[self.navigationController pushViewController:screen animated:YES];
}
And finally in the rootview:
RootView.m
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *navStack = [NSArray arrayWithObject:self];
self.navigationController.viewControllers = navStack;
[self.navigationController setNavigationBarHidden:NO];
}
This will make your RootView the new rootview of the NavigationController.
self.navigationController.viewControllers
is the array with all the ViewControllers that are on the navcontrollers stack. The first object is the rootcontroller. If we replace the whole array with our own array, it knows only one item. You CAN go back by dismissing if that's what you want though. This isn't the prettiest way of doing it, but it's not the worst either.

How to dismiss the keyboard on iPad when using modalPresentationStyle

For my iPad app, I have a view displayed modally as a formsheet when a button is pushed. In order to have the keyboard dismissed after entering text in a textfield i tried as suggested;
the "disablesAutomaticKeyboardDismissal" method.
This does not work, in fact, the method is never called acording to the log.
The keybord will dismiss for iPhone or when i choose to not present modally.
Here is my code:
- (BOOL)disablesAutomaticKeyboardDismissal
{
NSLog(#"method calls");
return NO;
}
- (IBAction)showNewView:(id)sender
{
MyViewController *mvc =
[[MyViewController alloc] init];
// some lines about setting content
//...
UINavigationController *navController = [[UINavigationController alloc]
initWithRootViewController:mvc];
[navController setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentViewController:navController animated:YES completion:nil];
}
-(BOOL)disablesAutomaticKeyboardDismissal
or not, the keyboard is not dismissed unless i remove tis line:
// [navController setModalPresentationStyle:UIModalPresentationFormSheet];
However, then it is not presented the way I want anymore.
Can anyone see what I am doing wrong?
-(BOOL)disablesAutomaticKeyboardDismissal needs to overridden to return NO by the view controller that is presented as a form sheet, not by the presenter; That's your mistake. In your case you could subclass UINavigationController to get the desired behaviour:
#interface AutomaticKeyboardDismissingNavigationController : UINavigationController
#end
#implementation AutomaticKeyboardDismissingNavigationController
- (BOOL)disablesAutomaticKeyboardDismissal
{
return NO;
}
#end
(The class name could probably be a bit shorter and still be comprehensible.)

Resources