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];
}
}
Related
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 ipad app.
I am trying to open view 2 (kind of push view) full with entire screen. how normally do with push view or UIModalPresentationFullScreen. but my base view which is view 1 is also modal view.
so i was trying to open view 2 when view 1 get dismiss…
- (void) handleNewButton :(int)id
{
[self dismissViewControllerAnimated:YES
completion:^{
NewViewController *View2 = [NewViewController alloc] init];
View2.modalPresentationStyle = UIModalPresentationFullScreen;
View2.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController: View2 animated:YES completion:nil];
}];
}
but my view 2 is not opening. i know i can not do push view. But is there any way to achieve it?.
When you do this dismissViewControllerAnimated the UIViewController (self in this case) is gone, in the sense that he is not on the screen anymore, if it has been released or not, that's another story. The reason for you to not be able to show the View2 (very poor name, it should at least ViewController2) is because you are trying to show it from a UIViewController that is not on the screen anymore.
So, what can you do?
The current self in the context of the handleNewButton method, in theory was presented by another UIViewController, that's from where you want to present your View2.
Probably the quickest way of implementing of what I said, would probably be with a notification described here. Although I would do it with a block, so when the self would be created, I would pass a dismissiCompletionBlock that would be called when that UIViewController was dismissed.
try to allocate NewViewController with nib name if you are not using storyboard,
[self dismissViewControllerAnimated:YES
completion:^{
NewViewController *n=[[NewViewController alloc]initWithNibName:#"NewViewController" bundle:nil];
View2.modalPresentationStyle = UIModalPresentationFullScreen;
View2.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController: View2 animated:YES completion:nil];
}];
or if you are using storyboard get NewViewController using identifier.
Sorry but none of the similar questions provide a working solution.
New single-view project.
Storyboard actions:
VC1 is embedded in nav-controller
a button in VC1 activates a push seque into VC2
a button in VC2 activates a modal segue into VC3
VC3 has a button, and a UIViewController-subclass to handle the button action
Problem statement: How can I have the button of the modal VC3 close both VC3 and VC2 at once, taking me back to VC1?
This did not work - takes me to VC2 only:
- (IBAction)dismissPressed:(id)sender {
UINavigationController *myNavController = [self navigationController];
[self dismissViewControllerAnimated:NO completion:^{
[myNavController popToViewController:[myNavController.viewControllers objectAtIndex:1] animated:NO];
}];
}
Thank you very much!
Ok guys, here is the solution:
If you can limit yourself to ios6+, the right answer is to make the button an unwind segue as described in What are Unwind segues for and how do you use them?
But if you are like me and must comply to ios5, the solution goes likes this:
In the VC who calls the modal segue declare a delegate like this:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"segueModalVC"]) {
ModalVC *vc = [segue destinationViewController];
vc.prevVC = self;
}
}
You will have to #import the header for the destination modal VC.
Now in the modal VC itself add a property to hold the previous VC like this:
#property (weak, nonatomic) UIViewController *prevVC;
And now the button's action for popping both VCs should look like this:
- (IBAction)pressedMainMenu:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
[[self.prevVC navigationController] popViewControllerAnimated:NO];
}
[EDITED to show the final version which works perfectly and can do flip animation from VC3 back to VC1. Took me hours to finalize this but now you can enjoy it for free :) ]
IMHO the fact that the modal VC has no 'native' method to access its preceding VC is a design bug.
Use self.presentingViewController to find out the "previous view controller".
You do not need to pass the previous view controller through the property.
- (IBAction)dismissPressed:(id)sender {
UIViewController *presentingViewController = self.presentingViewController;
[self dismissViewControllerAnimated:NO completion:^{
[self.presentingViewController popViewControllerAnimated:TES];
}];
}
Try to Use popToRootViewControllerAnimated method to go back VC1.
- (IBAction)dismissPressed:(id)sender {
UINavigationController *myNavController = [self navigationController];
[self dismissViewControllerAnimated:NO completion:^{
[myNavController popToRootViewControllerAnimated: NO];
}];
}
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];
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?