UIView animationWithDuration recursive cause jitter - ios

I tried to set the property as atomic but it doesn't seems to work anyway. Here's the code. It move a label displaying the song name being played. And sometime it start jittering as if the label was supposed to be at two places at the same time. Any clue ? Some lock property maybe...
- (void)animateSongLabel
{
if (!self.player.rate) {
[UIView animateWithDuration:.5 animations:^{
self.songLabel.left = 25.f;
}];
return;
}
[UIView animateWithDuration:.25 delay:0. options:UIViewAnimationOptionCurveLinear animations:^{
self.songLabel.left -= 15.f;
} completion:^(BOOL finished) {
if (self.songLabel.right < -10.f) {
self.songLabel.left = 320.f;
}
[self animateSongLabel];
}];
}

The rock-bottom simplest way to make text that just sits there and scrolls sideways is to display an animated UIImage or animated UIImageView - especially the former, because it animates forever, which is probably what you want. Generate the successive UIImage objects in code by drawing your text slightly offset each time, and hand them to animatedImageWithImages:duration:. Now wherever you show that image, it will be animating.
Here's some sample code to get you started (tweak to get precisely the effect you have in mind):
NSString* s = #"Mister Dobbalena, Mister Bob Dobbalena";
NSMutableArray* marr = [NSMutableArray array];
UIFont* f = [UIFont fontWithName:#"Helvetica" size:12];
float w = [s sizeWithFont:f].width;
for (int offset = 0; offset < w; offset++) {
UIGraphicsBeginImageContextWithOptions(self.iv.frame.size, NO, 0);
[s drawAtPoint:CGPointMake(10-offset,20) withFont:f];
[marr addObject: UIGraphicsGetImageFromCurrentImageContext()];
UIGraphicsEndImageContext();
}
UIImage* im = [UIImage animatedImageWithImages:marr duration:20];
self.iv.image = im;

Related

How to get the position of oldest object made during animation?

I make every second a UIImageView of a french frie called FrenchFrie. I also let them move along the screen with an animation.
-(void)FrenchFrieRain{
FrenchFrie = [[UIImageView alloc] initWithFrame:CGRectMake(randomX,0,30,30)];
FrenchFrie.image = [UIImage imageNamed:#"FrenchFriesMCDonalds"];
FrenchFrie.userInteractionEnabled = YES;
[self.view addSubview:FrenchFrie];
[FrenchFrieArray addObject: FrenchFrie];
[self letFrenchFrieFall];
}
-(void)letFrenchFrieFall {
[UIView animateWithDuration:10.0f delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
FrenchFrie.frame = CGRectMake(randomX, (int)[[UIScreen mainScreen] bounds].size.height + 40, FrenchFrie.frame.size.width, FrenchFrie.frame.size.height);
} completion:^(BOOL finished){
[[FrenchFrieArray objectAtIndex:0] removeFromSuperview];
}];
}
I need the position of the frie during the animation. I do this with the following code:
FrenchFrieFrame = [[FrenchFrie.layer presentationLayer] frame];
From NSLogging I know that with this code I get the position of the newest frie but I need the position of the oldest one. How can I get the position of the oldest French Frie?
Any help would be really appreciated!
You are keeping track of the order in which you create FrenchFries in your FrenchFrieArray. So, the item at index 0 will be the oldest FrenchFrie, always. You could use:
FrenchFrieFrame = [[FrenchFrieArray[0].layer presentationLayer] frame];
For this to work, you should also remove the FrenchFrie from the array, besides removing it from the view:
[[FrenchFrieArray objectAtIndex:0] removeFromSuperview];
[[FrenchFrieArray removeObjectAtIndex:0];

UIView Animation doesn't run in correct order in for loop - iOS

I have a simple for loop which animates some UILabels. The reason I have the animation block code in my for loop is because obviously I have more than one label I am trying to animate.
Now my program increments the numbers in the UILabels by one every time AFTER the ENTIRE for loop has finished. However by the time the animation happens, the number has already been incremented (which is not what I want), even though the number increment code comes AFTER the for loop....
I'm not sure if I have explained the problem correctly, but in ensconce what I am asking, since we know for a FACT that animations run on the main thread, how can we ensure that the animations happen in the correct order (while in a for loop)???
Here is my code:
for (int loop = 0; loop < [number_arrange count]; loop++) {
if ([number_arrange[loop] integerValue] == checknum) {
[anim_check replaceObjectAtIndex:loop withObject:[NSNumber numberWithInteger:1]];
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
((UILabel*)_labels_anim[loop]).transform = CGAffineTransformMakeScale(5.0, 5.0);
((UILabel*)_labels_anim[loop]).layer.anchorPoint = CGPointMake(0.5, 0.5);
((UILabel*)_labels_anim[loop]).frame = CGRectOffset(((UILabel*)_labels_anim[loop]).frame, 0.0, -20.0);
((UILabel*)_labels_anim[loop]).alpha = 0.0;
} completion:^(BOOL finished) {
((UILabel*)_labels_anim[loop]).transform = CGAffineTransformMakeScale(1.0, 1.0);
((UILabel*)_labels_anim[loop]).layer.anchorPoint = CGPointMake(0.5, 0.5);
((UILabel*)_labels_anim[loop]).frame = CGRectOffset(((UILabel*)_labels_anim[loop]).frame, 0.0, 20.0);
((UILabel*)_labels_anim[loop]).alpha = 1.0;
}];
}
}
Thanks for your time, Dan.
The issue is that you're accessing "loop" from inside both blocks. Why don't you try running the loop inside of the animation blocks? Something like this:
[UIView animateWithDuration:0.4 delay:0.0 options:UIViewAnimationOptionCurveEaseOut animations:^{
for (int loop = 0; loop < [number_arrange count]; loop++) {
if ([number_arrange[loop] integerValue] == checknum) {
[anim_check replaceObjectAtIndex:loop withObject:[NSNumber numberWithInteger:1]];
((UILabel*)_labels_anim[loop]).transform = CGAffineTransformMakeScale(5.0, 5.0);
((UILabel*)_labels_anim[loop]).layer.anchorPoint = CGPointMake(0.5, 0.5);
((UILabel*)_labels_anim[loop]).frame = CGRectOffset(((UILabel*)_labels_anim[loop]).frame, 0.0, -20.0);
((UILabel*)_labels_anim[loop]).alpha = 0.0;
}
}
} completion:^(BOOL finished) {
for (int loop = 0; loop < [number_arrange count]; loop++) {
if ([number_arrange[loop] integerValue] == checknum) {
((UILabel*)_labels_anim[loop]).transform = CGAffineTransformMakeScale(1.0, 1.0);
((UILabel*)_labels_anim[loop]).layer.anchorPoint = CGPointMake(0.5, 0.5);
((UILabel*)_labels_anim[loop]).frame = CGRectOffset(((UILabel*)_labels_anim[loop]).frame, 0.0, 20.0);
((UILabel*)_labels_anim[loop]).alpha = 1.0;
}
}
}];
I hope that helps!

Drop image and release when it goes out of bounds

I am new to programming in Objective-C, and am currently having an issue with the code below. I am trying to make an image drop when a button is touched, and it simply freezes when trying to do so. It needs to drop the image until it reaches the bottom of the screen, which then it should be released.
- (IBAction)itemDrop:(UIButton *)sender{
if (strncmp([[sender currentTitle] UTF8String], [copyPrimaryArray[0] UTF8String], 2) == 0)
{
UIImage *bubblImage = [UIImage imageNamed:#"Red_apple.png"];
UIImageView *appleImg = [[UIImageView alloc] initWithImage:bubblImage];
appleImg.translatesAutoresizingMaskIntoConstraints = NO;
[appleImg setContentMode:UIViewContentModeScaleAspectFit];
[appleImg sizeToFit];
[self.view addSubview:appleImg];
NSLog(#"You clicked the Apple");
int i = 0;
CGPoint coordinates = sender.frame.origin;
CGFloat x = coordinates.x;
CGFloat y = coordinates.y;
CGFloat z = UIScreen.mainScreen.bounds.size.height;
while (y < z) {
[UIView beginAnimations:nil context:nil];
appleImg.center = CGPointMake(x, y+i);
[UIView commitAnimations];
i++;
}
}
}
You do not need to move the image view one point at a time manually. That's what I seem to understand from your use of the while loop. Objective-c does a lot of things for you.
UIImage *bubblImage = [UIImage imageNamed:#"Red_apple.png"];
UIImageView *appleImg = [[UIImageView alloc] initWithImage:bubblImage];
// Set the frame of the original position here, for example:
CGRect originalPosition = CGRectMake(0, 0, appleImg.frame.size.width, appleImg.frame.size.height);
appleImg.frame = originalPosition;
// Then animate it to the new position:
[UIView animateWithDuration:1.0
animations:^{
CGRect newPosition = CGRectMake(0, 640, appleImg.frame.size.width, appleImg.frame.size.height);
appleImg.frame = newPosition;
}
completion:^(BOOL finished) {
NSLog(#"completion block"); // You can set the completion block to nil if you have nothing to do here.
}];
Make sure to start using block based animations.
The use of UIView's beginAnimations:context is highly discouraged since iOS 4.0.

The device freezes when there are a lot of UIView animation running with UILabels using shadows and I press the Home button

I'm having some problems in my application. The error occurs when I'm in a screen with a lot of icons (UIViews), each one running a animation. This screen reminds the springboard screen, and the animation visual is similar too.
So, if I press the home button, the application doesn't goes to background and I can't do anything. Even the power button doesn't work. And the icons continue shaking.
If I remove the call for label creation method, this don't happens.
Any advice?
Thanks!
Animation method (extracted from Three20 api):
- (void)wobble {
static BOOL wobblesLeft = NO;
if (isEditing)
{
CGFloat rotation = (kWobbleRadians * M_PI) / 180.0;
CGAffineTransform wobbleLeft = CGAffineTransformMakeRotation(rotation);
CGAffineTransform wobbleRight = CGAffineTransformMakeRotation(-rotation);
[UIView beginAnimations:nil context:nil];
NSInteger i = 0;
NSInteger nWobblyButtons = 0;
for(Icon *ic in iconList)
{
++nWobblyButtons;
i++;
if (i % 2)
{
ic.transform = wobblesLeft ? wobbleRight : wobbleLeft;
} else {
ic.transform = wobblesLeft ? wobbleLeft : wobbleRight;
}
}
if (nWobblyButtons >= 1)
{
[UIView setAnimationDuration:kWobbleTime];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(wobble)];
wobblesLeft = !wobblesLeft;
} else {
[NSObject cancelPreviousPerformRequestsWithTarget:self];
[self performSelector:#selector(wobble) withObject:nil afterDelay:kWobbleTime];
}
[UIView commitAnimations];
}
}
Label creation
-(void)layoutLabel
{
// If title isn`t builded.
if(_lblName == nil)
{
// Create new label.
_lblName = [[UILabel alloc] initWithFrame:CGRectMake(LABEL_POS_X,
LABEL_POS_Y,
LABEL_WIDTH,
LABEL_HEIGHT)];
// Clear the background.
[_lblName setBackgroundColor:[UIColor clearColor]];
// Sets the font.
[_lblName setFont:[UIFont fontWithName:#"Helvetica-Bold" size:11.3]];
[_lblName setAdjustsFontSizeToFitWidth:NO];
// Sets text color
[_lblName setTextColor:[UIColor whiteColor]];
// Adjust the number of lines.
[_lblName setNumberOfLines:2];
// Adjust the aligment to center.
[_lblName setTextAlignment:UITextAlignmentCenter];
// Adjust shadow like the springboad`s icons.
_lblName.layer.shadowOpacity = 1.0;
_lblName.layer.shadowRadius = 0.8;
_lblName.layer.shadowColor = [[UIColor blackColor] CGColor];
_lblName.layer.shadowOffset = CGSizeMake(0, 1.2);
// Add label to container.
[self addSubview:_lblName];
}
}
The problem is:
For each icon I have many subviews, so, the calculations of each icon, at animation time, resulted in a slow code.
So, I found some light to solve this. I builded the same icon structure, but, after, I merged everithing inside a UIImage with a code below.
+ (UIImage *) imageWithView:(UIView *)view
{
UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.opaque, 0.0);
[view.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
This increased my FPS from ~15 to 60 O.o
Follow some help that I found
Link
Shadows don't animate well at all. Disable them during animations and turn them back on once the animation is complete.
I haven't had freezes when animating with shadows, but performance is unacceptably jerky. In my experience, trying to animate anything that has shadows yields unacceptable results.

Make UIImageView animate after another UIImageView has finished

I am trying to animate the dealing of cards. Currently I have two cards, the dealer card (dCard1) and the player card (pCard1).
The player card should be dealt and as soon as it is dealt, the dealer card should then be dealt.
What is currently happening is the player card will animate properly when I press the deal button, but instead of waiting till the player card is done animating, the dealer card just appears at the dealerTo location. It does not animate. I am new to doing animations any help would be greatly appreciated.
-(void) animateHandsDealt: (Hand*) pHand:(Hand*) dHand{
pCard1= [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
dCard1= [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
CGFloat start = 0;
CGFloat duration = 1;
CGPoint playerTo = CGPointMake(120, 300);
CGPoint dealerTo = CGPointMake(120, 70);
pCard1.image = [UIImage imageNamed:[[pHand.cardArr objectAtIndex:0 ] imagePath]];
[self.view addSubview: pCard1];
[playerImages addObject:pCard1];
CABasicAnimation *move = [CABasicAnimation animationWithKeyPath:#"position" ];
move.fillMode = kCAFillModeBoth;
move.beginTime = start;
[move setToValue:[NSValue valueWithCGPoint:playerTo]];
[move setDuration:duration];
move.removedOnCompletion = FALSE;
[[pCard1 layer] addAnimation:move forKey:#"position"];
dCard1.image = [UIImage imageNamed:#"back-red-75-1.png"];
[self.view addSubview: dCard1];
CABasicAnimation *move2 = [CABasicAnimation animationWithKeyPath:#"position" ];
[move2 setToValue:[NSValue valueWithCGPoint:dealerTo]];
move2.beginTime = start + duration;
[move2 setDuration:duration];
move2.fillMode = kCAFillModeBoth;
move2.removedOnCompletion = FALSE;
[[dCard1 layer] addAnimation:move2 forKey:#"position"];
}
If you're just animating the frame of the view, you can use the much simpler UIView animateWithDuration... methods.
You chain them by using the animateWithDuration:animations:completion: version. You can specify the second animation in the completion: block of the first animation.
http://developer.apple.com/library/ios/#documentation/uikit/reference/uiview_class/UIView/UIView.html#//apple_ref/doc/uid/TP40006816-CH3-SW109
I don't have access to your classes (of course), but I think this is pretty close to "drop-in-ready" code. What do you think?
- (void)deal {
// Of course, you'd replace these NSString objects with actual card objects...
NSArray *array = [NSArray arrayWithObjects:#"pCard1", #"dCard1", #"pCard2", #"dCard2", #"pCard3", #"dCard3", nil];
[self performSelectorOnMainThread:#selector(animateDealWithArray:) withObject:array waitUntilDone:YES];
}
- (void)animateDealWithArray:(NSArray *)array {
// constants that won't change call to call
static CGFloat duration = 1;
static CGSize cardSize = {75, 110};
static CGPoint playerTo = {120, 300};
static CGPoint dealerTo = {120, 70};
// Seems like you want the card to start at the top of the "deck" whether it's a player card or not
UIImageView *card = [[UIImageView alloc] initWithFrame:CGRectMake(125, 0, 75, 110)];
// Figure out if the first object (the one we're currently using) is a player or dealer card
if ([[array objectAtIndex:0] isKindOfClass:[PlayerCard Class]]) {
// Player card, so show the "face"
[card setImage:[UIImage imageNamed:[(PlayerCard *)[array objectAtIndex:0] imagePath]]];
[UIView animateWithDuration:duration
animations:^{
[card setFrame:CGRectMake(playerTo.x, playerTo.y, cardSize.width, cardSize.height)];
}
completion:^(BOOL finished) {
[self dealNext:array];
}
];
} else {
// Dealer card, so show the back
[card setImage:[UIImage imageNamed:#"back-red-75-1.png"]];
[UIView animateWithDuration:duration
animations:^{
[card setFrame:CGRectMake(dealerTo.x, dealerTo.y, cardSize.width, cardSize.height)];
}
completion:^(BOOL finished) {
[self dealNext:array];
}
];
}
}
- (void)dealNext:(NSArray *)array {
int count = [array count];
if (count > 1) {
NSArray *newArray = [array subarrayWithRange:NSMakeRange(1, count - 1)];
[self animateDealWithArray:newArray];
}
}

Resources