Remove from superview not working in animation completion handler - objective-c

I have a UIView with several UILabels added. I am simply moving them all to the center of the screen with an animation, and then attempting to remove them from their superview in the animation completion handler.
for (label in [self.view subviews])
{
if([label isKindOfClass:[UILabel class]])
{
CGRect frame = CGRectMake(self.view.frame.size.width/2, self.view.frame.size.height/2, label.frame.size.width, label.frame.size.height);
[UIView animateWithDuration:2.0
delay:0.0
options: UIViewAnimationOptionCurveEaseInOut
animations:^{
[self->label setFrame:frame];
}
completion:^(BOOL finished){
dispatch_async(dispatch_get_main_queue(),^{
[self->label removeFromSuperview];
});
}
];
}
}
The problem that I am having is that at the end of the animation the UILabels remain. If I put the removeFromSuperView call outside of the animation block then it works, but of course then they are removed before the animation has a chance to complete.

You've got label as the variable in the for-in and self->label in the blocks. Apparently, you weren't operating on the label you thought you were.

Related

Animating with Auto Layout and iOS7 jumps to end of animation (fine in iOS6)

I am using Autolayout and animating by changing the constraints, however, in iOS7 the view simply jumps to the end position - in iOS6 I get a nice animation.
Is should be noted these views are UICollectionViews and I have checked the Storyboard and there are no Layout errors.
All I can think is there is something and am or am not setting on the Storyboard or something that I am doing wrong with the Constant settings in the Storyboard.
primaryMenuYContraints.constant = BUTTOMX;;
leftMenuYContraints.constant = 136.0f;
leftMenuBottomConstraint.constant = 5.0f;
[UIView animateWithDuration:0.7f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^
{
// Move in menus
[self.primaryOptionCollection layoutIfNeeded];
[self.menuOptionCollection layoutIfNeeded];
}
completion:^(BOOL finished)
{
}];
I changed to and now works in both iOS7 and 6, still not sure why it does/did it though! I still think I am setting something up wrong in the Storyboard. I am add another view (nothing to do with this lot) programmatically so I believe that is based around frames until I convert it (which I am not doing).
primaryMenuYContraints.constant = BUTTOMX;;
leftMenuYContraints.constant = 136.0f;
leftMenuBottomConstraint.constant = 5.0f;
[UIView animateWithDuration:0.7f
delay:0.0f
options:UIViewAnimationOptionCurveLinear
animations:^
{
// Move in menus
[self.view layoutIfNeeded];
}
completion:^(BOOL finished)
{
}];

Scaling UIView using transform after animating frame change causes UIView to jump back to original frame before scaling

I'm trying to scale a UIView (with animation) after I move it (with animation). The problem is, when the scaling animation begins, it jumps back to the original position. Why?
[UIView animateWithDuration:t delay:0 options:UIViewAnimationOptionCurveEaseIn animations:^{
// Drop the ball
CGRect frame = coinView.frame;
frame.origin.y += d;
coinView.frame = frame;
coinView.shouldSparkle = NO;
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
// Initial scale up for ball "poof"
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:^(BOOL finished) {
[UIView animateWithDuration:0.3 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 0.000001, 0.000001);
} completion:^(BOOL finished) {
[coinView removeFromSuperview];
}];
}];
}];
EDIT: This is how I generated my d:
static CGFloat groundPositionY = 325;
CGRect convertedFrame = [coinView.superview convertRect:coinView.frame toView:self.view];
CGFloat d = groundPositionY - CGRectGetMaxY(convertedFrame);
EDIT2: Okay, so I changed the second UIView animation to the following and I discovered that the jump (and the scale down) happens before the second animation occurs, i.e. when animateWithDuration:delay:options:animations:completion: is called.
[UIView animateWithDuration:5 delay:3 options:0 animations:^{
coinView.transform = CGAffineTransformScale(coinView.transform, 1.5, 1.5);
} completion:nil];
I tested your code and it works fine for me. How do you generate your d? and on which block exactly it goes back?
I found the problem...
The coinView is a subview of a child view controller's view. The problem comes from the fact that I overrode that child view controller's viewDidLayoutSubviews method to lay out all the coinView's, so whenever that method was called, the coin view would move back to its original position and size intended by the child view controller.
Thanks for all of your help, repoguy and sergio!!

Fading UIView allows subviews to be seen

I have a UIScrollView which contains various subviews (UIImageViews, UILabels and standard UIViews). Some of the UIImageViews are partially covered by other UIViews.
However, when I fade out the UIScrollView, the partially covered parts of the UIImageViews are being exposed for the brief moment of the animation.
I want to be able to fade the scrollview and all it's contents at the same time in the same animation - i.e. not revealing any of the partially covered images.
If it's not possible, I can always add a UIView on top of all the other controls and fade it from alpha 0 upto 1 to hide everything, but I'm sure there's a way to perform a complete fade on a view and all it's subviews.
I tried this:
[UIView beginAnimations:nil context:NULL];
[scrollViewResults setAlpha:0.0f];
[UIView commitAnimations];
And I've tried this:
- (IBAction)questionGroupChanged:(UIButton*)sender {
[UIView beginAnimations:nil context:NULL];
[self fadeViewHierarchy:scrollViewResults toAlpha:0.0f];
[UIView commitAnimations];
}
- (void)fadeViewHierarchy:(UIView*)parentView toAlpha:(float)alpha {
[parentView setAlpha:alpha];
for (UIView *subView in parentView.subviews) {
[self fadeViewHierarchy:subView toAlpha:alpha];
}
}
But I've still not cracked it. Any ideas?
This happens because of the way the compositor works. You need to enable rasterization on the view's layer when fading it in/out:
view.layer.shouldRasterize = YES;
You should probably only enable this for the duration of the animation because it will take up some extra memory and graphics processing time.
Mike's answer is the correct one and he deserves all credit for this. Just to illustrate, it might look like:
- (void)fadeView:(UIView*)view toAlpha:(CGFloat)alpha
{
view.layer.shouldRasterize = YES;
[UIView animateWithDuration:0.75
animations:^{
view.alpha = alpha;
}
completion:^(BOOL finished){
view.layer.shouldRasterize = NO;
}];
}
Thus, using your scrollViewResults, it would be invoked as:
[self fadeView:scrollViewResults toAlpha:0.0f];
Did you try with UIView class methods +animateWithDuration:* (available on iOS 4 and +)
Like :
- (void)fadeAllViews
{
[UIView animateWithDuration:2
animations:^{
for (UIView *view in allViewsToFade)
view.alpha = 0.0;
}
completion:^(BOOL finished){}
];
}

Canceling an UIView Animation Block

The code below shows an animation of a label which contains a status message for the user. If an event happens the label is showing prompt and is slowly disappearing via uiview animation block.
- (void)showStatusOnLabelWithString:(NSString *)statusMessage
{
// [self.view.layer removeAllAnimations]; // not working
[labelStatus.layer removeAllAnimations]; // not working, too
[labelStatus setText:statusMessage];
[labelStatus setHidden:NO];
[labelStatus setAlpha:1.0];
[UIView animateWithDuration:5.0 animations:^
{
[labelStatus setAlpha:0.0];
} completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
}
If there is another event in the following 5s after the first the label should animate again, so I removed the previous animation with [self.view.layer removeAllAnimations] (thats what I thought).
But the label just completely disappear and the next 5s the label is invisible again.
If I (or the user) wait(s) the 5s everything is working properly.
Why isn't this working?
Kind Regards,
$h#rky
Change this:
completion:^(BOOL finished)
{
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}];
to this:
completion:^(BOOL finished)
{
if (finished) {
[labelStatus setHidden:YES];
[labelStatus setAlpha:1.0];
}
}];
The reason is you are reaching this completion block when you remove the animations for the layer, but finished will be false because you interrupted it. Also, the order is important here. Perhaps you were expecting removeAllAnimations to call the completion block instantly, but instead it will be called after your showStatusOnLabelWithString: method finishes, so what is happening is you are calling setHidden:NO followed immediately by setHidden:YES.
Did you try removing animations from the label's layer (labelStatus.layer)?

presentModalViewController translucent with previous view in the background

Im trying to present a view translucently and that the previous view sticks around and be that its visible in the background.
I've got
[self presentModalViewController:modalView animation:YES];
and I have the transparency set in the modalView's viewDidLoad, but after modalView gets brought up the previous view disappears. What can I do to keep the other view to stay around in the background?
I have also tried adding it with
[self.view addSubview:modalView.view];
It doesn't cover the whole screen, I would like to be able to solve this problem using presentModalViewController method.
It sounds like you just want a view to be displayed on top of your main view. Modal views are a finicky way of presenting subviews, instead you should look at creating a simple view class to add to your view controller. You can then use [UIView animate...]; method to animate it in and out of view.
To get you started:
- (void)displayViewButtonPressed(id)sender
{
if (!self.topView)
{
UIView *overlayView = [[UIView alloc] initWithFrame:CGRectMake(44.0f, 22.0f, 40.0f, 44.0f];
[overlayView setAlpha:0.0f];
[overlayView setBackgroundColor:[UIColor redColor]];
[self setTopView:overlayView];
[overlayView release];
}
[self.view addSubView:self.topView];
[UIView animateWithDuration:0.5
animations:^{
[self.topView setAlpha:1.0f];
}];
}
In the above method we create a custom UIView and animate it into position. We maintain a pointer to it so we can remove it later (like so:)
- (void)dismissViewButtonTapped:(id)sender
{
[UIView animateWithDuration:0.5
animations:^{
[self.topview setAlpha:0.0f];
}
completion:^(BOOL finished) {
[self.topView removeFromSuperView];
}
}
Its a little more work that using modal views, but it gives you much more flexibility in regards to what you use and how you display it.
Hope this helps:)