Quick series of [UIView transitionWithView...] not going smoothly - ios

I've got a view which is a 4x4 grid of images, and am trying to achieve the effect of every square, going from top to bottom and left to right, flipping over to reveal a new image. Should be a bit of a "ripple" effect, so the top left square starts flipping immediately, the [0,1] square flips after 0.05 seconds, [0,2] after 0.10 seconds, and so on, so that after (0.05*15) seconds the final square starts to flip.
My implementation is this: Each grid square is a UIView subclass, DMGridSquareView, which contains a UIImageView, and the following method:
- (void)flipToNewImage:(UIImage*)img {
AATileView *mySelf = self;
[UIView transitionWithView:self.imageView
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
self.imageView.image = img;
} completion:^(BOOL finished) {
NSLog(#"Finished flip");
}];
}
Then, in order to trigger the animation of all the grid squares, within the DMGridView, I call this method:
- (void)flipAllSquares:(NSArray*)newImages {
int numTiles = self.numColumns * self.numRows;
for (int idx=0; idx<numTiles; idx++) {
UIImage *newImg = [newImages objectAtIndex:idx];
[[self.gridSquareViews objectAtIndex:idx] performSelector:#selector(flipToNewImage:) withObject:newImg afterDelay:(0.05*idx)];
}
}
When running on the simulator, this works beautifully. But when running on my iPhone 5, it has an unexpected (to me, at least) behavior. The animations of the individual grid squares are nice and smooth, and at the correct speed, but the start times are off. They happen in staggered "chunks". So the first 2 squares might flip immediately, then the next 4 squares will simultaneously flip after a moment, then the next 3 flip after another moment, and so on. It's not consistent, but it's always in chunks like this.
Any idea how to fix this?
Thanks.

EDIT 1:
Sorry, it looks like I misunderstood your question. You do not want them to flip one after another, you would like each view to begin flipping very soon after the previous view has begun flipping.
Have you tried calling the method recursively from the completion block? This way you know the method is only being called after the previous image has completed the flip animation.
NSInteger imageFlipIndex = 0;
- (void)flipToNewImage:(UIImage*)img {
AATileView *mySelf = self;
[UIView transitionWithView:self.imageView
duration:0.4
options:UIViewAnimationOptionTransitionFlipFromLeft animations:^{
self.imageView.image = img;
} completion:^(BOOL finished) {
imageFlipIndex++;
if (imageFlipIndex < newImages.count) {
UIImage *newImg = [newImages objectAtIndex:idx];
[self flipToNewImage:newImg];
} else {
imageFlipIndex = 0;
}
NSLog(#"Finished flip");
}];
}
What happens if you try:
imageFlipIndex++;
if (imageFlipIndex < newImages.count) {
UIImage *newImg = [newImages objectAtIndex:idx];
[[self.gridSquareViews objectAtIndex:imageFlipIndex] performSelector:#selector(flipToNewImage:) withObject:newImg afterDelay:(0.05*idx)];
} else {
imageFlipIndex = 0;
}
inside the animations block?

Related

iOS building custom rating control issue

I know there's lots of 3rd party solutions out there, but I've decided to build one myself to be in full control of the animations and to learn something new.
It seems like most of the time it is made through subclassing UIControl and tracking touches, but I've used a different approach. I create system buttons with images. This way I get a nice highlight animation on a press for free. Anyway, it works quite nice until you press on it really fast. Here's a gif of what's happening.
Sometimes, if you do it really fast a full star gets stuck. I'll try to explain what's happening behind this animation. Since full and empty stars have different color, I change tint color of the button each time i becomes full or empty. The animation you see is a separate UIImageView that is added on top of the button right before the beginning of the animation block and is removed in the completion block. I believe sometimes on emptying the star animation is not fired and image is not removed in the completion block. But I can't catch this bug in code.
I added some debugging code to see if the block that makes a star empty is even fired. Seems like it is, and even completion block is called.
2015-04-23 13:00:00.416 RERatingControl[24011:787202]
Touch number: 5
2015-04-23 13:00:00.416 RERatingControl[24011:787202]
removing a star at index: 4
2015-04-23 13:00:00.693 RERatingControl[24011:787202] star removed
Star indexes are zero-based, so the 5th star is supposed to be removed, but it's not.
Full project is on github. It's pretty small. I would appreciate if someone would help me fix this. Oh, and feel free to use this code if you want to :-)
UPDATE. I was suggested to post some logic code here. I didn't do it right away because I felt it would be too cumbersome.
Button action method:
- (void)buttonPressed:(id)sender {
static int touchNumber = 0;
NSLog(#"\nTouch number: %d", touchNumber);
touchNumber++;
if (self.isUpdating) {
return;
}
self.updating = YES;
NSInteger index = [self.stars indexOfObject:sender];
__block NSTimeInterval delay = 0;
[self.stars enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
REStarButton *btn = (REStarButton*)obj;
if (idx <= index) {
if (!btn.isFull) {
NSLog(#"\nadding a star at index: %ld", (long)idx);
[btn makeFullWithDelay:delay];
delay += animationDelay;
}
} else {
if (btn.isFull) {
NSLog(#"\nremoving a star at index: %ld", (long)idx);
[btn makeEmptyWithDelay:0];
}
}
}];
if ([self.delegate respondsToSelector:#selector(ratingDidChangeTo:)]) {
[self.delegate ratingDidChangeTo:index + 1];
}
self.updating = NO;
}
Empty a full star method:
- (void)makeEmptyWithDelay:(NSTimeInterval)delay {
// if (!self.isFull) {
// return;
// }
self.full = NO;
UIImageView *fullStar = [[UIImageView alloc] initWithImage:self.starImageFull];
[self addSubview:fullStar];
CGFloat xOffset = CGRectGetMidX(self.bounds) - CGRectGetMidX(fullStar.bounds);
CGFloat yOffset = CGRectGetMidY(self.bounds) - CGRectGetMidY(fullStar.bounds);
fullStar.frame = CGRectOffset(fullStar.frame, xOffset, yOffset);
[self setImage:self.starImageEmpty forState:UIControlStateNormal];
[self setTintColor:self.superview.tintColor];
[UIView animateWithDuration:animationDuration delay:delay options:UIViewAnimationOptionCurveEaseOut animations:^{
//fullStar.transform = CGAffineTransformMakeScale(0.1f, 0.1f);
fullStar.transform = CGAffineTransformMakeTranslation(0, 10);
fullStar.alpha = 0;
} completion:^(BOOL finished) {
[fullStar removeFromSuperview];
NSLog(#"star removed");
}];
Make an empty star full method:
}
- (void)makeFullWithDelay:(NSTimeInterval)delay {
// if (self.isFull) {
// return;
// }
self.full = YES;
UIImageView *fullStar = [[UIImageView alloc] initWithImage:self.starImageFull];
CGFloat xOffset = CGRectGetMidX(self.bounds) - CGRectGetMidX(fullStar.bounds);
CGFloat yOffset = CGRectGetMidY(self.bounds) - CGRectGetMidY(fullStar.bounds);
fullStar.frame = CGRectOffset(fullStar.frame, xOffset, yOffset);
fullStar.transform = CGAffineTransformMakeScale(0.01f, 0.01f);
[self addSubview:fullStar];
[UIView animateKeyframesWithDuration:animationDuration delay:delay options:0 animations:^{
CGFloat ratio = 0.35;
[UIView addKeyframeWithRelativeStartTime:0 relativeDuration:ratio animations:^{
fullStar.transform = CGAffineTransformMakeScale(animationScaleRatio, animationScaleRatio);
}];
[UIView addKeyframeWithRelativeStartTime:ratio relativeDuration:1 - ratio animations:^{
fullStar.transform = CGAffineTransformIdentity;
}];
} completion:^(BOOL finished) {
[self setImage:self.starImageFull forState:UIControlStateNormal];
[self setTintColor:self.fullTintColor];
[fullStar removeFromSuperview];
}];
}
Ah, I've managed to find a reason behind my bug and it works perfect now!
I used View Debugging to see if it's an UIImageView from animation is stuck on the button or the image of button itself is not set properly. I was surprised to see it's the button image that causing the issue. And then I looked through code again and realised I was changing the button image in the completion block after delay with no regard to if it's state has already changed. A simple fix to makeFullWithDelay: solved my issue.
} completion:^(BOOL finished) {
if (self.isFull) {
[self setImage:self.starImageFull forState:UIControlStateNormal];
[self setTintColor:self.fullTintColor];
}
[fullStar removeFromSuperview];
}];
I used whole evening trying to find the answer yesterday, and after I posted the question answer came by itself. Telling people about your problem really helps to go through your logic with more attention I guess. Thanks to everyone :-)
Should I delete the post or it might help someone in the future?

UIView Animation not working - iOS

I am trying to animate a simple UIImageView, but it is not working. Here is my code:
In my header file, I have declared "light_bulb" as a UIImageView.
NSMutableArray *bulb_off_images = [[NSMutableArray alloc] init];
for (int loop = 0; loop < 11; loop++) {
if (loop >= 10) {
[bulb_off_images addObject:[UIImage imageNamed:[NSString stringWithFormat:#"bulb_off-%d.png", loop]]];
}
else {
[bulb_off_images addObject:[UIImage imageNamed:[NSString stringWithFormat:#"bulb_off-0%d.png", loop]]];
}
}
[UIView animateWithDuration:0.5 delay:2.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
light_bulb.animationImages = bulb_off_images;
} completion:^(BOOL finished){
if (finished) {
NSLog(#"Finished !!!!!");
}
}];
The code seems to run and the completion block is called but the animation does not happen.....
**Update 1 **
I tried doing the animation this way, but I need a way of stopping the animation from repeating itself. How do I know when to call "stopAnimating". Here is my code:
light_bulb.animationImages = bulb_off_images;
light_bulb.animationDuration = 0.5;
light_bulb.animationRepeatCount = 0;
[light_bulb startAnimating];
What am I doing wrong?
In regards to your original question, you're over-complicating your solution. UIImageView has animation methods built-in. It looks like you're trying to use them in conjunction with UIView's animation features, which won't work exactly the way you're planning. Review the docs for each, and decide which classes' animation features you would like to use.
(For images, UIImageView's animation methods seem to be the preferred way to go.)
As for your second question: in the documentation for UIImageView, the notes for animationRepeatCount state:
The default value is 0, which specifies to repeat the animation indefinitely.
Setting your animationRepeatCount to 1 should play the animation only once.

Animate view repeatedly and continuously

I'm simulating some effects, like leafs falling, snow and rain.
My first call was to use CAEmitterLayer, that works very well, but i need to render the layer in context to save as an image, and that seens impossible or, at least, very complicated.
So i am working with some view animations. I need to continuously animate my imageViews so it can fall through screen and reappear on top of it, each one with diffenrent speed.
So i relocate each imageView on screen with animateWithDuration and when animation is done i call the method that do that recursively from completion block so the imageview can make it's way to the end of screen. Once an imageView reachs the end of screen i relocate it on top.
The problem is when animation is over my imageView stops a little bit until the completion block calls the method recursively, i want it to be continuously.
My code generates random position for each imageView, and my animation duration is very short so i can always update imageView's location in case the user tap the button to save the view as an image.
Anyway, here is my code:
- (void)effectOnObject:(UIImageView *)object{
NSInteger initialX;
NSInteger initialy;
CGFloat duration;
//the object have reached the end of screen
if (object.frame.origin.y >= self.frame.size.height) {
//generate random position to relocate the object on x axis
initialX = arc4random() % 321;
//generate random position to relocate the object on y axis (little bit above top of screen)
initialy = ((NSInteger)-object.frame.size.height) - arc4random() % 11;
//duration 0 so the object can be replaced without animation
duration = 0.0;
}
else{
initialX = object.frame.origin.x;
//this change the speed of object
initialy = object.frame.origin.y + 10
//setted duration to short time
duration = 0.01;
}
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
[object setFrame:CGRectMake(initialX, initialy, object.frame.size.width, object.frame.size.height)];
}
completion:^(BOOL completed){
//recursively call
[self effectOnObject:object];
}];
}
- (void)startEffect{
//initiate effect on each imageView
for (UIImageView *object in self.subviews) {
[self effectOnObject:object];
}
}
How can i make this animation repeatedly and continuously, without the gap when the method is called recursively from completion block and the animation restarts?
You don't need to do the animation with 0 duration when the object reaches the end of the screen. Just test for this in your completion block and relocate the view before calling recursively. Also, a duration of 0.01 seems a bit extreme (100 frames per second). Perhaps a more reasonable value like 0.1 would help.
Something like this:
- (void)effectOnObject:(UIImageView *)object{
//setted duration to short time
CGFloat duration = 0.1;
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionAllowAnimatedContent
animations:^{
NSInteger initialX = object.frame.origin.x;
//this change the speed of object
NSInteger initialy = object.frame.origin.y + 10;
[object setFrame:CGRectMake(initialX, initialy, object.frame.size.width, object.frame.size.height)];
}
completion:^(BOOL completed){
NSInteger initialX = arc4random() % 321;
//generate random position to relocate the object on y axis (little bit above top of screen)
NSInteger initialy = ((NSInteger)-object.frame.size.height) - arc4random() % 11;
[object setFrame:CGRectMake(initialX, initialy, object.frame.size.width, object.frame.size.height)];
//recursively call
[self effectOnObject:object];
}];
}
From what I read briefly you should check out CAReplicatorLayer. It is perfect for repeating stuff! Only issue I can think of is that the transform and values have to be incremental...

How i could to reload a imageView with a constant chaged setAlpha?

I want to make a effect similar to disolvense at the inverse changing the alpha parameter with a for, but the image doesn't change beetwen 0 and 1 only change when the alpha become 1.
PD. in my code the image is with setAlpha = 0
Here is the code of the method:
-(void)splash:(UIImageView *)img{
for (double i=0.0; i<=1.0; i=i+0.1) {
for (int x=0;x<=10000;x++){
for (int h=0;h<=10000;h++){
}
}
NSLog(#"%g",i);
[img setAlpha:i];
[img reloadInputViews];
}
}
Why not use a basic view animation to fade in the image view:
- (void)splash:(UIImageView *)img {
img.alpha = 0.0;
[UIView animateWithDuration:1.0 animations:^{
img.alpha = 1.0;
}];
}
Set the duration to whatever desired result you want (in seconds).
If you don't want to use UIView animation (as suggested above and you probably should use that way), then you can try a recursive loop like this:
- (void)performSplashAlpha:(UIImageView *)img
{
img.alpha += 0.1;
if(img.alpha < 1)
[self performSelector:#selector(performSplashAlpha:) withObject:img afterDelay:0.1]; //Or whatever timing you want in between alphas
}

dynamically change images in an animation IOS

I have a requirement to animate images . I have a large number of images and this needs to be played as an video. In between playing sometimes i need to change some images as they will be updated at server. so playing should automatically update this new images .
I have tried using UIImageView. There we cannot control the animation.
I then tried CAKeyframeAnimation supplying image array to values property. I could play and pause the animation. But here also i cannot dynamically change the image while playing.
Can anyone help me in solving this problem.
Thanks
mia
The animation system on UIImageView is very limited. I would suggest that you make your own implementation with say 2 image view.
You would than change the image in one imageview and the fade in and out using UIView animateWithDuration
I have written a method for you. Please note: I have not tested it.
It assumes you have your frames in a array called 'frames' and have two UIIMageView placed on top of each other called 'imgv1' and 'imgv2'
-(void)performAnimationOfFrameAtIndex:(NSNumber*)indexNum
{
int index = [indexNum intValue];
if(index >= frames.count)
{
return;
}
UIImage *img = [frames objectAtIndex:index];
UIImageView *imgIn;
UIImageView *imgOut;
if(index % 2 == 0)
{
imgIn = imgv1;
imgOut = imgv2;
}
else {
imgIn = imgv2;
imgOut = imgv1;
}
imgIn.image = img;
[self.view sendSubviewToBack:imgIn];
[UIView animateWithDuration:0.1 animations:^{
imgIn.alpha = 1;
imgOut.alpha = 0;
} completion:^(BOOL finished) {
[self performSelectorOnMainThread:#selector(performAnimationOfFrameAtIndex:) withObject:[NSNumber numberWithInt:index+1] waitUntilDone:NO];
}];
}
Use two UIImageViews and swap them.
If you have a weak reference on the "animation" UIView, be sure to check if animation has finished in the completion block. Otherwise you may experience performance issues when you navigate to another view, recursive calls to animation method will continue!
- (void)animateImagesAtIndex:(NSNumber *)imageIdx {
// Do something here
[UIView animateWithDuration:1 animations:^{
// Do swap
}
completion:^(BOOL finished) {
if (finished) {
[self animateImagesAtIndex:imageIdx];
}
}];
}
UIImageView supports animations for a series of images. You only have to set the properties animationImages with an array of the images, and call the methods startAnimating and stopAnimating.

Resources