I have an interface which contains several UIImageView components on a view and i would like to update the animation images in run-time.
The main idea is to change the animation images into a thread with a delay of 2 seconds. The code below works fine. However, when i introduce a sleep time (with the commented NSThread sleepForTime); it no longer works. I am sure there is a logical explanation but i cannot identify it.
It's important to note that the animation is already running with another set of animated images.
Any help or tip is more than welcome :)
dispatch_async(allowToTouchThread, ^{
//[NSThread sleepForTimeInterval:2.0];
int randomnReward = 0;
WizzReward* currentReward = [rewardListForPages objectAtIndex:randomnReward];
WizzAnimalModel* animalModel = [gameLevelManager getAnimalModelByCategoryId: [currentReward getAnimalId]];
NSMutableArray* expressionsForAnimals = [animalModel getArrayForClosedEyesFaceExpression];
float animationDuration = [animalModel getStandardRewardAnimationDuration];
pageContentViewController.imagesArrayFileLevel1 = expressionsForAnimals;
pageContentViewController.animationLevel1Duration = animationDuration;
}
});
What about replacing your dispatch_async to a dispatch_after and set a delay of 2 seconds:
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
int randomnReward = 0;
WizzReward* currentReward = [rewardListForPages objectAtIndex:randomnReward];
WizzAnimalModel* animalModel = [gameLevelManager getAnimalModelByCategoryId: [currentReward getAnimalId]];
NSMutableArray* expressionsForAnimals = [animalModel getArrayForClosedEyesFaceExpression];
float animationDuration = [animalModel getStandardRewardAnimationDuration];
pageContentViewController.imagesArrayFileLevel1 = expressionsForAnimals;
pageContentViewController.animationLevel1Duration = animationDuration;
});
Changing the animated images without any timer, could be done directly by modifying the array of images.
However, changing the same animated images with a timer, needed 3 steps:
first the modification of the image arrays
second the actual re-association of the UIView AnimationImages to the modified array of images;
and finally startAnimating the UIView eventhough it hasn't been stopped beforehand
The source code below now works fine with a timer:
WizzReward* currentReward = [rewardListForPages objectAtIndex:0];
WizzAnimalModel* animalModel = [gameLevelManager getAnimalModelByCategoryId: [currentReward getAnimalId]];
NSMutableArray* expressionsForAnimals = [animalModel getArrayForClosedEyesFaceExpression];
pageContentViewController.imagesArrayFileLevel1 = expressionsForAnimals;
pageContentViewController.reward1CurrentPage.animationImages = pageContentViewController.imagesArrayFileLevel1;
[pageContentViewController.reward1CurrentPage startAnimating];
I did find neither "time vs no-timer difference"information in the literature nor actual reason why it should be done like that; but it works every time. Hope it helps anyone who runs into the same problem.
Related
I am developing an IOS 10.x application using UICollectionView and would like to change the image of specific cells, at regular interval.
The code below shows the current implementation. Even though it should change the cell background image every half of second, it changes the images immediately disregarding the NSThread SleepAt interval of 0.5 seconds.
I suspect something about the main thread handling or the ReloadItem method but hasn't reached a clear conclusion. Any insight is very welcome! Thank you.
NSNumber* originalCardSelected;
int position;
for (int i = 0; i < [opponentOriginalCardArray count]; i++) {
originalCardSelected = [opponentOriginalCardArray objectAtIndex:i];
position = [self convertLevelDataPositionToCellViewPosition:[originalCardSelected intValue]];
NSMutableArray *tmpBackgroundAssets = [self getNewAssetBackgroundBasedOnBackgroundType:playbackBackground Index:position];
self.backgroundAssets = tmpBackgroundAssets;
dispatch_async(dispatch_get_main_queue(), ^{
[collectionViewRoot reloadItemsAtIndexPaths:[collectionViewRoot indexPathsForVisibleItems]];
});
[NSThread sleepForTimeInterval:0.5];
}
You should use performBatchUpdates(_:completion:) methods and add your for loop inside it.
Remember to not keep strong references inside the block to avoid retain cycles.
EDIT:
and in the completion block, you can check if finished and add your NSThread methods
I want to create an effect so that my collectionviewcell (which has one imageview as IBOutlet) will load its image with a random delay. So for example, cell #1 will have its image shown in 2 seconds, while #2 will take 1 second, cell #3 will take 4 seconds ... etc.
How would I do this? I heard about using NSOperationQueue at cellForRowAtIndexPath but not sure how to implement.
If your images are already available, you could try giving a random delay in your cellForItemAtIndexPath method:
UIImage *theImage = ...; //get your image
int maxDelay = 4;
int delayInSeconds = arc4random() % maxDelay;
cell.imageView.image = theImage;
cell.imageView.alpha = 0;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
cell.imageView.alpha = 1;
});
If your image is not available in memory, and it needs to be loaded, you may want to look at this post on how to load the image asynchronously:
You could have a method on the cell's subclass for setting the image, then within that method call another method with a random delay to actually set the UIImageView's image. Something like this:
- (void)delayedSetBackgroundImage:(UIImage *)image
{
[self performSelector:#selector(setBackgroundImage:) withObject:image afterDelay:(arc4random() % 5)];
}
- (void)setBackgroundImage:(UIImage *)image
{
_myImageView.image = image;
}
So then in cellForRowAtIndexPath you would call delayedSetBackgroundImage: and the cell itself would take care of the rest.
I am creating an animation using the below code:
NSMutableArray *dashBoy = [NSMutableArray array];
for (int i = 1; i<= 20; i++) {
butterfly = [NSString stringWithFormat:#"5c_%d.jpg", i];
if ((image = [UIImage imageNamed:butterfly]))
[dashBoy addObject:image];
}
[stgImageView setAnimationImages:dashBoy];
[stgImageView setAnimationDuration:7.0f];
[stgImageView setAnimationRepeatCount:-1];
[stgImageView startAnimating];
My requirement is if dashBoy is 5c_10.jpg, then pause the image for about 5 sec and then resume animation and another if dashBoy is 5c_20.jpg, then pause the image again for about 5 sec and resume again. Is it possible?
You can't use variable speeds for UIImageView animations like this.
There are a couple of ways around it though.
1. Do it all yourself.
For this you would use something like an NSTimer and have it fire a method repeatedly to change the images for you. Using this you can time each image individually (you could even create a data object that holds an image and the length of time to show it and then create and array of these.
2. Manipulate the current method.
If you have 20 images and you want to show them all in 7 seconds then thats... 0.35 seconds per image. So a 5 second pause is about 14 images worth.
So instead of adding each image once you can add 1-9 once and then add 10 fourteen times. 11-19 once and then 20 fourteen times.
This will then swap like it is doing but when it reaches 10 it will swap it for another copy of the same image so it will look like it's pausing.
You will then have to increase the duration... to 17 seconds to get a similar duration for each image.
Which I'd do?
Although it sounds like a hack (because it is) I think I'd give the second method a go first. It's a lot easier to get working so if it does fail you haven't spent a long time setting it up.
The first method is a lot more work to set up but will allow for greater control of the animation.
Quick and dirty example for method 1
Create an object something like...
MyTimedImage
------------
UIImage *image
CGFloat duration
So for instance...
// probably want this as a property
NSMutableArray *timedImages = [NSMutableArray array];
MyTimedImage *timedImage = [MyTimedImage new];
timedImage.image = [UIImage imageNamed:[NSString stringWithFormat:#"5c_%d.jpg", i]];
timedImage.duration = 0.4;
[timedImages addObject:timedImage];
Then you want a way of displaying them...
//set up properties something like this...
#property (nonatomic, assign) NSInteger currentIndex;
#property (nonatomic, assign) BOOL paused;
#property (nonatomic, assign) NSTimer *imageTimer;
- (void)displayNextImage
{
if (self.paused) {
return;
}
NSInteger nextIndex = self.currentIndex + 1;
if (nextIndex == [self.timedImages count]) {
nextIndex = 0;
}
MyTimedImage *nextImage = self.timedImages[nextIndex];
self.currentIndex = nextIndex;
self.imageView.image = nextImage.image;
self.imageTimer = [NSTimer scheduledTimerWithInterval:nextImage.duration target:self selector:#selector(displayNextImage) userInfo:nil repeats:NO];
}
Using the properties that you've exposed you can pause the image view on the current image from a button press (for example).
To start the process just run [self displayNextImage]; you can start anywhere in the loop of images too.
using UIImageView animation, this is not possible, you may need to create your own animation logic like loading all UIImages in array and through timer, switch to next animation frame and when you desire to pause, invalidate the timer.
I have an iOS App that loads a set of images and a duration into an array then I have a timer that displays the images like so:
- (void)fireTimer:(NSTimer *)theTimer
{
self.image = [frames objectAtIndex:currentFrame];
NSTimeInterval time = [[durations objectAtIndex:currentFrame] floatValue];
self.timer = [NSTimer scheduledTimerWithTimeInterval:time target:self selector:#selector(fireTimer:) userInfo:nil repeats:NO];
currentFrame++;
if (currentFrame >= [frames count])
{
currentFrame = 0;
[timer invalidate];
}
}
To start the animation I call fireTimer and cycle through the images then when all the images are processed I call [timer invalidate] to stop the animation. I cannot use startAnimation because I need different durations for each image.
Right know I am NOT doing this on a background thread so the animation is choppy because other processing is happening whilst the image animates.
What is the best way to animate this in the background? Can I simply put this call to fireTimer in a block?
I know this may not be the best way to animate an image on iOS, but I do not want to do a lot of refactoring on the code right now.
Thanks for any suggestions or examples for a better solution!!!
I would suggest you use UIImageView to animate your images : it is made to do this task. If, like you say, some images needs to remains longer then others, then you can just display them many times. Let's say that image2 needs to be displayed twice longer than image1 and image3, just initialize the array animationImages on your UIImageView like : #[image1, image2, image2, image3].
Instead of using a timer, you could use performSelector: withObject: afterDelay:
-(void)loopBackground:(int)index
{
if(index < [self.durations count]{
self.image = [self.frames objectAtIndex:index];
[self performSelector:#selector(loopBackground:) withObject:index++ afterDelay:[[self.durations objectAtIndex:index] floatValue]];
}
}
_datePicker.frame = CGRectMake(70, self.view.frame.size.height -150,250,100);
I'm using this code under viewDidLoad to change the size of a UIDatePicker. It works great but gives me another 3 seconds on load time. I tried using this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
_datePicker.frame = CGRectMake(70, self.view.frame.size.height -150,250,1);
});
but I lose all the animation between segues if I do, don't know why. I thought about using another thread in viewDidLoad but I don't know how to do it or if it's possible.
Thanks