How to dispose of old controller in a transition between two views? - iphone

I'm trying to create a transition between two views by using their controllers. Problem is, how to dispose of the old controller once the transition animation is finished? I can't dispose of it before the animation is finished as that leads to a segmentation fault - apparently the animations involved do not increment the retain count on the views they work with.
Only thing I can think of is to provide a method to be called once the animation is finished to release the controller argument. I am hoping someone will have a more elegant solution.
Here is my sample code for a method in my root view controller:
-(void) switchToController:(UIViewController *) controller {
[controller viewWillAppear:YES];
if (self.currentScreenController != nil) {
[self.currentScreenController viewWillDisappear:YES];
[[self.currentScreenController view] removeFromSuperview];
}
[self.view addSubview:[controller view]];
if (self.currentScreenController != nil) {
[self.currentScreenController viewDidDisappear:YES];
}
self.currentScreenController = controller;
[controller viewDidAppear:YES];
//[controller release]; // <- releasing now will cause errors as subsequent animations reference this controller's view
...
// animations start now
[Edit]
Well, I can't seem to be allowed to release the controller even after the animation is finished for some unexplained reason. I schedule an NSTimer call a few seconds after
the animation completes, I check that the retain count is 1 before releasing, and then
when I release it I get a crash. Here is the method that is called by the timer to release
already unused controllers:
- (void) releaseOldController {
#synchronized(arrayOfOldControllers) {
if (2 < [arrayOfOldControllers count]) {
NSObject * object = [arrayOfOldControllers objectAtIndex:0];
[arrayOfOldControllers removeObjectAtIndex:0];
NSLog(#"releasing object (%#) with retain count(%d)", object, [object retainCount]);
[object release];
}
}
}
Perhaps my strategy is wrong. What I am trying to accomplish is to simulate book page
turning. The book has more than 30 pages, I want to dispose of the old page as soon as
the transition to the new page is finished to release memory. So when the user turns to
a new page, a new controller with a new view is created and added to the root controller
with a fade animation transition of about 1/2 sec. As soon as the animation is finished
I want to release the previous page view as only the new page view is visible. This way
the root controller view has at most 2 subviews at any one time, and usually only 1 -- the current page viewed, after the transition is complete. This should not be so hard to do, but I don't understand why I am getting an error when releasing a controller when it's no longer in use.
[Update] This question is wrong -- the problem is somewhere else and has nothing to do with controller or view. The code above is correct.

Providing an animation finished callback method will certainly work, but it's a little annoying.
A nicer technique available to you: if you're targetting iOS4 and onwards only, you can use animation blocks -- please see iPhone UIView Animation Best Practice -- in particular, the completion: bit allows you to specify what happens at the end of the animation, without having to define a new callback method in the usual way.

If you are trying to simulate the page turning I would load all my controllers in to NSMutableArray and use it as my page guide. You can release all the UIViewControllers after adding them to the array, and then you will be able still have access to them whenever you want to go back.

Related

Releasing a view controller after a CATransition: Am I doing this right?

My application is loading a first view (used to login into a Web service). When the login is successful, it performs a CATransition (basic kCATransitionFromRight) to show a second view and hides the first view. I've set the delegate of the transition to self so I can use -(void)animationDidStop:(CATransition *)theAnimation finished:(BOOL)flag.
When that method is called (right after the transition is over) I want to release the first view since I won't need it anymore. However, when I call [firstView release] (in animationDidStop:) the retain count doesn't seem to change. I used [loginView retainCount] to check this and since I know it's not always reliable I was wondering: am I doing this right?
Thank you.
taken from the book "Cocoa Touch for iPhone OS 3" is a similar approach.
They set up an animation remove the old subview, add the new one and then commit the animation.
Jilouc in his comment is right, forget to check "retaincount"...
if you want to be sure that your object view firstView just add a
NSLog(#"i'm removing myFirstView");
in its
-(void)dealloc{
}
method...
if you get that NSLog in debugger console window then be sure you had it removed/released in the right way...
btw... the right way could be something like this:
in animationDidStop:
if (firstView!=nil){
[firstView.view removeFromSuperview];
[firstView release];
firstView=nil;
}

iOS Development: Why are the retain counts for my view controller so strange?

I'm diving into iOS development and I'm building a navigation based app that wasn't fully releasing one of the views that was being pushed onto the nav stack. This is problematic because the view controller is never being deallocated, so the memory it uses just builds up every time the view controller is pushed on to the stack. So after investigating the issue, I found the retain counts for the view controller were really strange. The view controller in question is pushed on to the stack once a countdown timer reaches zero.
Here's the code that creates the view controller in the timer callback, displays its retain count, and pushes it onto the nav stack...
-(void)updateCountDownTimer //Defined in MyViewController_A class
{
[self setTimeRemaining:([self timeRemaining] - 1)];
[[self countDownLabel] setAlpha:1];
[[self countDownLabel] setText:[NSString stringWithFormat:#"%d", [self timeRemaining]]];
//Fade out the current time
[UIView beginAnimations:#"FadeAnimation" context:nil];
[UIView setAnimationDuration:1];
[[self countDownLabel] setAlpha:0];
[UIView commitAnimations];
if ([self timeRemaining] == 0)
{
MyViewController_B *myvc_b = [[MyViewController_B alloc] initWithNibName:#"MyView_B_iPhone" bundle:nil];
[[self navigationController] pushViewController:myvc_b animated:YES];
NSLog(#"updateCountDownTimer: %d", [myvc_b retainCount]);
[myvc_b release];
[[self countDownTimer] invalidate];
[[self countDownLabel] setHidden:YES];
}
}
and here's the code that pops the view controller off the nav stack once the pause button is pressed...
- (void)pauseButtonPressed:(id)sender
{
//Stop the timer
[puzzleTimer invalidate];
NSLog(#"pauseButtonPressed before pop: %d", [self retainCount]);
//return to the previous view
[[self navigationController] popViewControllerAnimated:YES];
NSLog(#"pauseButtonPressed after pop: %d", [self retainCount]);
}
and here's the console output that shows really strange retain counts throughout the process...
2010-12-02 17:50:38.062 MyApp[821:307] updateCountDownTimer: 5
2010-12-02 17:50:40.453 MyApp[821:307] pauseButtonPressed before pop: 2
2010-12-02 17:50:40.462 MyApp[821:307] pauseButtonPressed after pop: 4
I'm new to iOS development, but the code seems pretty straightforward to me, so I don't know what I'm missing.
Thanks so much in advance for your wisdom!
UPDATE: It looks like the Leaks instrument is reporting a leak on the line of code that pushes the previous view controller onto the stack (that is, the view controller responsible for pushing the view controller in question). The code is once again very straightforward, so I don't know why it's reporting a leak...
MyViewController_A *myvc_a = [[MyViewController_A alloc] initWithNibName:#"MyView_A_iPhone" bundle:nil];
[[self navigationController] pushViewController:myvc_a animated:YES]; //<--Leak being reported here
[myvc_a release];
*UPDATE:*Found the problem, it was just as everyone was saying and the same problem as was shown in the link posted in the comments below, I had live objects still referencing my view controller, which prevented it from deallocating. In my case, I had two timers that were targeting my view controller and those timers weren't being invalidated before I popped the view off the stack, which meant there were two live objects still referencing the view controller. Here's a snippet I found in the Apple docs which uncovered the problem...
Perhaps more importantly, a timer also
maintains a strong reference to its
target. This means that as long as a
timer remains valid (and you otherwise
properly abide by memory management
rules), its target will not be
deallocated.
Anyhow, thanks again to everyone who helped!
You're not missing anything -- instances of UINavigationController just do strange, strange things to the retain count internally.
You should only worry about the retainCount if you see a specific memory leak that you're trying to patch up. In this case, of course, you DO have a problem... the retainCount just doesn't help, since it's so bizarre.
You might check if dealloc is getting called on MyViewController when you do the pop. Also, before you test, comment out the lines that check the retainCount. Calling retainCount will sometimes add to the retainCount.
To really nail down what's going on, in Xcode, go to the Run menu, and select Run With Performance Tool > Leaks. Push and pop that view controller, and you should see it pop up as a leak. You'll be able to see all of the retain and release calls on the object.
If you're really stuck, Apple's guide to Finding Leaks has a few more clever solutions. Good luck!
There is nothing wrong with your code as far as memory management is concerned.
You should not rely on retain counts to check if your objects are being released properly as the system will also be retaining what it needs and releasing when appropriate. For instance when you add your view controller to the stack it gets retained by the nav controller along with its subviews and when it's popped out it gets send a release message which propagates all of its subviews.
The general rule is if you alloc, retain or copy an object, it is your responsibility to release it. Everything else is dealt with by the system and will be flushed with the auto release pool.
Never, ever, ever look at the retain counts of your objects. They should not be used programmatically, and are misleading when you are trying to debug your code.
Here's why: You know that in your code, you are making calls to retain and release to manage your retain count. But you may also be making calls to -autorelease, which causes your retain count to be decremented at a later date over which you have little or no control. Worse, any time you pass a reference to your object to an object that you do not control the implementation of (which is likely to happen to most of the objects you create), the receiving object may make its own adjustments to the retain count - and that object may pass your object to yet other objects, which also adjust the retain count.
The point is, you should not ever look at the retain count of your objects for any reason at any time. They will only serve to confuse you. Your job is to manage your own claims to an object correctly, and trust that the code written by others is doing so as well.

crash when switching between tableviews using tab controller and MBProgressHUD

I have two tableviews. One loads when I select one tab, and the other loads when I select the other tab.
I use MBProgressHUD to allow for quick switching between the tabs as I pull the tableview datasource from the web using Objective Resource and that can take a little bit. So I throw up a HUD progress indicator waiting for the data to load.
This in turn has allowed me to quickly switch between tabs. But.... If I do it quick enough an exception occurs
EXC BAD ACCESS
with NSZombiesEnabled I get this:
2010-08-02 22:44:43.116 Film Fest[962:8703] *** -[MBProgressHUD release]: message sent to deallocated instance 0x85490b0
In both my tableviews I use two different custom tableviewcells.
What should be my next step to debug this?
... so I have moved the code to create the HUD from my viewWillAppear: method to viewDidLoad: and the crash went away.
// The hud will dispable all input on the view (use the higest view possible in the view hierarchy)
HUD = [[MBProgressHUD alloc] initWithView:self.view];
//HUD.graceTime = 0.5;
//HUD.minShowTime = 5.0;
// Add HUD to screen
[self.view addSubview:HUD];
// Register for HUD callbacks so we can remove it from the window at the right time
HUD.delegate = self;
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadFilms) onTarget:self withObject:nil animated:YES];
however this doesn't really fix my issue as viewDidLoad only occurs once in a long while especially with the new multitasking. I need this HUD to fire the selector everytime the tableview appears.
Why is it not correct for me to have it occur in the viewWillAppear... is it because that can be loaded so much and I just kept on allocating the object? perhasp I should allocate in viewDidload and fire the
// Show the HUD while the provided method executes in a new thread
[HUD showWhileExecuting:#selector(loadFilms) onTarget:self withObject:nil animated:YES];
only in my viewWillAppear:?
Thoughts?
Find all the places where you init/retain/release/autorelease an instance of MBProgressHUD.
The NSZombie tells that this project receives a release message after it is being already deallocated (retain count is zero).
It will help if you will post some code - otherwise it is too difficult to help you...
EDIT:
As you have mentioned in your edit, it would be much better if you'd initiate the HUD only once per view controller (e.g. in viewDidLoad) and call to showWhileExecuting each time you need it (e.g. in viewWillAppear).
But it still doesn't explain the crash.
You you execute the entire code you have posted (as is) for a few times from the same instance of a view controller then you should have memory leaks. That is because I don't see where you release the old instance of HUD.
In addition, you are better set the delegate of the HUD to be nil right before releasing it and before calling the [super dealloc]; in the dealloc method of the view controller.
The last one might cause the EXC BAD ACCESS error.
One more thing, if the entire code you have posted is executed more than once then you should also have few HUD views under the self.view.
I don't know what HUD does in the background - maybe this causes the error...

Getting "Using two-stage rotation animation" warning with UIImagePickerController

I wrote simple code to test UIImagePickerController:
#implementation ProfileEditViewController
- (void)viewDidLoad {
[super viewDidLoad];
photoTaker_ = [[UIImagePickerController alloc] init];
photoTaker_.delegate = self;
photoTaker_.sourceType = UIImagePickerControllerSourceTypeCamera;
photoTaker_.showsCameraControls = NO;
}
- (void)viewDidAppear: (BOOL)animated {
[self presentModalViewController: photoTaker_ animated: NO];
}
#end
And I'm getting strange warnings like the following:
2010-05-20 17:53:13.838 TestProj[2814:307] Using two-stage rotation animation. To use the smoother single-stage animation, this application must remove two-stage method implementations.
2010-05-20 17:53:13.849 TestProj[2814:307] Using two-stage rotation animation is not supported when rotating more than one view controller or view controllers not the window delegate
Got any idea what this is about? Thanks a lot in advance!
This message will appear if you are presenting the UIImagePickerController within another UIViewController. Because it isn't pushed like a UINavigationController stack, there is confusion at the UIWindow level. I don't know if the warning is a problem, but to eliminate the warning you can do the following:
// self = a UIViewController
//
- (void) showCamera
{
cameraView = [[UIImagePickerController alloc] init];
[[[UIApplication sharedApplication] keyWindow] setRootViewController:cameraView];
[self presentModalViewController:cameraView animated:NO];
}
- (void) removeCamera
{
[[[UIApplication sharedApplication] keyWindow] setRootViewController:self];
[self dismissModalViewControllerAnimated:NO];
[cameraView release];
}
Perhaps you are adding the root UIViewController's view as a subview of the window instead of assigning the view controller to the window's rootController property?
IT ALL FALLS BACK ON THE UI
This warning can be implemented for several different objects: Pickers, keyboard, etc.
I have found that it is related to the UI taking two steps to complete a transition or other animation. Or for any instance where the UI is trying to finish one thing and is being asked to execute another before it has finished. (therefore it covers a wide range of possible triggers)
I have seen the warning appear on 4.0 & 4.2. In my case I was working with rotating the device and catching whether the keyboard was still up-(i.e. text field was still first responder). If so, the keyboard needed to stay up for between the views, but this presented other complications with other views.
Therefore, I implemented a BOOL tracker to keep track if keybaordIsPresent, and if so I was {textfield resignFirstResponder]; when detecting the orientation change and the reset the textfield to becomeFristResponder after the transition that was wrapped in an Animation Block. My BOOL tracker worked better, I still use the NSNotifications for the Keyboard, but there were overlaps of notifications during rotations because the keyboard was being dismissed without requesting such. The BOOL is set to NO on Load, and when the [textfield resignFirstResponder]; is implemented. *not when "-(void)keyboardWillhide is trigger by the NSNotifications, which gives me both working triggers that never conflict. The BOOL is set to YES, only when the user touches textfield which automatically triggers becomeFirstResponder.
I removed the warning by taking the [textfild resignFirstResponder]; out of the
-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}
}
and placing it back at the top of the code for the:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
if (keyboardIsPresent == YES) {
[self.entryTextField resignFirstResponder];
}
//Determine Which Orientation is Present:
if((fromInterfaceOrientation == UIInterfaceOrientationPortrait) || (fromInterfaceOrientation == UIInterfaceOrientationPortraitUpsideDown)){
//LANDSCAPE VIEW:
[self configureLandscapeView];
}else {
//PORTRAIT VIEW:
[self configurePortraitView];
}
}
**Even though I had no code inside the -(void)willAnimatFirstHalfOfRotationToInterface:, the warning was still popping up. I think the warning was still popping up because the compiler still has to attempt the method while it is trying to execute the first animation and therefore gets the double animation call or overlap of resources. It doesn't know that there is no executable code with the method until after it runs through it. And by that time it already set aside resource in preparation for handling possible actions within the method.
**To ellimiate the warning I had to remove or nil out the code for the willAnimateFirstHalfOfRotation, so that the compiler does not have to even check to see if there is a possible 2nd animation or action that may need to be executed at the same time.
/*-(void)willAnimateFirstHalfOfRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
//if (keyboardIsPresent == YES) {[self.entryTextField resignFirstResponder];}}*/
After the transition is completed, within the original animation block I check to see if the "keyboardIsPresent" was YES prior to the rotation, and if so, I resign the First Responder once again. I use setAnimationDuration:0.3which comes out pretty clean and not jumpy.
Well, you are presenting UIImagePickerController modally inside viewDidAppear of ProfileEditViewController.
Think about this. That means when ProfileEditViewController view appears, the UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and UIImagePickerController appears, say later you dismiss UIImagePickerController and it goes back to ProfileEditViewController, then viewDidAppear is called again and.... you get the idea.
That warning is rather cryptic though, not sure if that is what it's trying to tell you. I would suggest making a button somewhere on the ProfileEditViewController that calls presentModalViewController and also make sure you have a way to dismiss the UIImagePickerController (I've never used it not sure if it has one automatically).
You may be trying to present two modal view controllers at the same time, and they are fighting for animation resources.
1) There is rarely any UI reason to do this. You could instead just go directly to the second view controller (the image picker); and, after dismissing it, then present the first view or view controller.
2) If you do want two stacked view controllers or a view controller on top of a view, then set a timer in viewDidAppear to present the second view controller after the first one has finished it's animation. (You could display a dummy png image of a blank picker in the first one to prevent too much display flashing until the second view controller goes live.)
EDIT - Added a random code example:
I might try substituting this as an experiment:
- (void)foo {
[self presentModalViewController: photoTaker_ animated: NO];
}
- (void)viewDidAppear: (BOOL)animated {
NSTimer *bar = [ NSTimer scheduledTimerWithTimeInterval: (2.0f)
target: self
selector: #selector(foo)
userInfo: nil
repeats:NO ];
}
A shorter time delay may work as well.
I just had the same problem. In my case was a silly mistake that I'm putting here just in case anyone else falls into that same issue.
In my tabbed app I remove one of the original ViewControllers and added a new one with Storyboard to create a "Settings" section.
This new VC had to be a table view VC and even I designed, compiled and run it without a problem, when I changed the orientation of the app I kept getting this “Using two-stage rotation animation” error.
My problem was that I forgot to change in the original .h file interface "UIViewController" for "UITableViewController".
Once this was done I changed on the Storyboard identity badge the class from the general value to my SettingsViewController and that was the end of it.
I hope it can help someone else. It took me a while to get to bottom of this.
Cheers,
I think the warning here is about Core Animation performance. As a test, I loaded the image picker without any action sheet or other animations going on and the warnings are still there. I think these are warnings coming from the image picker class itself and not from any misuse of the API.

How to recreate UIViewController stack?

I'm writing a 'load' feature for my current iPhone app.
Practically what I want to do is get the user back where s/he left off. To do so I want to recreate all the UIViewControllers appeared before.
My problem is the UIViewControllers won't appear if I simply call [[self navigationController] pushViewController:newController animated:YES];.
The same code works fine when invoked as an event handler, like after touching a button. But if I do same without an even (for ex. in viewDidLoad) the new view controller's loadView method won't get called.
My actual code (with some pseudo elements):
- (void)viewDidLoad {
[super viewDidLoad];
if (loading)
[self onSomeEvent];
}
- (void)onSomeEvent {
UIViewController *newController = //init....;
[[self navigationController] pushViewController:newController animated:YES];
[newController release];
}
I guess viewDidLoad is not the right place to do such a call, but then what is?
I'm not sure that spreading your "load" feature all accross your controllers is the best way to achieve it.
I would rather put it in the init of your application, in the applicationDidFinishLauching part. Then you have a centralized place where you restore the previous state (easier to handle IMHO).
Let's suppose you want to implement some more advanced restore feature like:
displaying a splash UIView with an activity indicator to indicate the user that you're restoring previous state
restore the stack of your controllers in the navigation controller
remove the splash
it's easier to have all this code in the applicationDidFinishLauching : it's all managed at one point.
Morever, when restoring the old state to your navigation controller, you can avoid using the transitions and use animated:NO instead of YES when pushing your controllers. It will be easier to handle from your perspective and the restore time may be decreased if you remove time needed to achieve transitions.