I'm trying to do an animation where when a person clicks from point A to Point B on screen the object should slowly slide straight across (horizontally) from point A to Point B.
I'm new at animations by the way.
[UIView animateWithDuration:10
delay:0
options:nil
animations:^ {
if(magnifier != nil){
[magnifier removeFromSuperview];
}
magnifier = [[MagnifierView alloc] init];
magnifier.viewToMagnify = imageView;
magnifier.touchPoint = newPoint;
[imageView addSubview:magnifier];
[magnifier setNeedsDisplay];
}
completion:nil];
but for some reason it is moving it way up and then eventually to point B. sort of in a weird curve.
how can I do this correctly?
Don't use a loop. Just use the animation block that you have.
Before the block, set the object to point A.
In the block, set it to point B. The block will cause it to animate.
Also, initialize your object outside the animation block.
Example:
// initialize your object outside the block
magnifier = [[MagnifierView alloc] init];
[magnifier setFrame:CGRectMake(0, 0, 100, 100)]; // for example, start at 0, 0 (width and height set to 100 for demo purposes)
[imageView addSubview:magnifier];
[UIView animateWithDuration:10
delay:0
options:nil
animations:^ {
// inside the animation block, put the location you want the magnifier to move to
[magnifier setFrame:CGRectMake(500, 0, 100, 100)]; // for example, move to 500, 0
}
completion:^(BOOL finished) {
// do anything you need after here
}];
Also for options you can set UIViewAnimationOptionCurveEaseInOut if you want an easing effect at the beginning and end of the animation or UIViewAnimationOptionCurveLinear if you want an equally timed animation with no easing (there are others available, look up UIViewAnimationOptions).
Related
What I have:
I have a UIView (named pView) which has as sublayer a CAGradientLayer. Practically is this:
ViewController -> View ->pView -> CAGradientLayer
This is the code that creates all this:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
UITestView * pView = nil;
UIColor * scolor = nil, *ecolor = nil;
self.view.backgroundColor = [UIColor yellowColor];
pView = [[UITestView alloc] initWithFrame: CGRectMake(self.view.frame.origin.x, 100.0, self.view.frame.size.width, 100.0)];
scolor = [UIColor colorWithRed:(14/255.0) green: (238/255.0) blue:(123/255.0) alpha:1];
ecolor = [UIColor colorWithRed:(6/255.0) green: (216/255.0) blue:(69/255.0) alpha:1];
// creating the gradient layer
CAGradientLayer * layer = [[CAGradientLayer alloc] init];
layer.frame = self.bounds;
layer.colors = #[(id)scolor.CGColor,(id)ecolor.CGColor];
[pView.layer insertSublayer:layer atIndex:0];
// creating a tapGestureRecognizer
[pView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTapInViews:)]];
[self.view addSubview:pView];
touched = NO;
}
....
#end
What I'm trying to do
Once a tap gesture is detected over pView I want to increase the height of pView by 100.0 but animatedly. If pView was previously touched (that is, its height was already increased by 100.0) I want to decrease the height of pView by 100.0 (that is, returning it to its original size);
What I already know
I know that since I want to change the frame of pView I must change also the frame (or bounds) of the CAGradientLayer attached to pView. Since I want to animate these changes, I want these animations occurs at the same time and have the same duration.
I know that the frame (or bounds) of a layer is only animatable inside an animation block.
What I do:
This is the option I've test:
- (void) handleTapInViews: (nonnull UITapGestureRecognizer *) sender {
pView = sender.view;
if (touched) {
[UIView animateWithDuration:0.4 animations:^{
pView.frame = CGRectMake(pView.frame.origin.x, pView.frame.origin.y, pView.frame.size.width, pView.frame.size.height - 100.0);
pView.layer.sublayers[0].frame = pView.bounds;
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
touched = NO;
}];
}
else {
[UIView animateWithDuration:0.4 animations:^{
pView.frame = CGRectMake(pView.frame.origin.x, pView.frame.origin.y, pView.frame.size.width, pView.frame.size.height + 100.0);
pView.layer.sublayers[0].frame = pView.bounds;
[self.view setNeedsLayout];
} completion:^(BOOL finished) {
touched = YES;
}];
}
}
This option actually works (that is, both the frame of pView and the frame of the layer change animatedly) but they are not synchronised; that is, the changes in the frame of the layer are slightly (but perceptible) more faster than the changes in the frame of pView. This effect is more evident when the height of pView is decreased.
I 've also read about CABasicAnimation and CAAnimationGroup, in order to animate both the bounds and position of the layer. In this case also the animation of the layer is bit faster than the animation of the view (it is perceptible and it is not a matter of seconds in case anyone ask about setting duration or whatever) and also in this case, after the animation the bound of the layer return to its original size which is not the desired effect. I already know this last matter can be fixed assigned the new values to the layer at the end of the animation but I certainty do not know where in my code put that.
Most of what i've read regarding this other option is from these links:
link1
link2
link3
link4
In any case, does anybody please knows how can I fix this?? thanks in advance.
Well I ended setting the backgroundColor of Pview as ecolor = [UIColor colorWithRed:(6/255.0) green: (216/255.0) blue:(69/255.0) alpha:1. This way, the effect I commented about the animation of the layer been faster than the animation of the view is not noticeable now. I think maybe this is not the proper answer but it suit me.
Before you discredit this question as being asked before, please look at the details:
I have a simple animation of shrinking the frame of a view from it's normal size to a zero frame in the middle of the superview. In my completion block I remove the superview from its superview and remove the view controller from the parent view controller. The completion block is called immediately. Moreover, the BOOL parameter finished to the completion block equals YES. So when I check if the animation is finished it says that it is even when it's not and the view is prematurely removed. Here is my code:
- (void)closeWindow {
[UIView animateWithDuration:0.5
animations:^{
contentView.frame = [self getSchoolSetUpStartingFrame];
} completion:^(BOOL finished){
if(finished) {
[self.view removeFromSuperview];
[self removeFromParentViewController];
}
}];
}
- (CGRect)getSchoolSetUpStartingFrame {
CGRect startingFrame;
CGRect myFrame = self.view.frame;
float xPos = (myFrame.origin.x + (myFrame.size.width/2));
float yPos = (myFrame.origin.y + (myFrame.size.height/2));
startingFrame = CGRectMake(xPos, yPos, 0, 0);
return startingFrame;
}
Is there anything else that could be related to this problem that I haven't considered yet? I appreciate all the help.
I'd try changing the animation duration to .5f, and verify that contentView is not nil during the animation block.
Also, it might be easier to achieve this animation by using a transform
animations:^{
contentView.transform = CGAffineTransformMakeScale(.1f, .1f);
}
I have 1 year works with Objective-C and is my first question that I make because I always found the answer in this site, but this situation is completely insane and did not found anything similar.
I have a custom class (called BallClass from UIView). The UIView have an UIImageView inside on it and a UITapGesture to make an action.
So when I create the object in a UIViewController, does not works the tapAction when press in the object, but if I change to other screen and return it, the tapGesture works perfect.
I tried many options, but I cant makes work in the first load.
I work with ARC, Xcode (4.6.3) and OSX (10.8.4) and IB but this objects was created with no IB .
Thanks in advance.
Here is the class BallClass.m
- (void)tapGesture:(UIGestureRecognizer *)tapGesture {
NSLog(#"hola");
}
- (id)initBall:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
// Recibir las variables iniciales para el control del timer
ball = [[Ball alloc] init];
// Agregar la imagen de la pelota en el view
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
[imageView setCenter:CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds]))];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
[imageView setImage:[UIImage imageNamed:#"pelota.png"]];
[imageView setTag:100];
[imageView setNeedsDisplay];
// Add TAP
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
// Tap Properties
[tapGesture setDelaysTouchesBegan:YES];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:(id)self];
[imageView addGestureRecognizer:tapGesture];
[self addSubview:imageView];
[self setNeedsDisplay];
// Propiedades del View
[self setUserInteractionEnabled:YES];
self.backgroundColor = [UIColor clearColor];
[self setTag:100];
// BALL BOUNCE
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)onTimer:(NSTimer *)timerLocal {
// Do something
}
... // more methods
Here is the instantiate:
ballView00 = [[BallView alloc] initBall:CGRectMake(10, 10, 40, 40)];
The UiView show perfect, and the animation works fine, but the UITapGesture just works, as mentioned early, after reLoad the ViewController.
Here is the link to image the Ball and UIView (#kBall). Sorry, about image, but I cant load until I have 10 points :(
This might be an issue with the superview's frame. What view is holding ballView00? Does ballView lie entirely within it's superview's bounds?
Usually when you encounter problem where you're expecting a touch but don't get it it's because the view set to receive the touch is outside one of its parents' bounds.
So if the superview bounds are 100x100 and the ball is at x=125 y=125 it will not receive a touch.
A lot of magic happens when your views get laid out, it's not surprising to sometimes find one with 0x0 dimensions because of some incorrect constraints.
You can easily check the bounds of all your views by giving them a background color.
I found the solution.
The ball make a spin with this:
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: UIViewAnimationOptionCurveEaseIn
animations: ^{
self.transform = CGAffineTransformRotate(self.transform, M_PI / 2);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
}
}
}];
And generate conflict with the main timer. I change the spin action with simple add self.transform = CGAffineTransformRotate(self.transform, M_PI / 2); inside drawRect method in the class.
Apparently, I can't nested two timers, because "disable" the tapGesture, so when I change of the screen and return, the spin was stopped and this help to found this situation.
Thanks for your help.
I'm attempting to fade in a UIView that I've created by animating the alpha. I need to fade it in, leave it visible for a few seconds, then fade it out.
The fade out function works fine. The view smoothly disappears. But the fade in just makes the view appear instantly instead of slowly appearing over an interval of 0.5 seconds.
So it seems like the fade in animation isn't working, just instantly setting the alpha to 1.0. I'm kind of at a loss here. Any ideas what I'm doing wrong? Thanks!
-(void)presentPopupPhrase:(NSString *)phrase
inView:(UIView *)view
withDelegate:(id)delegate
andCompletion:(void (^)(BOOL completed))completion {
MessagePopupView *pv = [[[MessagePopupView alloc] initWithFrame:self.frame andText:phrase] autorelease];
pv.alpha = 0.0;
[view addSubview:pv];
[self fadeInMPV:pv
withDuration:self.fadeDuration
andDelay:self.fadeInDelay];
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
}
-(void)fadeInMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
andDelay:(NSTimeInterval)delay
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 1.0;
}
completion:nil];
}
-(void)fadeOutMPV:(MessagePopupView *)mpv
withDuration:(NSTimeInterval)duration
afterDelay:(NSTimeInterval)delay
withCompletion:(void (^)(BOOL completed))completion
andDelegate:(id)delegate
{
[UIView animateWithDuration:duration
delay:delay
options:UIViewAnimationOptionCurveLinear
animations:^{
mpv.alpha = 0.0;
}
completion:completion];
}
EDIT:
If it helps, here's the VC code where I'm calling it from:
-(void)viewDidAppear:(BOOL)animated {
CGRect phraseFrame = CGRectMake(20, 341, 280, 65);
PopupPhraseController *phraseController = [[[PopupPhraseController alloc] initWithFrame:phraseFrame] autorelease];
[phraseController presentPopupPhrase:#"Test Phrase" inView:self.view withDelegate:self andCompletion:^(BOOL completed){
if (completed) {
NSLog(#"completed");
} else {
NSLog(#"not completed");
}
NSLog(#"blocked!");
}];
[super viewDidAppear:animated];
}
You can't chain the animations like that as the second animation block essentially causes the first one to be cancelled out.
You have two options for Linking Multiple Animations Together:
Use the completion handler of the first animation
Nest the animations but delay the second one
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
} completion:^(BOOL finished) {
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
}];
Looks something like this
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeInDelay
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 1.0;
[UIView animateWithDuration:self.fadeDuration
delay:self.fadeOutDelay + self.fadeDuration
options:UIViewAnimationOptionCurveLinear
animations:^{
pv.alpha = 0.0;
} completion:completion];
} completion:nil];
The issue is because of the way you are setting up the animations.
When you set animatable properties on a view the backing model of the view is updated instantly. So when you set the alpha = 1.0 in the first block the backing data model for the view has the alpha as 1.0 and then the actual animation is kicked off on another thread to show the interpolation between the two values.
When you define the second block straight after the first the view essentially says "ok I need to animate from 1.0 (my current value in the model) to 0.0", which is why you don't see what you expected.
Thanks to Paul.s for dropping the hint there. I think this expected behaviour is a little weird, even if it seems logical from a low level perspective.
To expand on Paul's 2 solutions, there is a 3rd solution which is better IMO.
Use GCD to add a delay to the fade out code, like such:
//Make sure this delay exceeds your fade-in time...
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self fadeOutMPV:pv
withDuration:self.fadeDuration
afterDelay:self.fadeOutDelay
withCompletion:completion
andDelegate:delegate];
});
This solution's better because the executing view controller has complete control on not only when, but what gets to trigger the fade out (for example, a user action).
I am currently iterating through a list and based on the condition of the element I would like to display one of two images. This works fine but how do i put a pause in between displaying the image. I have tried using:
for(
...
[myView addSubview:myImage]
sleep(1)
...
)
after each check but this for some reason waits for the end of the function before displaying any of the images.
Does anyone what the cause of this is and how to get around it?
NSArray *imageArray = [NSArray arrayWithObjects:image1, image2, imageN, nil];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.animationImages = imageArray;
imageView.animationDuration = [imageArray count]*3;
imageView.animationRepeatCount = 0;
[imageView startAnimating];
You can't use sleep() here since it will block the thread that is updating you UI.
What you could do is call a different method that set the second image:
for(
...
[myView addSubview:myImage]
// schedule the method.
[myView performSelector:#selector(addSubview:) withObject:mySecondImage afterDelay:1.0f];
...
)
You could set the alpha/visibility of the element to 0.0f/hidden and use an animation block with a delay to display the view. Gain a fancy animation in the process by setting the duration to something bigger than 0 ;-)
[UIView animateWithDuration: 0.0
delay: 1.0
options: UIViewAnimationCurveEaseOut
animations: ^{
myView.alpha = 1.0f;
}
completion:^(BOOL finished){}];
You can create a function to display an image a use "perform after delay":
-(void)displayImage {
[...get the image...]
[myView addSubview:myImage];
[self performSelector:#selector(displayImage) withObject:nil afterDelay:1.0];
}