When I pop a UIViewController instance off of my UINavigationController, I find that its properties remain (NSTimers keep timing, AVAudioPlayers keep playing, etc.). I'm wondering what's wrong with my approach?
I push the UIViewController instance onto the UINavigationController this way:
- (IBAction)buttonPressed:(id)sender {
UINavigationController *nc=[self navigationController];
NewViewController *nvc=[[NewViewController alloc] init];
[nvc setNameToUse:[self nameToUse]];
[nc pushViewController:nvc2 animated:YES];
}
The NewViewController has a sub-viewcontroller, that is added via NiewViewController's viewDidLoad method:
self.mySubViewController=[[SubViewViewController alloc] initWithName:self.nameToUse];
self.mySubViewController.view.frame=CGRectMake(0,
0,
self.mySubViewController.view.frame.size.width,
self.mySubViewController.view.frame.size.height);
[self.view addSubview:self.mySubViewController.view];
It's the properties of SubViewController that don't go away when NewViewController is popped. One of these in particular is a timer, declared as follows:
#property (nonatomic) NSTimer *aTimer;
Any advice on this would be terrific. I'm hoping that by solving this issue, the crashes that have been happening once in a while (after the app has been running for 45 or so minutes) will stop! Or at least I'll have a better idea of what's causing them... :) Thanks for reading.
NSTimer retains it's target, if you are passing self (your SubViewViewController) then you'll be creating a retain cycle between the view controller and the timer.
Simply adding SubViewController's view to ViewController's will not correctly pass events.
You need to correctly implement UIViewController child containment, as such:
So your second code block should be something like:
[self addChildViewController:self.mySubViewController];
[self.view addSubview:self.mySubViewController.view];
[self.mySubViewController didMoveToParentViewController:self];
Related
New to iOS development & Objective-C and am a little unsure how to go about solving this issue.
I'm working on a project that works like a video player. There are two ViewControllers:
MenuViewController (has a list of titles that act as buttons)
PlayerViewController (view where video plays)
In the MenuViewController, I want to be able to click onto a button (video title) :
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
[self presentModalViewController:vc animated:YES];
}
and have an action that's currently defined in the PlayerViewController automatically execute as soon as its loaded.
Is there a way to have a button in one ViewController call the action in another ViewController as soon as that second ViewController has loaded?
Thanks!
The right way to solve this problem will be set up a delegate pattern between the two view-controllers.
Example:
#protocol PlayerViewControllerDelegate<NSObject>
{
-(void)playerViewControllerViewDidLoad:(PlayerViewController *)playerVC;
}
Then, in PlayerViewController.h create a weak delegate variable:
#property (nonatomic, weak) id<PlayerViewControllerDelegate> delegate;
In PlayerViewController.m, notify the delegate on viewDidLoad:
-(void)viewDidLoad {
[super viewDidLoad];
[self.delegate playerViewControllerViewDidLoad:self]
}
In MenuViewController:
- (IBAction)videoOne:(id)sender
{
PlayerViewController * vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
vc.delegate = self
[self presentViewController:vc animated:YES];
}
Finally, implement the protocol in MenuViewController, and you're ready to go:
#interface MenuViewController : UIViewController<PlayerViewControllerDelegate>
#end
#implementation MenuViewController
-(void)playerViewControllerViewDidLoad:(PlayerViewController*)playerVC
{
[playerVC playVideo];
}
#end
It is usually not good practice to have one controller controlling the behavior of another. Instead, you can have the MenuViewController create the PlayerViewController and set variables so the new player knows how to behave based on its internal state.
There are several UIViewController methods that you can override in order to perform actions during the controller's lifecycle. Based on your question it seems like you want the viewDidLoad method.
I am not sure how you are passing videos between controllers, but if you were using URLs (to Youtube videos for example) then you could do something like the following:
// MenuViewController.m
- (IBAction)videoOne:(id)sender {
PlayerViewController* vc = [[PlayerViewController alloc] initWithNibName:#"PlayerViewController" bundle:nil];
// Pass any necessary data to the controller before displaying it
vc.videoURL = [self getURLForSender:sender];
[self presentViewController:vc animated:YES completion:nil];
}
// PlayerViewController.m
- (void)viewDidAppear {
// View did appear will only be called after the controller has displayed
// its primary view as well as any views defined in your storyboard or
// xib. You can safely assume that your views are visible at this point.
[super viewDidAppear];
if (self.videoURL) {
[self playVideo];
}
}
You would need to define the property videoURL on PlayerViewController and expose it publicly. If you are using local files (such as from the user's photo storage) you could pass the video to the new view controller before presenting it.
There are other UIViewController lifecycle methods that you can override. They are explained in more depth in this post as well as Apple's UIViewController Documentation.
Edit: changed presentModalViewController:animated: to presentViewController:animated:completion: and changed viewDidLoad to viewDidAppear as it seems more appropriate for the question.
presentModalViewController:animated: is deprecated. Use presentViewController:animated:completion: instead.
In the completion Block, you can call a method on the presented view controller:
[self presentViewController:otherVC
animated:YES
completion:^{ [otherVC startPlaying]; }];
The completion is run after the presented controller's viewDidAppear.
I am little confuse because my dealloc is not called in ARC. I have using storyboard in my application.
Case 1: Mydealloc called when i use all IBOutlet from storyboard
Case 2: My dealloc is not called when i try to use alloc and init methods in UIViewController. such as below.
UIViewController *vc = [[UIViewController alloc] initWithNibName:#"ProfileDetailView" bundle:nil];
__weak ProfileDetailView *detailview = (ProfileDetailView *)vc.view;
detailview.backgroundColor = [UIColor clearColor];
vc = nil;
....Set value in object.....
[self.view addSubview:detailview];;
detailview = nil;
Can you explain why dealloc is not called? and How can i able to achieve to call dealloc?
Thanks
The concept of ARC is that an object's retain count should theoretically be 1 in order for it to be deallocated. When you execute:
[self.view addSubview:detailview];;
Your self.view increments detailview's retain count by 1 when it adds it to view.subviews. Logically, when self.view removes detailview, the retain count is decremented by 1.
Again, this is a theoretical perspective. The reality is usually:
INSANE.
No one really knows how the mysterious Objective-C runtime works! (just kidding the whole source code is available online.)
Thanks for your reply. I am able to called my dealloc function when view controller pop. For achieving that, we need to remove my added subviews from my view when user tapped on back button.
I have run into a strange issue where pushing a custom UIViewController is resulting in a long delay before my controller appears. If I change my code to present the controller instead of pushing it there is no delay.
I am not doing anything fancy when I create and push my VC:
MyCustomViewController *myView = [[MyCustomViewController alloc] init];
NSLog(#"myView init done");
[[self navigationController] pushViewController:tagsView animated:YES];
NSLog(#"myView PUSHED");
// switching this to [self presentViewController:myView animated:YES completion:nil] gets rid of the delay
I have added a number of logs to try to identify exactly when the delay is taking place. The NSLogs from above print quickly as expected:
myView init done
myView PUSHED
Then there is a long delay, probably 20-30 seconds, before all of these lines print:
ViewController loadView begins
ViewController loadView ends
ViewController viewDidLoad begins
ViewController viewDidLoad ends
ViewController viewWillAppear begins
ViewController viewWillAppear ends
willShowViewController
ViewController viewDidAppear here
didShowViewController
All of the ViewController logs are obviously from my UIViewController and *ShowViewController ones are from my UINavigationController delegate. All of the methods of my viewController seem to be running quickly. As I mentioned, if I instead present this same controller at this point in my code there is no delay. Is there a state my UINavigationController can be in to cause this? Any suggestions would be much appreciated.
In an application where several UIViewControllers work together,
firstViewController added to root. Till here its fine now I want to go to secondViewController I dont want to use UINavigationController or UITabBarController. I have already read the View Controller Programming Guide but it does not specify without using UINavigationController, UITabBarController and story board.
and when user want to move from secondViewController to firstViewController how secondViewController will be destroyed?
Apple Doc also does not specify how UIViewController is release or destroyed? It only tell the life cycle inside UIViewController.
If you are concerned about how UIViewController is release or destroyed then here is a scenario for you:-
Here is a button tap method in FirstViewController that presents SecondViewController (using pushViewController,presentModalViewController etc)
In FirstViewController.m file
- (IBAction)btnTapped {
SecondViewController * secondView = [[SecondViewController alloc]initWithNibName:#"SecondViewController" bundle:nil];
NSLog(#"Before Present Retain Count:%d",[secondView retainCount]);
[self presentModalViewController:secondView animated:YES];
NSLog(#"After Present Retain Count:%d",[secondView retainCount]);
[secondView release]; //not releasing here is memory leak(Use build and analyze)
}
Now In SecondViewController.m file
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"View Load Retain Count %d",[self retainCount]);
}
- (void)dealloc {
[super dealloc];
NSLog(#"View Dealloc Retain Count %d",[self retainCount]);
}
After Running the code:
Before Push Retain Count:1
View Load Retain Count 3
After Push Retain Count:4
View Dealloc Retain Count 1
If you are allocating and initializing a ViewController, You are the owner of its lifecycle and you have to release it after push or modalPresent.
In the above output at the time of alloc init retain count of SecondViewController is One,,,,surprisingly but logically its retain count remains One even after it has been deallocated (see dealloc method), So require a release in FirstViewController to completely destroy it.
Other way to present a new view controller is doing it like a modal view controller(notice that self is firstViewController):
[self presentModalViewController:secondViewController animated:YES];
then, when you wan to come back to the firstViewController and destroy the secondViewController, you have to dismiss the view controller(from the secondViewController):
[self dismissModalViewControllerAnimated:YES];
Hope that helps.
You can use UINavigationController to move to secondViewController and come back by setting the UINavigationController property 'navigationBarHidden' to YES. Which will hide the navigation bar. Releasing and destroying of view controllers will takes care by this.
Then, you can take other strategy, is not the best to build your view controller hierarchy, but it also could work. You can overlay the secondViewContrller's view over the firstViewController's and make the secondViewController become the child from the firstViewController:
//...
[self addChildViewController:secondViewController];
[self.view addSubview:secondViewContrller.view];
//...
And when you want to remove the view controller, you have to remove the view and ask for the view controller to remove from his parent:
//...
[self.view removeFromSuperview];
[self removeFromParentViewController];
//...
But then you will have to control the view hierarchy by your own(putting and removing views and view controllers by your own).
I am working with Automatic Reference Counting.
I have a custom UIViewController subclass and whenever I call -presentViewController: animated:completion: or remove its view from the superview I would like to NSLog something like "I am dealloced" so I know that the view controller has successfully been removed. I have implemented the -dealloc method in my view controller. However I started a test project where I just had two UIViewController instances (no retain cycles) and -dealloc is not called either when I push the second UIViewController modally or when I remove the superview or when I remove it from the parent view controller. Am I missing something ? In my original project (not the test case) Instruments shows me that those controllers leave a memory footprint that I can't get rid off.
If you want to switch view controllers, and have the one you're switching away from be deallocated, then just switch the root view controller of the window. So, if you're in VC1 and want to go to VC2, then do this in VC1:
VC2 *vc2 = [[VC2 alloc] init]; // or however else is appropriate to get an instance of this class
self.view.window.rootViewController = vc2;
If you haven't created any property to point to vc1, then it will be deallocated after making this switch.
If you want to use a modal presentation or a modal segue (to get the animation when you switch controllers), you can still get the initial controller to be deallocated by switching the root view controller after the presentation from the viewDidAppear method of vc2:
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
self.view.window.rootViewController = self;
}
To get a print when the View Controller is deallocated you can implement the dealloc method as
- (void) dealloc {
NSLog(#"The instance of MyViewController was deallocated");
}
Then to get a print when the View Controller left the view you can implement
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
NSLog(#"The instance of MyViewController left the main view")
}
If you use -presentViewController:animated:completion: you are retaining the parentViewController every time you call this method. ModalViewControllers are simply pushed on top of the other ViewController.
ModalViewControllers are only designed for some kind of information / User Input and stuff like that. If you want to dealloc the ParentViewController you have to deal with your own implementation.
dealloc method isn't called when the class is retained (or something in this class is retained) and not reeleased. It is justly for projects with both ARC and without it. So check your code twice.