need help in start with GCD one method - many calls - objective-c

i want to learn GCD on my own app-project and i've got problem with it. i think is easy but i don't know how solve it. so i've got 2 methods:
- (void)viewDidLoad
{
[super viewDidLoad];
queue = dispatch_queue_create("com.fe.effect.load", NULL);
__block UIImage *th;
dispatch_sync(queue, ^{
th = [self doThumbWithImage:thumbnail andTag:1];
UIButton *btn1 = [self buttonWithFrame:CGRectMake(0, 0, 200, 100) andBackgroundImage:th andSelector:#selector(doEffect:) andTag:1];
[self.view addSubview:btn1];
});
dispatch_sync(queue, ^{
th = [self doThumbWithImage:thumbnail andTag:2];
UIButton *btn2 = [self buttonWithFrame:CGRectMake(0, 100, 200, 100) andBackgroundImage:th andSelector:#selector(doEffect:) andTag:2];
[self.view addSubview:btn2];
});
}
and 2 method:
-(UIImage *)doThumbWithImage:(UIImage *)_img andTag:(int)_tag {
ImageProcessing *ip = [[ImageProcessing alloc] initWithImage:_img andTag:_tag];
[ip doImageProcessing];
_img = ip.image;
ip = nil;
return _img;
}
And in doThumbWithImage:andTag: i've got ImageProcessing class where i do something with my thumbnail object.
when i use dispatch_sync, time for this operation is the same as without GCD. When i use dispatch_async, i can't see thumbanils of buttons. I know something is wrong, but i don't know what.
How can i repair this?
thank you for help.

You probably need to do the addSubview in the main thread. You can nest the dispatch_async calls to handle that.

Related

Objective-C: Activity Indicator during method call

I have a method which checks for bluetooth devices ([service scanForSensBoxes]).
The method scanForSensBoxes has a timeout of 5 seconds. During this time I would like to animate an Activity Indicator.
There for I start the spinning in beginn of the attached method, and stop it after the call of "scanForSensBoxes".
Apparently this doesn't work. The result of the code I have at the moment is, that the start and stop animation of the spinner is only processed when the "buttonScanPressed" method is completed instead of during the processing of it. Means the spinner never does animate.
How do I have to change the approach to that, so the spinner will animate during the call of "buttonScanPressed"?
Thanks for any help.
- (IBAction)buttonScanPressed:(id)sender {
[_waitingSpinner startAnimating];
SensBoxServiceLib *service = [[SensBoxServiceLib alloc]init];
NSString *tmp=[NSString stringWithFormat:#"%d",[service scanForSensBoxes]];
[_waitingSpinner stopAnimating];
_sensBoxCount.text=tmp;
}
If scanForSensBoxes is blocking the main thread, the activity indicator won't animate. Assuming this is this case you need to perform the blocking activities within that method on a background queue.
-(IBAction)buttonScanPressed:(id)sender {
// animate activity indicator
[_waitingSpinner startAnimating];
// perform blocking activity in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) {
SensBoxServiceLib *service = [[SensBoxServiceLib alloc] init];
NSString *tmp = [NSString stringWithFormat:#"%d", [service scanForSensBoxes]];
// perform UI updates on main thread
dispatch_async(dispatch_get_main_queue(), {
[_waitingSpinner stopAnimating];
});
});
}
You need to hide it
- (IBAction)buttonScanPressed:(id)sender {
_waitingSpinner.hidden = NO;
[_waitingSpinner startAnimating];
SensBoxServiceLib *service = [[SensBoxServiceLib alloc]init];
NSString *tmp=[NSString stringWithFormat:#"%d",[service scanForSensBoxes]];
[_waitingSpinner stopAnimating];
_waitingSpinner.hidden = YES;
_sensBoxCount.text=tmp;
}

Unable to store Image on a UIIMageView after the animation

I've have a bunch of .png files that i am supposed to animate over a period of 1.05 seconds. After the animation executes, i would like to have the last .png file show up permanently. I am using an UIIMageview IBOutlet to hold the animation. However, after the animation executes, the image just disappears and i do not see anything in on the screen.
The code for the animation is as follows:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self loadArrayForTickAnimation];
});
if (tickArray) {
self.animationView.animationImages = tickArray;
self.animationView.animationDuration = 1.05;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
[self performSelector:#selector(showFinalImage) withObject:nil afterDelay:1.2];
}
}
- (void)showFinalImage
{
[self.animationView setImage:[UIImage imageNamed:#"check_37.png"]];
}
Here, check_1.png to check_37.png are the files that needed to be animated. I store them in an array called "tickArray", and populate the array using the [self loadArrayForTickAnimation] message.And finally, i have to set the UIIMage for the animationView as check_37.png
Fixed it !!! You need to set the image for the imageview before the animation starts. SO, i would have
self.animationView.image = [UIImage imageNamed:#"check_37.png"];
if (tickArray) {
self.animationView.animationImages = tickArray;
self.animationView.animationDuration = 1.05;
self.animationView.animationRepeatCount = 1;
[self.animationView startAnimating];
}
that finally made it work !!!

How do you display images one after the other

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];
}

Wait, before iterating next loop in objective c (ios)

I want to loop through an array of images, subsequently setting them as the new image of a uiimageview, but because it's a for loop it obviously does it so fast you cannot see a difference.
I would like to add a pause of about 0.2s to the loop before it goes on to the next iteration.
Is there a way to do this?
And more importantly is there an easier way to accomplish what I want to do with some kind of inbuilt framework? thanks!
Alternative to what you want but will work better-Use this method of NSTimer with repeats as YES
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats
use some method changeImage like
-(void) changeImage:(NSTimer*)theTimer
{
//code to get the image
NSUInteger idx=[yourArrayOfImages indexOfObjectIdenticalTo:yourImageView.image];
if(idx<[yourArrayOfImages count])
yourImageView.image=[yourArrayOfImages objectAtIndex:++id];
[yourImageView setNeedsDisplay];
}
Do not invalidate the timer. You could use any way to index like use an instance variable, pass it as userInfo.
Adding a delay to your for loop won't make a difference, because redrawing won't happen until you return control to the main event loop. Set up a timer to send you a message every so often to advance to the next image you want to show.
More context would be useful, but it sounds like you should look into NSTimer.
Below is the logic is how the Timer to be call a function recursively in a delay, and once the iteration of the array is done , disable the timer
NSArray *arrayOfImages;
timer = [NSTimer scheduledTimerWithTimeInterval:0.2 target:self selector:#selector(timerFired:) userInfo:nil repeats:NO];
int i = 0
int count = [arrayOfImages count];
- (UIIMage *)timerFired:(NSTimer *)aTimer{
if (i == count)
{
timer == nil;
return nil;
}
i++;
return [arrayOfImages objectAtIndex:i];
}
You'd better use UIImageView property - animationImages.
#property(nonatomic,copy) NSArray *animationImages;
For example you have an UIImageView* animationImageView :
// pass NSArray of UIImage's in the order of desired appearance
animationImageView.animationImages = [NSArray arrayWithObjects:
[UIImage imageNamed:#"image1"],
[UIImage imageNamed:#"image2"],
[UIImage imageNamed:#"image3"],
[UIImage imageNamed:#"image4"],
[UIImage imageNamed:#"image5"], nil];
animationImageView.animationDuration = 1.5; // duration (seconds) of ONE animation cycle
animationImageView.animationRepeatCount = 3; // number of repeats (cycles)
animationImageView startAnimating];
In case you want to start some other action when animation finishes, use dispatch_after :
dispatch_time_t animTime = dispatch_time(DISPATCH_TIME_NOW, animationImageView.animationDuration * NSEC_PER_SEC);
dispatch_after (popTime, dispatch_get_main_queue(), ^(void){
// do your stuff
[self animationDidFinish];
});

Animation using array of images in sequence

I have an array of images which I want to animate by playing these images one after the other in a sequence. I want to repeat the whole loop several times. I am developing a game for iPad. Suggest to me a method to achieve this functionality in Objective-C with the Cocoa framework.
NSArray *animationArray = [NSArray arrayWithObjects:
[UIImage imageNamed:#"images.jpg"],
[UIImage imageNamed:#"images1.jpg"],
[UIImage imageNamed:#"images5.jpg"],
[UIImage imageNamed:#"index3.jpg"],
nil];
UIImageView *animationView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0,320, 460)];
animationView.backgroundColor = [UIColor purpleColor];
animationView.animationImages = animationArray;
animationView.animationDuration = 1.5;
animationView.animationRepeatCount = 0;
[animationView startAnimating];
[self.view addSubview:animationView];
[animationView release];
add your own images in the array.repeat Count 0 means infinite loop.You can give your own number also.
There are at least 3 ways to animate an array of images through a UIImageView. I'm adding 3 links to download sample code for the 3 possibilities.
The first one is the one that everyone knows. The other ones are less known.
- UIImageView.animationImages
Example Link
The problem of this one is that do not have Delegate to tell us in which moment the animation is finished. So, we can have problems if we want to display something after the animation.
In the same way, there is no possibility to kept the last image from the animation in the UIImageView automatically. If we combine both problems we can have a gap at the end of the animation if we want to kept the last frame on screen.
self.imageView.animationImages = self.imagesArray; // the array with the images
self.imageView.animationDuration = kAnimationDuration; // static const with your value
self.imageView.animationRepeatCount = 1;
[self.imageView startAnimating];
- CAKeyframeAnimation
Example Link
This way to animate works through CAAnimation. It have an easy delegate to use and we can know when the animation finish.
This is probably the best way to animate an array of images.
- (void)animateImages
{
CAKeyframeAnimation *keyframeAnimation = [CAKeyframeAnimation animationWithKeyPath:#"contents"];
keyframeAnimation.values = self.imagesArray;
keyframeAnimation.repeatCount = 1.0f;
keyframeAnimation.duration = kAnimationDuration; // static const with your value
keyframeAnimation.delegate = self;
// keyframeAnimation.removedOnCompletion = YES;
keyframeAnimation.removedOnCompletion = NO;
keyframeAnimation.fillMode = kCAFillModeForwards;
CALayer *layer = self.animationImageView.layer;
[layer addAnimation:keyframeAnimation
forKey:#"girlAnimation"];
}
Delegate:
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag)
{
// your code
}
}
- CADisplayLink
Example Link
A CADisplayLink object is a timer object that allows your application to synchronize its drawing to the refresh rate of the display.
This way to do it is really interesting and opens a lot of possibilities to manipulate what are we showing in screen.
DisplayLink getter:
- (CADisplayLink *)displayLink
{
if (!_displayLink)
{
_displayLink = [CADisplayLink displayLinkWithTarget:self
selector:#selector(linkProgress)];
}
return _displayLink;
}
Methods:
- (void)animateImages
{
self.displayLink.frameInterval = 5;
self.frameNumber = 0;
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
- (void)linkProgress
{
if (self.frameNumber > 16)
{
[self.displayLink invalidate];
self.displayLink = nil;
self.animationImageView.image = [UIImage imageNamed:#"lastImageName"];
self.imagesArray = nil;
return;
}
self.animationImageView.image = self.imagesArray[self.frameNumber++];
self.frameNumber++;
}
GENERAL PROBLEM:
Even though we have this 3 possibilities, if your animation is with a lot of big images, consider using a video instead. The usage of memory will decrease a lot.
A General problem you will face doing this is in the moment of the allocation of the images.
If you use [UIImage imageNamed:#"imageName"] you will have cahe problems.
From Apple:
This method looks in the system caches for an image object with the specified name and returns that object if it exists. If a matching image object is not already in the cache, this method locates and loads the image data from disk or asset catelog, and then returns the resulting object. You can not assume that this method is thread safe.
So, imageNamed: stores the image in a private Cache.
- The first problem is that you can not take control of the cache size.
- The second problem is that the cache did not get cleaned in time and if you are allocating a lot of images with imageNamed:, your app, probably, will crash.
SOLUTION:
Allocate images directly from Bundle:
NSString *imageName = [NSString stringWithFormat:#"imageName.png"];
NSString *path = [[NSBundle mainBundle] pathForResource:imageName
// Allocating images with imageWithContentsOfFile makes images to do not cache.
UIImage *image = [UIImage imageWithContentsOfFile:path];
Small problem:
Images in Images.xcassets get never allocated. So, move your images outside Images.xcassets to allocate directly from Bundle.
See the animationImages property of UIImageView. It’s hard to say if it fits your needs as you don’t give us details, but it’s a good start.
I have added a swift 3.0 extension for this
extension UIImageView {
func animate(images: [UIImage], index: Int = 0, completionHandler: (() -> Void)?) {
UIView.transition(with: self, duration: 0.5, options: .transitionCrossDissolve, animations: {
self.image = images[index]
}, completion: { value in
let idx = index == images.count-1 ? 0 : index+1
if idx == 0 {
completionHandler!()
} else {
self.animate(images: images, index: idx, completionHandler: completionHandler)
}
})
}
}
Best solution for me use CADisplayLink. UIImageView doesn't have completion block and you can't catch steps of animation. In my task i must changing background of view with image sequencer step by step. So CADisplayLink allows you handling steps and finishing animation. If we talk about usage of memory, i think best solution load images from bundle and delete array after finishing
ImageSequencer.h
typedef void (^Block)(void);
#protocol ImageSequencerDelegate;
#interface QSImageSequencer : UIImageView
#property (nonatomic, weak) id <ImageSequencerDelegate> delegate;
- (void)startAnimatingWithCompletionBlock:(Block)block;
#end
#protocol ImageSequencerDelegate <NSObject>
#optional
- (void)animationDidStart;
- (void)animationDidStop;
- (void)didChangeImage:(UIImage *)image;
#end
ImageSequencer.m
- (instancetype)init {
if (self = [super init]) {
_imagesArray = [NSMutableArray array];
self.image = [self.imagesArray firstObject];
}
return self;
}
#pragma mark - Animation
- (void)startAnimating {
[self startAnimatingWithCompletionBlock:nil];
}
- (void)startAnimatingWithCompletionBlock:(Block)block {
self.frameNumber = 0;
[self setSuccessBlock:block];
self.displayLink.frameInterval = 5;
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStart)]) {
[self.delegate animationDidStart];
}
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop]
forMode:NSRunLoopCommonModes];
}
-(void)stopAnimating {
self.image = [self.imagesArray lastObject];
[self.displayLink invalidate];
[self setDisplayLink:nil];
Block block_ = [self successBlock];
if (block_) {
block_();
}
if (self.delegate && [self.delegate respondsToSelector:#selector(animationDidStop)]) {
[self.delegate animationDidStop];
}
[self.imagesArray removeAllObjects];
}
- (void)animationProgress {
if (self.frameNumber >= self.imagesArray.count) {
[self stopAnimating];
return;
}
if (self.delegate && [self.delegate respondsToSelector:#selector(didChangeImage:)]) {
[self.delegate didChangeImage:self.imagesArray[self.frameNumber]];
}
self.image = self.imagesArray[self.frameNumber];
self.frameNumber++;
}
#pragma mark - Getters / Setters
- (CADisplayLink *)displayLink {
if (!_displayLink){
_displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(animationProgress)];
}
return _displayLink;
}
- (NSMutableArray<UIImage *> *)imagesArray {
if (_imagesArray.count == 0) {
// get images from bundle and set to array
}
return _imagesArray;
}
#end
This is a simple and working code for animation./
-(void)move
{
[UIView animateWithDuration:5
delay:0.0
options: UIViewAnimationCurveEaseOut
animations:^{
[_imgbox setFrame:CGRectMake(100, 100, _imgbox.frame.size.width, _imgbox.frame.size.height)];
}
completion:^(BOOL finished){
NSLog(#"Done!");
}];
}