I have a parent UIView and an UITextView as one of the subviews.
And I created a button to dismiss the parent UIView like this:
-(void)cancelButtonPressed:(UIButton *)sender
{
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.frame = CGRectZero;
} completion:^(BOOL finished) {
if (finished) {
[self removeFromSuperview];
}
}];
}
I can tell that the parent UIView didn't get released because if I typed some text into the UITextView and dismissed it, when I opened the UIView again, instead of a blank UITextView, the SAME text is in it again.
I checked the Leaks tool but I didn't see any leaking. So I'm guessing if I have some kind of retain cycle or what.
UPDATE:I have another object (which is the AppDelegate) who is holding the UIView's instance: _myView as a global variable like this:
_myView = [[MyView alloc] init];
_myView.nameLabel.text = _user.screen_name;
[_window addSubview:_myView];
[UIView animateWithDuration:0.2 delay:0.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
_myView.frame = CGRectZero;
} completion:nil];
But in order to avoid retain cycle, should I create a weak self like this: __weak MyView *weakSelf and in the animation block do this: [weakSelf removeFromSuperview]?
I've also tried calling removeFromSuperview on the view itself, and it doesn't result in the view being released.
If you want to release the view, then go with an approach that uses a delegate. That way, you will be able to call removeFromSuperview on the view, once the animation is complete, and set it to nil. This has worked for me in the past.
So, you can add a method to the view class that you want to animate closed, where you will do the animation. Set your view controller as a delegate to your view, and call some method on the delegate, from the completion block of that animation.
You can create your own protocol for this. If you keep it general enough, and focus only on animation callbacks, you can reuse the protocol in all your view controllers.
Memory management and logic are independent things. A memory leak will never change the behavior of your program. Behavior like displaying something is controlled by what you tell it to display. If it displays the same thing as before, then you must be giving it the same thing to display somehow. Even if you somehow leaked the original thing, if you pass a new thing for it to display, it will display that thing. So look at your logic. Memory management has nothing to do with what you're seeing.
Related
I have a login screen that I allocate in one place, and dismiss in another, and upon dismissal, its dealloc method is never called and the iVar holding the login screen still has a value even after being assigned nil in my dismissal code.
Here is my allocation
-(void)loginUser
{
loginScreen = [[LoginScreen alloc] initWithNibName:#"LoginScreen" bundle:nil];
[self.tabBarController addChildViewController:loginScreen];
[self.tabBarController.view addSubview:loginScreen.view];
[loginScreen didMoveToParentViewController:self.tabBarController];
[self.tabBarController.view bringSubviewToFront:loginScreen.view];
}
Here is my dismissal and deallocation in another method (which fails to deallocate)
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
loginScreen.view.frame = CGRectOffset(frame, -1024, 0);
}
completion:^(BOOL finished) {
// Remove the loginScreen
[loginScreen willMoveToParentViewController:nil];
[loginScreen.view removeFromSuperview];
[loginScreen removeFromParentViewController];
[loginScreen cleanupBeforeDealloc];
loginScreen = nil;
}];
I have some code that listens for keyboard notifications inside LoginScreen, but I added a method below to clean that up, and I tried calling it in my dismissal code above, but that still didn't fix it. grrrr.
-(void)cleanupBeforeDealloc
{
[self deregisterFromKeyboardNotifications];
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
I would suggest getting rid of loginScreen entirely. There is no need for it, as this is a child view controller and is accessible through your childViewControllers array. childViewControllers manages the retain and release for the child view controller, and your loginScreen property is adding an extra retain that could be messing things up.
However, the actual cause of the retain cycle is probably that the view controller has registered and retained an observer with the notification center. That is a common cause of retain cycles. The notification center retains the observer and the observer retains self. You cannot unregister in dealloc to break the cycle because the retain means that dealloc isn't called.
Now, this question have partially been asking alot, but none actually considering how (or when) the messages -viewWillDisappear & -viewDidDisappear are being sent. Almost every example use the following design:
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationCurveEaseOut
animations:^{
yourView.alpha = 0;
}completion:^(BOOL finished){
[yourView removeFromSuperview]; // Called on complete
}];
The problem with this is that these messages will both be sent when de animation ends!
Now, -addSubview can be animated (if put inside the animations-block) which will send the corresponding messages (-viewWillAppear & -viewDidAppear) with correct timedifference. So naturally one would place -removeFromSuperview inside the animations-block. This WILL send the messages correctly, but the view is actually removed instantly making the animation... Well, it won't animate because nothing is left to animate!
Is this intentional from apple and if so, why? How do you do it correctly?
Thanks!
Edit.
Just to clearify what I'm doing:
I got a custom segue, vertically animating a Child-ViewController down from top which works as expected with the following code:
-(void)perform{
UIViewController *srcVC = (UIViewController *) self.sourceViewController;
UIViewController *destVC = (UIViewController *) self.destinationViewController;
destVC.view.transform = CGAffineTransformMakeTranslation(0.0f, -destVC.view.frame.size.height);
[srcVC addChildViewController:destVC];
[UIView animateWithDuration:0.5f
animations:^{
destVC.view.transform = CGAffineTransformMakeTranslation(0.0f, 0.0f);
[srcVC.view addSubview:destVC.view];
}
completion:^(BOOL finished){
[destVC didMoveToParentViewController:srcVC];
}];
}
Here it will happen in the following order (thanks to -addSubview being inside the animations-block):
Add childView (will automatically invoke -willMoveToParentViewController)
-addSubview will invoke -viewWillAppear
When the animation finishes, -addSubview will invoke -viewDidAppear
Manually invoke -didMoveToParentViewController inside the completion-block
Above is the exact expected behavior (just like the built-in transitions behave).
With the following code to do the above segue but backwards (with an unwindSegue):
-(void)perform{
UIViewController *srcVC = (UIViewController *) self.sourceViewController;
srcVC.view.transform = CGAffineTransformMakeTranslation(0.0f, 0.0f);
[srcVC willMoveToParentViewController:nil];
[UIView animateWithDuration:5.5f
animations:^{
srcVC.view.transform = CGAffineTransformMakeTranslation(0.0f, -srcVC.view.frame.size.height);
}
completion:^(BOOL finished){
[srcVC.view removeFromSuperview]; // This can be done inside the animations-block, but will actually remove the view at the same time ´-viewWillDisappear´ is invoked, making no sense!
[srcVC removeFromParentViewController];
}];
}
the flow will be like this:
Manually invoke -willMoveToParentView:nil to notify that it will be removed
When the animation finishes, both -viewWillDisappear & -viewDidDisappear will be invoked simultaneously (wrong!) and -removeFromParentViewController will automatically invoke -didMoveToParentViewController:nil.
And if I now move -removeFromSuperview in to the animations-block, the events will be sent correctly but the view is removed when the animation starts instead of when the animation finishes (this is the part that makes no sense, following how -addSubview behaves).
Your question is about removing view controller, because, viewWillDisappear and viewDidDisappear are method of view controller.
viewWillDisappear: will be called from completion block, not earlier, because this is the place where you said that you want to remove subview from main view.
If you want to remove some property before that point, then in child controller override willMoveToParentViewController: method. This method will be called before animation block.
Here's code example:
//Prepare view for removeing.
[self.childViewController willMoveToParentViewController:nil];
[UIView animateWithDuration:0.5
delay:1.0
options: UIViewAnimationOptionCurveEaseOut
animations:^{
self.childViewController.view.alpha = 0;
}completion:^(BOOL finished){
[self.childViewController.view removeFromSuperview];
[self.childViewController didMoveToParentViewController:self];
}];
So, the flow will be:
First willMoveToParentViewController: with nil parameter will be called
Animation block will start and view will set it's alpha property to 0
When animation finish, completion block will start to execute...
[self.childViewController.view removeFromSuperview]; will be called first
Then viewWillDissapear: in childViewController will be called
Then [self.childViewController didMoveToParentViewController:self];
And at the end viewDidDissapear: in childViewController will execute.
Pre request for this flow is that you embed childViewController with code like this:
[self addChildViewController:self.childViewController];
[self.view addSubview:self.childViewController.view];
[self.childViewController didMoveToParentViewController:self];
I can't find this anywhere. So, if you possess the info about it, please give me a link.
I have one view controller, I made a menu for a simple game. There are some buttons on it.
I have another view controller and there some buttons too.
The question is:
"How I can do an animation of this buttons (hiding off the screen) after I choose one button that triggers a custom segue (without any animation) to another View Controller, which will run it's button animation(coming to the screen from a border of the screen)?"
I made this like this:
1) Theory: I make a IBAction for a menu button, then in this IBAction I call an animation method, which call a performSegueMethod:. After this in new VC in viewWillAppear method call a animation method (that almost equal method from source VC). All this works, but this don't look smooth. The problem with this animation occurs when destination VC replace source VC. There is some split second, when all looks static, and only after this animation starts.
I don't know how to remove this destination VC lag. May be I must load a destination view before a segue? I tried to do this, but may be a made something wrong, or it's just don't help me.
2) Practice:
firstViewController.m:
- (IBAction)newGameSegueButton {
[self menuSlideInDirection:#"CenterToLeft" performingSegueWithIdentifier:#"mode"];
}
-(void)menuSlideInDirection:(NSString *)direction performingSegueWithIdentifier:(NSString *)segueIdentifier
{
[UIView animateWithDuration:0.4 animations:^{
CGPoint newGameButtonCenter;
newGameButtonCenter.x = directionIndex * 160;
newGameButtonCenter.y = self.gameButtonSlide.center.y;
self.gameButtonSlide.center = newGameButtonCenter;
[UIView animateWithDuration:0.3 delay:0.1 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:nil];
[UIView animateWithDuration:0.2 delay:0.2 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//animation
} completion:nil];
[UIView animateWithDuration:0.1 delay:0.3 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
//some animation too
} completion:^(BOOL finished){
if(segueIdentifier){
[self performSegueWithIdentifier:segueIdentifier sender:self];
}
}];
}];
}
Okay, then custom segue code is pretty simple:
-(void)perform
{
UIViewController *src = self.sourceViewController;
UIViewController *dst = self.destinationViewController;
[src.navigationController pushViewController:dst animated:NO];
}
And my secondViewController.m:
-(void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil];
}
menuSlideInDirection:#"RightToCenter" performingSegueWithIdentifier:nil in secondViewController identical to method with same name in firstView.
I'm looking for smooth animation of particular objects of destination view controller right after a segue.
May be a doing all wrong and there a another way to do this? (I only think of adding all view and all controls of destination VC to source VC and remove "destination VC" at all).
Hope, somebody can help me with this.
Try doing all first view controller's animations in perform method.
As for the second view controller's animations, I don't think there is any other 'good' way than doing it in viewWillAppear (although I would prefer viewDidAppear) since the outlets of the destination view controller are not set while performing the segue(they will be nil). In other words, you do not have a way to access your buttons, let alone animate them.
A hackish way would be to call [segue.destinationViewController view] before perform so that the destination view controller's view hierarchy is loaded and the outlets are set. Then perhaps, you may animate buttons in the destination view controller in perform before pushing it onto navigation stack.
For my question, I choose the way of putting two views under the same VC. Animation is smooth and it's look much better than using perform / viewWillAppear method.
I have a project in which I'm switching one view with another:
- (IBAction)onClick:(id)sender
{
ViewControllerSecond * sc=[[ViewControllerSecond alloc]initWithNibName:#"ViewControllerSecond" bundle:nil];
[UIView transitionFromView:self.view toView:sc.view duration:3.0
options:UIViewAnimationOptionTransitionFlipFromLeft completion:^(BOOL finished) {
}];
}
I'm using 3 seconds here to make a point. in this second view I have a method to update the GUI that adds another view from a view controller:
-(void)updateGUI
{
sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil];
sample.view.frame=CGRectOffset(sample.view.frame, 0, 150);
[self.view addSubview:sample.view];
}
Now, here is the problem: when I'm calling this from the viewDidLoad function - it's working just fine.
However, if called from the viewWillAppear function, the view will appear at the top of the screen and only after the animation has ended will jump to it's position.
How can it be fixed?
Whats wrong putting it in viewDidLoad?
From the looks of it, the order is loading, animating then the delegate methods of appearing. Also put your sample=[[ViewControllerSample alloc]initWithNibName:#"ViewControllerSample" bundle:nil]; in the init part of your Second view controller.
Alternatively, you can call updateGUI before your animation. So before [UIView transition...
[sc updateGUI];
Just call updateGUI in viewDidLoad. Don't do much work in viewWillAppear. viewWillAppear will prevent your view's appear if you make it do too much work.
The answer for that was of two parts:
update when the view is loaded, however this will only happen once, so to re-use the controller I have to call it again.
use the isViewLoaded to see if the view is loaded. if it is, then call the update method.
I am struggling with understanding why the first method below works for hiding and removing a subview of a view. In this first method I pass the pointer by reference. In the second method, which is less general, I have a delegate method designed for removing a specific view. I would like to use the first method, because I have several views that I would like to apply this too. I should mention that the first method works without fail as long as it is called within the implementing class. It fails when I call it from the view controller that I wish to dismiss. I get an EXC_BAD_ACCESS on the removeFromSuperview line when it fails in the first method.
-(void)closeView:(UIViewController **)viewController
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionCurveLinear
animations:^
{
[[*viewController view] setAlpha:0.0];
}
completion:^(BOOL finished)
{
[[*viewController view] removeFromSuperview];
[*viewController release], *viewController = nil;
}];
}
-(void)closeButtonClicked
{
[delegate closeView:&self];
}
//
// This method works without fail:
//
-(void)closeView
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionCurveLinear
animations:^
{
// In this context viewController is defined in the class interface
[[viewController view] setAlpha:0.0];
}
completion:^(BOOL finished)
{
[[viewController view] removeFromSuperview];
[viewController release], viewController = nil;
}];
}
-(void)closeButtonClicked
{
[delegate closeView];
}
First of all, it is not according to the style guides, and not a good idea in general, to do a release of the viewController within a method like this. It will get you into trouble quickly. If the caller of this method is responsible for the viewController (it has done the retain), then it should release it as well. This is likely the cause of the first method not working from within the viewcontroller itself.
In the second method you do not pass in the viewController as parameter, which means it needs to be defined in the context.
If you don't release the viewController in this method, then you don't need to set its variable to nil either, and you can simply pass it as normal parameter:
-(void)closeView:(UIViewController *)viewController
{
[UIView transitionWithView:self.view
duration:UINavigationControllerHideShowBarDuration
options:UIViewAnimationOptionTransitionCrossDissolve
animations:^
{
[[viewController view] removeFromSuperview];
}
completion:nil];
}
you would then do this at the call-site:
[self closeView:childViewController];
[childViewController release]; childViewController = nil;
It safe to release the child in this way before the animation is done, because the animations block implicitly retains all objects referenced from the block, including the viewController parameter. Therefore, the child's dealloc is not called until the animations block releases it.
This does not work in your first code example, because you pass a pointer to a variable. That is, the animations block does not know it needs to retain the child.
BTW, I am not sure why you want to set the alpha, in the example above I show that you can also remove the view already in the animations block. See more about that in the UIView Class Reference.
**viewcontroller and &self is not the way to go. In Objective-C, you do [self.view removeFromSuperview] in the subview itself, in the parent viewcontroller you do release or with ARC just replace the subview with another view.