I have a timer:
[NSTimer scheduledTimerWithTimeInterval:(float)jpegInterval/1000 target:self selector:#selector(jpegDownloaderSelector:) userInfo:url repeats:YES];
jpegDownloaderSelector perform the fetching of image from url and set it at imageView as below:
-(void) jpegDownloaderSelector:(NSTimer*)timer{
[self performSelectorInBackground:#selector(jpegDownloader:) withObject:(NSString*)[timer userInfo]];
}
-(void) jpegDownloader:(NSString*)imageUrl{
dispatch_async(dispatch_get_main_queue(), ^{
imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://10.8.9.100:509/jpeg"]];
imageView.image = [UIImage imageWithData:imgData];
});
}
I have also set imageView with gestureRecognizer
[imageView addGestureRecognizer:singleTapRecognizer];
[imageView addGestureRecognizer:doubleTapRecognizer];
where the recognizer definitions are:
singleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(functoCall:)];
singleTapRecognizer.numberOfTapsRequired = 1;
singleTapRecognizer.numberOfTouchesRequired = 1;
doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(funcToCall2:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
The problem i have is, when timer starts, and i double tap imageView, double tap recognizer does not response, instead single tap responded.
If i set NO for timer repeats parameter, double tap responded. Thus, i suspect double tap is not responding due to running of background functions that is called by timer.
Anyone has suggestion that i can implement the background function and no problem with recognizing double tap.
When you do this:
dispatch_async(dispatch_get_main_queue(), ^{
You're pushing the task on to the main thread, just after a very short delay. So your image download is done on the main thread and is blocking all other interaction.
Instead, you should run the download on a background thread (dispatch_get_global_queue) and then push back to the main thread ONLY to handle the result.
-(void) jpegDownloader:(NSString*)imageUrl {
dispatch_async(dispatch_get_global_queue(), ^{
imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://10.8.9.100:509/jpeg"]];
dispatch_async(dispatch_get_main_queue(), ^{
imageView.image = [UIImage imageWithData:imgData];
});
});
}
Related
I'm trying to create a magnet reset dot for my app, when someone clicks on the reset button it performs a reset of the hardware I use, and also flashes an orange circle from orange to green....
[NSThread detachNewThreadSelector:#selector(myMethod) toTarget:self withObject:nil];
- (void)myMethod {
UIImage *image = [UIImage imageNamed: #"orangeDot.png"];
[dot setImage: image];
sleep(1);
image = [UIImage imageNamed: #"greenDot.png"];
[dot setImage: image];
}
As you can see this occurs on a thread.
Anyway, here's my issue: The orangeDot is not being shown, even though there is a 1 second sleep?
-(void) onTick {
[self battery];
}
- (void) resetMagnet:(UIButton *) sender {
[[MySlateManager sharedManager] doReset];
if(![SettingsManager shared].isVibrationDisabled)
{
[[VibrationHelper sharedInstance]singleShortVibration];
}
NSLog(#"Testttt");
[self flickerOrange];
NSTimer *t = [NSTimer scheduledTimerWithTimeInterval: 0.1
target: self
selector:#selector(onTick)
userInfo: nil repeats:NO];
}
That worked!
I have a UICollectionView displaying a bunch of images. If I don't load the images asynchronously the scrolling is very choppy and provides a poor user experience. When I load the images asynchronously the scrolling is smooth but it takes a good 5 to 10 seconds to load each image.
Why does it take so long for images to appear when loaded in the background? Here is my code for the background thread which is inside of the cellForItemAtIndexPath delegate:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]])
bg = [[UIImageView alloc] initWithImage:thumb];
else
[bg setImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
} else
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
dispatch_sync(dispatch_get_main_queue(), ^{
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
EDIT: As #geraldWilliam pointed out, I shouldn't be accessing views from the secondary thread. Here is what I have updated my code to and fixed the issue of images getting set to the wrong cell:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImageView *bg = (id)self.backgroundView;
UIImageView *selbg = (id)self.selectedBackgroundView;
if (![bg isKindOfClass:[UIImageView class]]) bg = [[UIImageView alloc] initWithImage:thumb];
if (![selbg isKindOfClass:[UIImageView class]]){
selbg = [[UIImageView alloc] initWithImage:thumb];
coloroverlay = [[UIView alloc] initWithFrame:selbg.bounds];
[coloroverlay setAutoresizingMask:UIViewAutoresizingFlexibleWidth|UIViewAutoresizingFlexibleHeight];
[selbg addSubview:coloroverlay];
}
dispatch_sync(dispatch_get_main_queue(), ^{
[bg setImage:thumb];
[selbg setImage:thumb];
[bg setContentMode:UIViewContentModeScaleAspectFill];
[bg setTag: 1];
[coloroverlay setBackgroundColor:[col colorWithAlphaComponent:0.33f]];
[selbg setContentMode:UIViewContentModeScaleAspectFill];
[self setBackgroundView:bg];
[self setSelectedBackgroundView:selbg];
});
});
Most of the code you have here is fine for the main queue. The loading of the image should be on a global queue, but the rest, especially setting the image view's image, should be on the main queue. What's going on in your code is that you're dispatching back to the main queue to set the background view but leaving the assignment of the image property in the background. So, try something like:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:myImageURL]];
dispatch_sync(dispatch_get_main_queue(), ^{
imageView.image = image;
[self setBackgroundView:imageView];
});
});
I strongly recommend watching WWDC 2012 Session 211: Building Concurrent User Interfaces on iOS, which is the best WWDC session ever. It’s full of clearly presented, practical advice.
Doing stuff with UIImageView off the main queue is worrying and should be fixed, but is probably not the cause of slowness. You haven’t showed us where thumb comes from, which is likely the slow bit.
When app launches I add UIImageViews to screen with no image. Then I load image one by one using NSThread and set it for those views. The UiImageViews remain blank until after all images have been loaded and the thread finishes. Why doesn't the image show as soon as I set it for the UIImageView, why does it wait for NSThread to end?
To add image views :
for(i = value; i < val; i++){
UIButton *newbutton = [UIButton buttonWithType:UIButtonTypeCustom];
[newbutton setBackgroundColor:[UIColor whiteColor]];
UIImageView *newback = [[UIImageView alloc] init];
newback.contentMode = UIViewContentModeScaleAspectFit;
[newback setFrame:CGRectMake(0, 0, 140, 140)];
[newbutton addSubview:newback];
height = MIN(300, newSize.height);
[newbutton setFrame:CGRectMake(currentX, currentY, width, height)];
newbutton.layer.cornerRadius = 10;
newbutton.clipsToBounds = YES;
[newbutton addTarget:self action:#selector(processButtonClick:) forControlEvents:UIControlEventTouchUpInside];
}
To add images in a thread I call a function which does following :
for(i = value; i < val; i++){
UIImage *newimage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#“http://test.com/a.jpg”, hash]]]];
UIButton *newbutton = (UIButton*)[self.subviews objectAtIndex:i];
[((UIImageView*)[newbutton.subviews objectAtIndex:0]) newimage];
}
Generally speaking, UIKit is not thread-safe. Fetch data from URL in a background thread and set the image in the main thread.
EDIT:
In your main thread do something like
dispatch_async(someDispatchQ, ^{
[self functionToGetData];
});
and inside functionToGetData do
for(i = value; i < val; i++){
NSData *data = //get Data
UIImage *newImage = //make image from data
dispatch_async(dispatch_get_main_queue(), ^{
[imageView setImage:newImage];
});
}
This will ensure that you are using a background thread to fetch data/make image and using the main thread to set the image. This also eliminates the need to use a timer to constantly poll the background thread since the background thread automatically dispatches image-setting to the main thread as soon as an image is received.
use-
import class ImageDownloader.h & .m
link-> https://github.com/psychs/imagestore/tree/master/Classes/Libraries/ImageStore
//Now use this method to download
-(void)downloadImageWithURL:(NSString *)url{
if(self.downloder)return; //If already downloading please stay away
url=[NSString stringWithFormat:#"%#%0.0fx%0.0f/%#/%#",IMAGE_ENDPOINT,self.imgViewExhibitors.frame.size.width,self.imgViewExhibitors.frame.size.height,#"fit",[ImagePath forURLString:url]];
self.downloder=[[ImageDownloader alloc] init];
self.downloder.delegate=self;
self.downloder.indicator=self.indicator;
[self.downloder downloadImageWithURL:url];
}
-(void)imageDownloaded:(UIImage *)image forIndexPath:(NSIndexPath *)indexPath{
if(image){
//Put the downloaded image in dictionary to update while scrolling
//[self.speakerData setObject:image forKey:#"image"];
self.imgViewExhibitors.image=image;
}
}
A simple fix would be
for(i = value; i < val; i++){
UIImage *newimage = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#“http://test.com/a.jpg”, hash]]]];
UIButton *newbutton = (UIButton*)[self.subviews objectAtIndex:i];
// this ensures that the UI update (setting the image) is done on the main thread.
// This is important for the UI to update properly.
dispatch_async(dispatch_get_main_queue(), ^{
[((UIImageView*)[newbutton.subviews objectAtIndex:0]) setImage:newimage];
}
}
Im attempting add image views to a UIView using this code:
for (int i = 0; i <numberOfImages; i++) {
UIImageView *image = [UIImageView alloc]initWithFrame:CGRectMake(40, 40, 40, 40)];
image.image = [images objectAtIndex:i];
[self.view addSubview:image];
}
This works but the problem is I would like to have a 5 second delay before it adds each image, instead it adds them all at the same time. Can anybody help me out? Thanks.
Example:
5 seconds = one image on screen
10 seconds = two images on screen
15 seconds = three images on screen
It will be more efficient to use an NSTimer.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:numberOfSeconds
target:self
selector:#selector(methodToAddImages:)
userInfo:nil
repeats:YES];
This will essentially call methodToAddImages repeatedly with the specified time interval. To stop this method from being called, call [NSTimer invalidate] (bear in mind that an invalidated timer cannot be reused, and you will need to create a new timer object in case you want to repeat this process).
Inside methodToAddImages you should have code to go over the array and add the images.
You can use a counter variable to track the index.
Another option (my recommendation) is to have a mutable copy of this array and add lastObject as a subview and then remove it from the mutable copy of your array.
You can do this by first making a mutableCopy in reversed order as shown:
NSMutableArray* reversedImages = [[[images reverseObjectEnumerator] allObjects] mutableCopy];
Your methodToAddImages looks like:
- (void)methodToAddImages
{
if([reversedImages lastObject] == nil)
{
[timer invalidate];
return;
}
UIImageView *imageView = [[UIImageView alloc] initWithFrame:(CGRectMake(40, 40, 40, 40))];
imageView.image = [reversedImages lastObject];
[self.view addSubview:imageView];
[reversedImages removeObject:[reversedImages lastObject]];
}
I don't know if you're using ARC or Manual Retain Release, but this answer is written assuming ARC (based on the code in your question).
You can use dispatch_after to dispatch a block, executed asynchronously that adds the image. Example:
for(int i = 0; numberOfImages; i++)
{
double delayInSeconds = 5.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void)
{
UIImageView *image = [UIImageView alloc]initWithFrame:CGRectMake(40, 40, 40, 40)];
image.image = [images objectAtIndex:i];
// Update the view on the main thread:
[self.view performSelectorOnMainThread: #selector(addSubview:) withObject: image waitUntilDone: NO];
});
}
Separate your code into a function, and call via NSTimer.
for (int i = 0; numberOfImages; i++) {
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(showImage)
userInfo:[NSNumber numberWithInt:i]
repeats:NO];
And then your function:
-(void) showImage:(NSTimer*)timer {
//do your action, using
NSNumber *i = timer.userInfo;
//Insert relevant code here
if (!done)
NSTimer *newTimer = [NSTimer scheduledTimerWithTimeInterval:5
target:self
selector:#selector(showImage)
userInfo:[NSNumber numberWithInt: i.intValue+1]
repeats:NO];
}
}
userInfo is a convenient way of passing parameters to functions that you need to call (but they do have to be Objects). Also, by using repeats:NO, you don't have to worry about invalidating the timer, and there's no risk of leaving timer running in memory.
also this is best option. Try this
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
I think you'd be better off with an animation
for (int i = 0; i < numberOfImages; i++)
{
// Retrieve the image
UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(40, 40, 40, 40)];
image.image = [images objectAtIndex:i];
// Add with alpha 0
image.alpha = 0.0;
[self.view addSubview:image];
[UIView animateWithDuration:0.5 delay:5.0*i options:UIViewAnimationOptionAllowUserInteraction animations:^{
// Fade in with delay
image.alpha = 1.0;
} completion:nil];
}
Not exactly what you asked for, since all the views will be added immediately, and then faded-in, but I feel that you're actually trying to achieve that, like some sort of stacking of images, right?
In fact, if you plan on removing the previous image, you can do it in the completion block, like this:
UIImageView *imagePrevious = nil;
for (int i = 0; i < numberOfImages; i++)
{
// Retrieve the image
UIImageView *image = [[UIImageView alloc] initWithFrame:CGRectMake(40, 40, 40, 40)];
image.image = [images objectAtIndex:i];
// Add with alpha 0
image.alpha = 0.0;
[UIView animateWithDuration:0.5 delay:5.0*i options:UIViewAnimationOptionAllowUserInteraction animations:^{
// Add and fade in with delay
[self.view addSubview:image];
image.alpha = 1.0;
} completion:^(BOOL finished)
{
if (finished && imagePrevious)
{
[imagePrevious removeFromSuperview];
}
}];
imagePrevious = image;
}
I am using a UIImageView to display both still and animated images. So sometimes, I'm using .image and sometimes I'm using .animationImages. This is fine.
Whether it's static or animated, I store the UIImages's in item.frames (see below)
The problem is that I'd like to display a UIActivityIndicatorView in the center of the view when the animation frames are loading. I would like this to happen with no images or frames in there. The line that isn't doing what it's supposed to is:
[self.imageView removeFromSuperview];
As a matter of fact, setting it to another image at this point doesn't do anything either. It seems like none of the UI stuff is happening in here. BTW,
NSLog(#"%#", [NSThread isMainThread]?#"IS MAIN":#"IS NOT");
prints IS MAIN.
The image that is in there will stick around until the new animation frames are all in there (1-2 seconds) and they start animating
This is all being run from a subclass of UIView with the UIImageView as a subview.
- (void)loadItem:(StructuresItem *)item{
self.imageView.animationImages = nil;
self.imageView.image = nil;
[self.spinner startAnimating];
self.item = item;
if (item.frameCount.intValue ==1){
self.imageView.image = [item.frames objectAtIndex:0];
self.imageView.animationImages = nil;
}else {
[self.imageView removeFromSuperview];
self.imageView =[[UIImageView alloc] initWithFrame:self.bounds];
[self addSubview:self.imageView ];
if( self.imageView.isAnimating){
[self.imageView stopAnimating];
}
self.imageView.animationImages = item.frames;
self.imageView.animationDuration = self.imageView.animationImages.count/12.0f;
//if the image doesn't loop, freeze it at the end
if (!item.loop){
self.imageView.image = [self.imageView.animationImages lastObject];
self.imageView.animationRepeatCount = 1;
}
[self.imageView startAnimating];
}
[self.spinner stopAnimating];
}
My ignorant assessment is that something isn't being redrawn once that image is set to nil. Would love a hand.
What I have found is not an answer to the question, but rather a much better approach to the problem. Simply, use NSTimer instead of animationImages. It loads much faster, doesn't exhaust memory and is simpler code. yay!
Do this:
-(void)stepFrame{
self.currentFrameIndex = (self.currentFrameIndex + 1) % self.item.frames.count;
self.imageView.image = [self.item.frames objectAtIndex:self.currentFrameIndex];
}
and this
-(void)run{
if (self.item.frameCount.intValue>1){
self.imageView.image = [self.item.frames objectAtIndex:self.currentFrameIndex];
self.timer = [NSTimer scheduledTimerWithTimeInterval:1/24.0f target:self selector:#selector(stepFrame) userInfo:nil repeats:YES];
}else{
self.imageView.image = [self.item.frames objectAtIndex:0];
}
}