Update views while being touched - iphone

I am having an app where I load images in a UITableView. Each row has an image view.
I download images from URL and assign it to UIImageView. The problem here is that the image views are not updating if I was dragging the table view. It only updates when I release the finger.
Another problem is that I have a label in all cells that shows a counter which is incremented using a timer. Timer action is also not getting called when my finger is on the table view.
How do I do the updates in both these cases?
May be this is a very basic question. But I have no clue.
Thanks everyone!
EDIT: I have noticed that -connection:didReceiveResponse: method is not getting called when the view is being touched.
EDIT 2: I tried adding run loop with NSURLConnection. My method looks like this.
- (void)start {
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.imageURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:self.timeoutInterval];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSPort* port = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
[rl addPort:port forMode:NSRunLoopCommonModes];
[_connection scheduleInRunLoop:rl forMode:NSRunLoopCommonModes];
[_connection start];
[rl run];
}
This loads the images even when I am touching on the view. But the table view is scrolling very slowly. Its not even scrolling. It only moves when I drag. No movement after I release the finger.

Problem 1
The solution is to use downloading mechanism that doesn't get blocked while you drag the table view. SDWebImage is an asynchronous image downloader with cache support with an UIImageView category. This will let your image view to be updated even if you are dragging your table. I have faced a the same issue in my project where i used SDWebImage. Just a call like this will do.
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Problem 2
If its scheduledTimer will not get called while the main thread is tracking touches. Do something like this. Just create a timer and add to loop
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
this will not interrupt your timer.

this might be some helpful to you because you downloading images for displaying in UItableViewCells here i am giving you the example of the anything in background without blocking UI there you can get good tutorial link.they have mentioned enough good way same thing.go through this link
And Rest of the thing could be solved with the help of as Meera j Pai mentioned in his Answer.

I solved the problem with both NSConnection & NSTimer using the answers given by #Amar & #Meera J Pai, by using NSRunLoop.
I have changed my connection code to be like this.
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]
[_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
It worked great!
Thanks everyone.

Related

How to track the progress of a download using ASIHTTPRequest (ASYNC)

I am currently using an asynchronous call to my API (I setup) on my site. I am using ASIHTTPRequest's setDownloadProgressDelegate with a UIProgressView. However I don't know how I can call a selector (updateProgress) which will set a CGFloat 'progress' to the progressView's progress. I tried the following, but both the progresses were zero. Please can you tell me how I can get this working?
(in some method)
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:[NSURL URLWithString:[url stringByAppendingFormat:#"confidential"]]];
[request setDownloadProgressDelegate:progressView];
[NSTimer scheduledTimerWithTimeInterval:1.0f/60.0f target:self selector:#selector(updateProgress:) userInfo:nil repeats:YES];
[request setCompletionBlock:^{~100 lines of code}];
[request setFailedBlock:^{~2 lines of code :) }];
[request startAsynchronous];
- (void) updateProgress:(NSTimer *)timer {
if (progressView.progress < 1.0) {
currentProgress = progressView.progress;
NSLog(#"currProg: %f --- progressViewProg: %f", currentProgress, progressView.progress);
}
else {
[timer invalidate];
}
return;
}
For people still finding this answer: Please note ASI is highly deprecated, you should use NSURLSession or ASIHTTPRequest instead.
One way to achieve what you want would be to set the downloadProgressDelegate to be your own class and implement setProgress:. In this implementation, update your progress variable and then call [progressView setProgress:];
Or in code, set up the request's download progress delegate:
[request setDownloadProgressDelegate:self];
and then add the method to your class:
- (void)setProgress:(float)progress
{
currentProgress = progress;
[progressView setProgress:progress];
}
Try to add in to your request:
[request setShowAccurateProgress:YES];
It won't help you to call updateProgress, ASIHTTPRequest will change progress indicator itself.
BTW: NS*Connection appears to be quite a bit faster than ASI* when downloading stuff.
In any case, the example on this page implies that you don't need to manually "copy" the value from the the download object to the progress view object.
In fact, your timer based code is getting the progress from the progress view which should be showing the progress already. There should be no need for a timer in this code at all, if I understand ASIHTTP* correctly.

How to consume UI Events while loading data in the background

I have a little iPhone app that loads data from a web service. To make sure that nothing goes wrong while loading the data I create a semi-transparent view over the app and use CFRunloopRun() to wait until all the data is loaded in the background. This is the code for that:
self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
// Now show an animation
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
UIView *window = [[UIApplication sharedApplication] keyWindow];
UIView *shield = [[UIView alloc] initWithFrame:window.bounds];
shield.backgroundColor = [UIColor blackColor];
shield.alpha = 0.5f;
[window addSubview:shield];
spinner.center = shield.center;
[shield addSubview:spinner];
spinner.hidden = NO;
NSLog( #"JCL.callServerWithRequest(), spinner view: %#, shield view: %#, window: %#", spinner, shield, window );
[spinner startAnimating];
// Hand over to the Runnloop to wait
CFRunLoopRun();
[spinner stopAnimating];
[spinner removeFromSuperview];
[spinner release];
[shield removeFromSuperview];
[shield release];
This works fine except that any clicks on a button somewhere is played after the loading so if the users clicks on the download button twice he will do the download twice as well.
Any idea how to consume the UI events before the shield is removed.
Thanks - Andy
Try it without messing with runloops. I suspect that the UI events are coming in to the normal loop on the window but not being processed until your custom loop returns, at which point the "shield" view is no longer there to catch them. If you put the shield in place and then let the main runloop handle things, the shield should catch them all as normal.
Thanks to Anomie I finally tried out to go without the CFRunLoopRun() and it is quite difficult because the execution is split into two parts: - the Invocation and the Return of the Result through a callback. But then I shot myself in the proverbial foot because I tried to block the returning thread to slow down the execution which did not work because that was executed again in the main thread.
Eventually I did slow down the Web Service and then everything worked as expected.

Approaching the label at timer, it can's renewal string of label

I make a Voca App.
After playing voice file and showing English text, it shows the meaning label.
So, I want to hide label texts before playing next voice file.
I have problem.df
These labels are not hidden.
Approaching the label at timer, it can's renewal string of label.
What is the problem?
Help me, please!
- (void)showLabel{
Word *word = [wordArray objectAtIndex:selectedIndex];
[simpleMeaning setText:word.mean];
NSTimer *timer2 = [[NSTimer scheduledTimerWithTimeInterval:2 target:self selector:#selector(hideLabel) userInfo:nil repeats:NO] retain];
[timerArray addObject:timer2];
[timer2 release];
timer2 = nil;
}
- (void)hideLabel{
[simpleMeanig setText:#" "];
++selectedIndex;
[self filePlay];
}
I'm wondering if the call to filePlay is blocking the UI update (hard to tell without seeing the guts of filePlay).
This is just a guess, but instead of:
[self filePlay];
try:
[self performSelectorOnMainThread:(#selector(filePlay) withObject:nil waitUntilDone:NO];
This will defer the call to filePlay until after the current event (as a result of the timer firing) has been processed (which should in turn should allow the UI to refresh itself).

How to update a UILabel immediately?

I'm trying to create a UILabel which will inform the user of what is going on while he waits. However the UILabel always delay its text update until after the system goes idle again.
The process:
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Calculating..." waitUntilDone:YES];
[distanceManager calc]; // Parses a XML and does some calculations
[infoLine performSelectorOnMainThread:#selector(setText:) withObject:#"Idle" waitUntilDone:YES];
Should not waitUntilDone make this happen "immediately"?
If you are doing this on the main UI thread, don't use waitUntilDone. Do a setText, setNeedsDisplay on the full view, set a NSTimer to launch what you want to do next starting 1 millisecond later, then return from your function/method. You may have to split your calculation up into chucks that can be called separately by the timer, maybe a state machine with a switch statement (select chunk, execute chunk, increment chunk index, exit) that gets called by the timer until it's done. The UI will jump in between your calculation chunks and update things. So make sure your chunks are fairly short (I use 15 to 200 milliseconds).
Yes waitUntilDone makes the setText: happen immediately, but setting the label's text does not mean the screen is updated immediately.
You may need to call -setNeedsDisplay or even let the main run loop tick once before the screen can be updated.
Here's a useful function I added to a subclass of UIViewController. It performs the selector in the next run loop. It works, but do you think I should make NSTimer *timer an instance variable since it's likely this method will be called more than once.
- (void)scheduleInNextRunloopSelector:(SEL)selector {
NSDate *fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:0.001]; // 1 ms
NSTimer *timer = [[NSTimer alloc]
initWithFireDate:fireDate interval:0.0 target:self
selector:selector userInfo:nil repeats:NO];
[fireDate release];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[timer release];
}
Use performSelector:(SEL) withObject:(id) afterDelay:(NSTimeInterval):
self.infoLine.text = #"Calculating...";
[self performSelector:#selector(likeABoss) withObject:nil afterDelay:0.001];
//...
-(void) likeABoss {
// hard work here
self.infoLine.text = #"Idle";
}

NSOperation for animation loop causes strange scrolling behaviour

I've created an animation loop which I run as an operation in order to keep the rest of my interface responsive. Whilst almost there, there is still one remaining issue. My UIScrollViews don't seem to be reliably picking up when a user touch ends. What this means is, for example, if a user drags down on a scroll view, when they lift their fingers the scrollview doesn't bounce back into place and the scrollbar remains visible. As if the finger hasn't left the screen. It takes another tap on the scrollview for it to snap to its correct position and the scrollbar to fade away...
Here's the loop I created in a subclassed NSOperation:
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
_displayLink = [[CADisplayLink displayLinkWithTarget: self selector: #selector(animationLoop:)] retain];
[_displayLink setFrameInterval: 1.0f];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
while (![self isCancelled])
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[loopPool drain];
}
[_displayLink invalidate];
[pool release];
}
It seems that whilst this operation is running things go a little weird...
DOes anyone have any idea what might be going on here, and even better how to fix it...
Thanks!
I do not understand why are you using a so complicated solution for your need which seems very classic.
In my opinion you'd better use the Quartz basic features to create your animation and let the OS choose when to compose for rendering. You can also use the setNeedsLayout and setNeedDisplay method to notify the rendering loop you've modify something instead of implementing your own loop.
Or maybe you have a specific need I did not understand.