UIView's edge flickers at the end of an animation - ios

My main UIViewController has a sub UIView named viewContainer. It has a couple of UIImageViews, UIButtons and UIViews added to it. In my viewDidLoad method I've applied the following to viewContainer:
self.viewContainer.layer.shadowColor = [UIColor blackColor].CGColor;
self.viewContainer.layer.shadowOffset = CGSizeMake(0, 0);
self.viewContainer.layer.shadowOpacity = 1.0;
self.viewContainer.layer.shadowRadius = 10.0;
self.viewContainer.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.viewContainer.layer.bounds].CGPath;
self.viewContainer.backgroundColor = [UIColor blackColor];
self.viewContainer.opaque = YES;
When a user presses a button inside the viewContainer, I perform the following translation:
[UIView animateWithDuration:0.3
animations:^{
[self.viewContainer setCenter: CGPointApplyAffineTransform(self.viewContainer.center, CGAffineTransformConcat(self.viewContainer.transform, CGAffineTransformMakeTranslation(60, 0)))];
}
completion:nil];
My problem is that at the end of the animation, the edge of the view flickers for a fraction of a second (a white vertical line). I've tried setting the frame instead of a transform but still the same result. I'm attempting to do a sort of slide to reveal options thing, which works but flickers on completion.
Any ideas?

Related

UIView animation for a shared view in a UIPageController

Much like other apps, my app has a "welcome" page controller with a quick overview of the features. During this overview, I draw a UITabBar and have a circle show where the relevant feature is seen below:
I draw the circle using the following code that is executed every time a page is drawn:
double circleSize = 75;
[circleView removeFromSuperview];
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
I wish to have this circle appear to "breath" (grow slowly then shrink back to its original size). I use the exact code from my answer to this question:
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
This circle is shared across three pages of the page controller and draws just fine. The animation works just fine on the first page, periodically works on the second page, and never works on the third page.
How can I have the animation play on every page for every circle view?
Here is a solution that works across all tabs. The animation can be moved around while in progress. You may want to use a better mechanism to manage the animation so that it can be cancelled efficiently. The code below is implemented in the Tab Controller. Ensure -showOverTabBarItem: and -hideCircleView are executed on main thread. It has been built, linked, ran and tested.
Show
-(void)showOverTabBarItem:(CGFloat)x {
[self hideCircleView];
self.circleView.frame =({
CGRect frame = self.circleView.frame;
frame.origin.x = x;
frame;
});
[self.view addSubview:self.circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
self.circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];
}
Hide
-(void)hideCircleView
{
[self.circleView removeFromSuperview];
}
Initialize
Such as in viewDidLoad.
double circleSize = 75;
CGFloat circleY = self.view.bounds.size.height-(circleSize/2);
self.circleView = [[UIView alloc] initWithFrame:CGRectMake(0,
circleY,
circleSize,
circleSize)];
self.circleView.layer.cornerRadius = (circleSize / 2);
self.circleView.layer.borderColor = [UIColor blueColor].CGColor;
self.circleView.layer.borderWidth = 2;
Invoke
Pass the horizontal position to your animation: [self showOverTabBarItem: self.view.bounds.size.width/2];
Keep the view around
This is a critical step: when your -removeFromSuperView, you want to make sure that the object does not get recycled.
#interface TabController ()
#property (nonatomic, retain) UIView * circleView;
#end
Why have you alloc the circle every time a page drawn?
You can remove these lines (just need these for the first init):
if (circleView == nil) {
circleView = [[UIView alloc] initWithFrame:CGRectMake(circleX,
circleY,
circleSize,
circleSize)];
circleView.layer.cornerRadius = (circleSize / 2);
circleView.layer.borderColor = [UIColor VancityTransitBlue].CGColor;
circleView.layer.borderWidth = 2;
}
For add to another page: remove from current page
[circleView removeFromSuperview];
[circleView.layer removeAllAnimations];
and then add it to another page:
[self.view addSubView:circleView];
[UIView animateWithDuration:1
delay:0
options:UIViewKeyframeAnimationOptionAutoreverse | UIViewKeyframeAnimationOptionRepeat
animations:^{
circleView.transform = CGAffineTransformMakeScale(1.5, 1.5);
}
completion:nil];

"Fill button" animation in iOS

Do you have any suggestion to implement the following button animation in an iOS app?
Ok lets split this animation into a few segments to figure it out.
1) We need a white and green circle.
2) We need a tick image.
3) We need to animate the white circle and the tick.
Step One
Add a green UIView to your interface file. This will be the background view. We do not need to animate this view. Then link the green view to a IBOutlet like so:
IBOutlet UIView *greenView;
Step Two
Add a white UIView inside of the green UIView and make it the exact same size as the green UIView. Then link the white UIView to an IBOutlet in you header file like so:
IBOutlet UIView *whiteView;
Step Three
Make or download a 'tick' image and add it to your Xcode project. Then add the tick image ABOVE the green/white views that we just created. Please place the tick above the views and NOT in the views. Then link the tick image to an IBOutlet - this will help us detect when the view has been pressed. Do it like so:
IBOutlet UIImageView *tickImage;
Step Four
We need to change the green and white UIView to circles. We can do this with code. Import the #import <QuartzCore/QuartzCore.h> framework into you header file.
Then in your viewDidLoad method, add the following code which will change the green/white views into circles:
CGPoint greenCenter = greenView.center;
CGPoint whiteCenter = whiteView.center;
CGRect newFrameGreen = CGRectMake(greenView.frame.origin.x, greenView.frame.origin.y, 20, 20);
CGRect newFrameWhite = CGRectMake(whiteView.frame.origin.x, greenView.frame.origin.y, 20, 20);
greenView.frame = newFrameGreen;
greenView.layer.cornerRadius = newSize / 2.0;
greenView.center = greenCenter;
whiteView.frame = newFrameWhite;
whiteView.layer.cornerRadius = newSize / 2.0;
whiteView.center = whiteCenter;
Step Five
In order to run the animation, we need to connect the image view to a method which will run the animation. You can do it by using a UITapGestureRecognizer. In your viewDidLoad method, add the following:
UITapGestureRecognizer *newTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tickAnimation)];
[tickImage setUserInteractionEnabled:YES];
[tickImage addGestureRecognizer:newTap];
Then add the animation method to you implementation (.m) file as well:
-(void)tickAnimation {
}
Step Six
Create a BOOL value in your header file - we will use this to detect if the button is in the "ON" or "OFF" states.
BOOL animationMode;
Then in your viewDidLoad method, set the animationMode bool value to "OFF" - this is because the button will be "OFF" or in the white state by default.
animationMode = NO;
Now we can go on to the animation. From what I can see, this is split up into two animations. The first one is making the white view smaller and the second one is doing a subtle popup animation on the tick image. Update the tickAnimation method in order to perform the animation:
-(void)tickAnimation {
if (animationMode == NO) {
// Button is OFF or set to white, lets set it to ON.
[UIView animateWithDuration:1.0f animations:^{
whiteView.transform = CGAffineTransformMakeScale(0,0);
} completion:nil];
[UIView animateWithDuration:1.0f animations:^{
tickImage.transform = CGAffineTransformMakeScale(2,2);
} completion:nil];
[UIView animateWithDuration:1.0f animations:^{
tickImage.transform = CGAffineTransformMakeScale(0,0);
} completion:nil];
animationMode = YES;
}
else if (animationMode == YES) {
// Button is ON or set to green, lets set it to OFF.
[UIView animateWithDuration:1.0f animations:^{
whiteView.transform = CGAffineTransformMakeScale(1,1);
} completion:nil];
animationMode = NO;
}
}
This is my attempt at making this kind of animation. I hope this helps :)
You could animate the layer.borderWidth property for the colour fill.. It draws inside of the layer, something like this.
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(30, 30, 100, 100)];
[self.view addSubview:button];
button.layer.borderWidth = 10;
button.layer.borderColor = [UIColor greenColor].CGColor;
button.layer.cornerRadius = 50;
CABasicAnimation *border = [CABasicAnimation animationWithKeyPath:#"borderWidth"];
[border setFromValue:[NSNumber numberWithInt:10]];
[border setToValue:[NSNumber numberWithInt:50]];
border.duration = 1.0;
button.layer.borderWidth = 50;
[button.layer addAnimation:border forKey:#"border"];
Here is a really slow gif screen cap of this working with autoReverse set to true.
If you like Swift it looks like this
var button = UIButton(frame: CGRect(x: 30, y: 30, width: 100, height: 100))
self.view.addSubview(button)
button.layer.borderWidth = 10
button.layer.borderColor = UIColor.greenColor().CGColor
button.layer.cornerRadius = 50
let border:CABasicAnimation = CABasicAnimation(keyPath: "borderWidth")
border.fromValue = 10
border.toValue = 50
border.duration = 1.0
button.layer.borderWidth = 50
button.layer.addAnimation(border, forKey: "border")
First, you have an imageView with image like this.
Second, you overlay it with a white UIView, and at the same time, init a UIImageview for the v symbol to turn it into white color.
Next step, run animation to scale the white view from 1 to 0 in 0.2 sec.
When it done, you run other animation to make the white v symbol scale from 1 to 1.2 in 0.2 sec.
That is my idea.

Animation in UITableViewCell completes immediately

I have a subclass of UITableViewCell with button inside of it. The button have clear backgroundColor with some border. I want to do a fill in animation starting from the center of the button. My problem is that animation completes immediately. Here's my code, I've tried doing this in 2 different ways.
- (void)goingButtonAnimation {
UIView *goingButtonBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(self.goingButton.center.x, self.goingButton.center.y, 0., 0.)];
goingButtonBackgroundView.layer.cornerRadius = 2.;
goingButtonBackgroundView.backgroundColor = [UIColor blackColor];
[self insertSubview:goingButtonBackgroundView belowSubview:self.goingButton];
[UIView animateWithDuration:2. animations:^{
goingButtonBackgroundView.frame = self.goingButton.frame;
}];
}
I've also tried this:
goingButtonBackgroundView.transform = CGAffineTransformMakeScale(0.1, 0.1);
[UIView animateWithDuration:2. animations:^{
goingButtonBackgroundView.transform = CGAffineTransformMakeScale(1.0, 1.0);
goingButtonBackgroundView.center = self.goingButton.center;
}];
But the animation completes immediately in each case. goingButtonAnimation method is called after tapping on self.goingButton
My problem was that after calling goingButtonAnimation I called [self.tableView reloadData]

Animating UIView frame with constraints

I have an element in a UIView with a constraint that says it should always be 10 pixels from the bottom of the view. I am then attempting to animate this view's height so that it appears to slide down the screen. According to the constraint, the element should always be 10 pixels from the bottom of the view. This holds true when I add the view like so...
printView *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"printView"];
[self addChildViewController:vc];
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, HEIGHT);
vc.view.layer.masksToBounds = FALSE;
[vc.view.layer setShadowOffset:CGSizeMake(0.0, 5.0)];
[vc.view.layer setShadowOpacity:0.8];
vc.view.layer.shadowColor = [[UIColor blackColor] CGColor];
vc.view.layer.shadowRadius = 8;
vc.view.clipsToBounds = TRUE;
[self.view insertSubview:vc.view aboveSubview:docWebView];
I can change HEIGHT to whatever I want and the element is always 10 pixels from the bottom of the view. The problem comes when I try to animate the height
printView *vc = [self.storyboard instantiateViewControllerWithIdentifier:#"printView"];
[self addChildViewController:vc];
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, 0);
vc.view.layer.masksToBounds = FALSE;
[vc.view.layer setShadowOffset:CGSizeMake(0.0, 5.0)];
[vc.view.layer setShadowOpacity:0.8];
vc.view.layer.shadowColor = [[UIColor blackColor] CGColor];
vc.view.layer.shadowRadius = 8;
vc.view.clipsToBounds = TRUE;
[self.view insertSubview:vc.view aboveSubview:docWebView];
[UIView animateWithDuration:5.0 animations:^{
vc.view.frame = CGRectMake(0, 60, vc.view.frame.size.width, 200);
[self.view setNeedsDisplay];
}];
The constraint is no longer honored and instead of the view sliding in with the element always 10 pixels from the bottom, it looks like the element is being uncovered because the element does not move with the view. I hope I am explaining this well enough. To put it another way, I'm going for the effect of a map being pulled down, but instead it looks like the map is already there and a piece of paper that is covering it is being pulled away. Thanks for your help.
When using constraints, you shouldn't be setting frames, you should be adjusting the constraints. When you initially set up your constraints, you should make an IBOutlet to the height constraint, and then animate its constant parameter in the animation block. If you had a height constraint called heightCon, you could do this:
[UIView animateWithDuration:5.0 animations:^{
self.heightCon.constant = 200
[self.view layoutIfNeeded];
}];
I'm not sure about your structure, that self.view might have to be vc.view instead.
After Edit:
This is the way to animate a height constraint, but I'm not sure that's what you want to do to accomplish the look you're after. I'm not really sure what to make of your last paragraph. If you're going for an effect of a map being pulled down, it seems like the bottom constraint needs to be either animated or eliminated.

iOS slide up an image over an image, revealing the underneath one. (A-la jQuery.slide())

I'm trying to do the following thing -
I have two version of the same image, one colored and one black and white. I want the B&W to "Slide up" revealing the one under it.
I tried setting the height and the frame, but couldn't get it to work like I want it to.
For example , this would be a combination of both UIImages where one is revealing the other, while not moving:
Any ideas? :)
OK, here is a different method using a mask on the grey view. I actually tested this with a blueView and a greyView, where the grey covers up the blue. Put a mask layer on the grey layer so that the grey layer is visible. Now animate the mask away from the grey layer, making the grey layer disappear without moving it, the blue shows thru where the grey disappears.
If you want to experiment with this, create an empty project in XCode with a single view, and put this code inside viewDidLoad. That's how I tested this.
self.view.backgroundColor = [UIColor whiteColor];
UIView* blueView = [[[UIView alloc] init] autorelease];
blueView.backgroundColor = [UIColor blueColor];
blueView.frame = CGRectMake(50,50,100,100);
UIView* grayView = [[[UIView alloc] init] autorelease];
grayView.backgroundColor = [UIColor grayColor];
grayView.frame = CGRectMake(50,50,100,100);
[self.view addSubview:blueView];
[self.view addSubview:grayView]; // gray covers the blue
// Create a mask to allow the grey view to be visible and cover up the blue view
CALayer* mask = [CALayer layer];
mask.contentsScale = grayView.layer.contentsScale; // handle retina scaling
mask.frame = grayView.layer.bounds;
mask.backgroundColor = [UIColor blackColor].CGColor;
grayView.layer.mask = mask;
// Animate the position of the mask off the grey view, as the grey becomes unmasked,
// that part of the grey "dissappears" and is no longer covering the blue underneath
CABasicAnimation* a = [CABasicAnimation animationWithKeyPath:#"position"];
a.duration = 4;
a.fromValue = [NSValue valueWithCGPoint:mask.position];
CGPoint newPosition = mask.position;
newPosition.y += mask.bounds.size.height;
a.toValue = [NSValue valueWithCGPoint:newPosition];
a.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[mask addAnimation:a forKey:#"colorize"]; // animate presentation
mask.position = newPosition; // update actual position
Don't forget this line at the top with the #imports:
#import <QuartzCore/QuartzCore.h>
One way is to stack the UIView's so that the B&W image on top of the color image, where the color image is completely covered (hidden) by the B&W image.
[self.view insertSubview:bwImage aboveSubview:colorImage];
Only the B&W view is visible. Now set clipsToBounds on the B&W view:
bwImage.clipsToBounds = YES;
Finally animate the height in the bounds of the bwImage so that its height shrinks down to 0, clipping the B&W image and revealing the color image behind it. Something like this:
[UIView animateWithDuration:1.0 animations:^{
CGRect b = bwImage.bounds;
b.size.height = 0;
bwImage.bounds = b;
}];
I haven't tried this but hopefully you get the idea.

Resources