connectionDidFinishLoading - how to force update UIView? - iphone

I am able to download a ZIP file from the internet. Post processing is done in connectionDidFinishLoading and works OK except no UIView elements are updated. For example, I set statusUpdate.text = #"Uncompressing file" but that change does not appear until after connectionDidFinishLoading has completed. Similarly, the UIProgressView and UIActivityIndicatorView objects are not updated until this method ends.
Is there any way to force an update of the UIView from within this method? I tried setting [self.view setNeedsDisplay] but that didn't work. It appears to be running in the main thread. All other commands here work just fine - the only problem is updating the UI.
Thanks!
Update: here is the code that is NOT updating the UIVIEW:
-(void)viewWillAppear:(BOOL)animated {
timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:#selector(processUpdate:) userInfo:nil repeats:YES];
downloadComplete = NO;
statusText.text = #"";
}
-(void)processUpdate:(NSTimer *)theTimer {
if (! downloadComplete) {
return;
}
[timer invalidate];
statusText.text = #"Processing update file.";
progress.progress = 0.0;
totalFiles = [newFiles count];
for (id fileName in newFiles) {
count++;
progress.progress = (float)count / (float)totalFiles;
// ... process code goes here ...
}
}
At then end of processUpdate, I set downloadComplete = YES. This builds & runs without errors and works as intended except nothing updates in the UIVIEW until after processUpdate completes, then everything updates at once.
Thanks for your help so far!

As Niels said, you must return control to the run loop if you want to see views update. But don't start detaching new threads unless you really need to. I recommend this approach:
- (void)connectionDidFinishLoading:(NSConnection *)connection {
statusUpdate.text = #"Uncompressing file";
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
}
- (void)doUncompress {
// Do work in 100 ms chunks
BOOL isFinished = NO;
NSDate *breakTime = [NSDate dateWithTimeIntervalSinceNow:100];
while (!isFinished && [breakTime timeIntervalSinceNow] > 0) {
// do some work
}
if (! isFinished) {
statusUpdate.text = // here you could update with % complete
// better yet, update a progress bar
[self performSelector:#selector(doUncompress) withObject:nil afterDelay:0];
} else {
statusUpdate.text = #"Done!";
// clean up
}
}
The basic idea is that you do work in small chunks. You return from your method to allow the run loop to execute periodically. The calls to performSelector: will ensure that control eventually comes back to your object.
Note that a risk of doing this is that a user could press a button or interact with the UI in some way that you might not expect. It may be helpful to call UIApplication's beginIgnoringInteractionEvents to ignore input while you're working... unless you want to be really nice and offer a cancel button that sets a flag that you check in your doUncompress method...
You could also try running the run loop yourself, calling [[NSRunLoop currentRunLoop] runUntilDate:...] every so often, but I've never tried that in my own code.

While you are in connectionDidFinishLoading nothing else happens in the application run loop. Control needs to be passed back to the run loop so it can orchestrate the UI updating.
Just flag the data transfer as complete and the views for updating. Defer any heavy processing of the downloaded data to it's own thread.
The application will call your views back letting them refresh their contents later in the run loop. Implement drawRect on your own custom views as appropriate.

If you're receiving connectionDidFinishLoading in the main thread, you're out of luck. Unless you return from this method, nothing will be refreshed in the UI.
On the other hand, if you run the connection in a separate thread, then you can safely update the UI using the following code:
UIProgressView *prog = ... <your progress view reference> ...
[prog performSelectorOnMainThread:#selector(setProgress:)
withObject:[NSNumber numberWithFloat:0.5f]
waitUntilDone:NO];
Be careful not to update the UI from a non-main thread - always use the performSelectorOnMainThread method!

Do exactly what you're doing with the timer, just dispatch your processing code to a new thread with ConnectionDidFinish:. Timers can update the UI since they're run from the main thread.

The problem turned out to that the UI isn't updated in a for() loop. See the answer in this thread for a simple solution!

Related

referencing a method in an if statement

How can I check if a method is or isn't running, in an if statement? For example-
if ([(UIButton *)sender isEqual:blueButton] && **showBlueText method is running** )
{
Keep playing.
}
else if ([(UIButton *)sender isEqual:blueButton] && **showBlueText method is NOT running** )
{
Game over.
}
-(void)showBlueText
{
blueText.hidden = NO;
[self performSelector:#selector(hideText) withObject:nil afterDelay:textDelay];
[self performSelector:#selector(showGreenText) withObject:nil afterDelay:hideDelay];
}
Just to clarify, 'showBlueText' is a part of its own loop that runs independently of this if statement. I'm just trying to check if showBlueText is currently running.
You want to record state here. Make a new instance variable in this class.
// new iVar
BOOL textIsShowing;
// method
-(void)showBlueText {
textIsShowing = YES;
blueText.hidden = NO;
[self performSelector:#selector(hideText) withObject:nil afterDelay:textDelay];
}
// method
- (void)hideText {
textIsShowing = NO;
blueText.hidden = YES;
}
// button press
- (void)buttonPressed {
if (textIsShowing) {
NSLog(#"Keep playing");
} else {
NSLog(#"Game over");
}
}
Between the time you call this method, and the animation stops, don't think of it as "running". It schedules code to be executed later. Instead you want to be notified after it has finally run.
And in this case it's easiest to keep track of the state yourself. Use a new variable to track the state of things, and change it's value when that state changes.
But can't you just check if (blueText.hidden)? Yeah, you could. But it's bad practice to store state about your program in some obscure property of a random unimportant object.
Examine your state to figure out what you show. Don't examine what's showing to figure out your state.
I suggest replace Keep playing or Game over or Doing stuff with NSLog() statements. I always use it to keep a track of the changes in program if I am getting unexpected result.
So your statement may look like:
NSLog(#"Keep playing");
I hope this helps.
Just check if the text is hidden. No need to store parallel state in your controller - all that does is create the possibility that they'll be out of sync.

Stop performing the animation in background thread and run loop

I run my animations in a UITAbleViewCell.
Each cell has its own animation and the cells are reusable.
I use [mView performSelectorInBackground:#selector(layoutSubview) withObject:nil];
There in the background thread I initiate the runLoop to perform tasks like this:
- (void)startAnimation
{
NSRunLoop *mLoop = [NSRunLoop currentRunLoop];
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
mRunLoop = YES;
while (mRunLoop == YES && [mLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]);
}
and stop it:
- (void)stopAnimation
{
if (![NSThread isMainThread]) {
[[NSThread currentThread] cancel];
}
mRunLoop = NO;
self.animationTimer = nil;
CFRunLoopStop(CFRunLoopGetCurrent());
}
I run into problems when I fast scroll through table, because on the first cell initiation I begin the animation, so the first runLoop call occures which performs a setNeedDisplay and all the methods from it. But before finishing the first runLoop cycle the cell disappears from the view and is already available for reuse. So I begin clearing it, while the cycle is still performing operations and here I meet situations like
message sent to deallocated instance
So could you please give me some hints of how should I correctly stop performing the operations in that thread? I mean if I want to realese for example an object, which is performing some actions how to immediately stop'em?
Hope I gave enough info.
Thanks
UPDATE: No ideas at all?
I'll take a completely different stab on it:
Get rid of the cell's timers and background threads altogether!
Animation is not something where NSTimer is a good fit in the first place and having multiple timers won't help much, either.
UITableView has a method visibleCells and a method indexPathsForVisibleRows. I'd suggest to use a single CADisplayLink — which is suited for animation, as it calls you back with the actual refresh rate of the display or a fraction thereof — in your tableview-controller and in the callback of that display-link iterate over the visible cells.
If you want to schedule the display-link on the run-loop of a secondary thread, feel free to do so, but I'd check if you can get away without extra threading first.
Some code:
#interface AnimatedTableViewController ()
#property (strong, nonatomic) CADisplayLink *cellAnimator;
- (void)__cellAnimatorFired:(CADisplayLink *)animator;
#end
#implementation AnimatedTableViewController
#synthesize cellAnimator = cellAnimator_;
- (void)setCellAnimator:(CADisplayLink *)animator
{
if (animator == cellAnimator_)
return;
[cellAnimator_ invalidate];
cellAnimator_ = animator;
[cellAnimator_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSCommonRunLoopModes];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.cellAnimator = [CADisplayLink displayLinkWithTarget:self selector:#selector(__cellAnimatorFired:)];
...
}
- (void)viewWillDisappear:(BOOL)animated
{
self.cellAnimator = nil;
...
[super viewWillDisappear:animated];
}
- (void)__cellAnimatorFired:(CADisplayLink *)animator
{
NSArray *visibleCells = [self.tableView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger unused, BOOL *stop){
[cell setNeedsDisplay];
}];
}
...
#end
NSTimer has a -cancel method that stops the timer from firing. Calling it in -prepareForReuse (and, for that matter, in -stopAnimation) may help.
However, this code looks rather dangerous. Nesting run loops like this is almost never a good idea—and moreover, as far as I can tell it's totally unnecessary. If you let -startAnimation return, your animation timer will still get run on the main run loop. And if you're doing it this way because there's some code after -startAnimation that you want to delay, you should restructure your code so this isn't needed.
(If you drop the runloop stuff in -startAnimation, don't stop the runloop in -stopAnimation either.)
Something like the approach danyowdee recommends would be even better, but at least get rid of this runloop stuff. It's just asking for trouble.
I think you can use this method for your problem
[NSObject cancelPreviousPerformRequestsWithTarget:yourTarget selector:aSelector object: anArgument];
I think that the best way to avoid that behavior is assigning the delegate that receives the cancel method in other class that won't be reused. For example, you can have a private array of instances that process all the cancel methods, each row mapped into an array element.
I recommend you the lazy tables example provided by Apple in Xcode documentation. It's a great example of how to load images asynchroniously in background with a table. I think that also it would be useful for you for the scrolling subjects (decelerating and paging).
Only one more consideration, i don't recommend messing up with several cfrunloopstop, test it hard!

GCD, Threads, Program Flow and UI Updating

I'm having a hard time figuring out how to put this all together.
I have a puzzle solving app on the mac.
You enter the puzzle, press a button, and while it's trying to find the number of solutions,
min moves and such I would like to keep the UI updated.
Then once it's finished calculating, re-enable the button and change the title.
Below is some sample code from the button selector, and the solving function:
( Please keep in mind I copy/paste from Xcode so there might be some missing {} or
some other typos.. but it should give you an idea what I'm trying to do.
Basicly, user presses a button, that button is ENABLED=NO, Function called to calculate puzzle. While it's calculating, keep the UI Labels updated with moves/solution data.
Then once it's finished calculating the puzzle, Button is ENABLED=YES;
Called when button is pressed:
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// I've tried to use GCD but similar issue and can't get UI updated.
//dispatch_queue_t queue = dispatch_queue_create("com.gamesbychris.createTree", 0);
//dispatch_sync(queue, ^{[self createTreeFromNode:rootNode];});
}
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
if (numSolutions == 0) {
solveButton.title = #"Not Solvable";
} else {
solveButton.title = #"Solve Puzzle";
}
}
Needs to run in background so UI can be updated:
-(void)createTreeFromNode:(TreeNode *)node
{
// Tried using GCD
dispatch_queue_t main_queue = dispatch_get_main_queue();
...Create Tree Node and find Children Code...
if (!solutionFound){
// Solution not found yet so check other children by recursion.
[self createTreeFromNode:newChild];
} else {
// Solution found.
numSolutions ++;
if (maxMoves < newChild.numberOfMoves) {
maxMoves = newChild.numberOfMoves;
}
if (minMoves < 1 || minMoves > newChild.numberOfMoves) {
solutionNode = newChild;
minMoves = newChild.numberOfMoves;
// Update UI on main Thread
dispatch_async(main_queue, ^{
minMovesLabel.stringValue = [NSString stringWithFormat:#"%d",minMoves];
numSolutionsLabel.stringValue = [NSString stringWithFormat:#"%d",numSolutions];
maxMovesLabel.stringValue = [NSString stringWithFormat:#"%d",maxMoves];
});
}
GCD and performSelectorInBackground samples below. But first, let's look at your code.
You cannot wait where you want to in the code above.
Here's the code you had. Where you say wait in the comment is incorrect. See where I added NO.
- (void) solvePuzzle:(id)sender{
solveButton.enabled = NO;
solveButton.title = #"Working . . . .";
// I've tried using this as a Background thread, but I can't get the code to waitTilDone before continuing and changing the button state.
[self performSelectorInBackground:#selector(createTreeFromNode:) withObject:rootNode];
// NO - do not wait or enable here.
// Need to wait here until createTreeFromNode is finished.
solveButton.enabled=YES;
}
A UI message loop is running on the main thread which keeps the UI running. solvePuzzle is getting called on the main thread so you can't wait - it will block the UI. It also can't set the button back to enabled - the work hasn't been done yet.
It is the worker function's job on the background thread to do the work and then when it's done to then update the UI. But you cannot update the UI from a background thread. If you're not using blocks and using performSelectInBackground, then when you're done, call performSelectorOnMainThread which calls a selector to update your UI.
performSelectorInBackground Sample:
In this snippet, I have a button which invokes the long running work, a status label, and I added a slider to show I can move the slider while the bg work is done.
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
[self performSelectorInBackground:#selector(performLongRunningWork:) withObject:nil];
}
- (void)performLongRunningWork:(id)obj
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
[self performSelectorOnMainThread:#selector(workDone:) withObject:nil waitUntilDone:YES];
}
- (void)workDone:(id)obj
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
GCD Sample:
// on click of button
- (IBAction)doWork:(id)sender
{
[[self feedbackLabel] setText:#"Working ..."];
[[self doWorkButton] setEnabled:NO];
// async queue for bg work
// main queue for updating ui on main thread
dispatch_queue_t queue = dispatch_queue_create("com.sample", 0);
dispatch_queue_t main = dispatch_get_main_queue();
// do the long running work in bg async queue
// within that, call to update UI on main thread.
dispatch_async(queue,
^{
[self performLongRunningWork];
dispatch_async(main, ^{ [self workDone]; });
});
}
- (void)performLongRunningWork
{
// simulate 5 seconds of work
// I added a slider to the form - I can slide it back and forth during the 5 sec.
sleep(5);
}
- (void)workDone
{
[[self feedbackLabel] setText:#"Done ..."];
[[self doWorkButton] setEnabled:YES];
}
dispatch_queue_t backgroundQueue;
backgroundQueue = dispatch_queue_create("com.images.bgqueue", NULL);
- (void)process {
dispatch_async(backgroundQueue, ^(void){
//background task
[self processHtml];
dispatch_async(main, ^{
// UI updates in main queue
[self workDone];
});
});
});
}
By and large, any work to be submitted to a background queue needs to follow this pattern of code:
dispatch_queue_t queue = dispatch_queue_create("com.myappname", 0);
__weak MyClass *weakSelf = self; //must be weak to avoid retain cycle
//Assign async work
dispatch_async(queue,
^{
[weakSelf doWork];
dispatch_async(dispatch_get_main_queue(),
^{
[weakSelf workDone];
});
});
queue = nil; //Using ARC, we nil out. Block always retains the queue.
Never Forget:
1 - queue variable above is a reference counted object, because it is a private queue, not a global one. So it is retained by the block which is executing inside that queue. Until this task is complete, it is not released.
2 - Every queue got its own stack which will be allocated / deallocated as part of recursive operation. You only need to worry about class member variables which are reference counted (strong, retain etc.) which are accessed as part of doWork above.
3 - While accessing those reference counted vars inside background queue operation, you need to make them thread-safe, depending on use cases in your app. Examples include writes to objects such as strings, arrays etc. Those writes should be encapsulated inside #synchronized keyword to ensure thread-safe access.
#synchronized ensures no another thread can get access to the resource it protects, during the time the block it encapsulates gets executed.
#synchronized(myMutableArray)
{
//operation
}
In the above code block, no alterations are allowed to myMutableArray inside the #synchronized block by any other thread.

Update tableview with regular time intervals in iphone

I have created a sample application. It has fetched data from server for every 1 sec and updated the result in UITableView. I had already done. But it crashes my app rapidly. What I do is call NSTimer every sec.
timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:#selector(MainLoop)
userInfo:nil repeats:YES];
In that timer function i have called NSThread function.
-(void)MainLoop
{
if(isPreviousThreadFinished)
{
NSLog(#"Thread Opened");
isPreviousThreadFinished = NO;
[NSThread detachNewThreadSelector:#selector(MainLoopThread) toTarget:self withObject:nil];
}
}
-(void)MainLoopThread
{
MainLoopPool=[[NSAutoreleasePool alloc]init];
//Get data from the server
[self performSelectorOnMainThread:#selector(UpdateTable) withObject:nil waitUntilDone:YES];
[MainLoopPool release];
}
-(void)UpdateTable
{
[self.tableView reloadData];
isPreviousThreadFinished = YES;
NSLog(#"Thread closed");
}
It works fine. And reloads data correctly from the server. I stop NSTimer in viewWillDisappear method. When I go to previous page sometimes it crashes application. In console i have seen the error is given below,
bool _WebTryThreadLock(bool), 0xb2aa410: Tried to obtain the web lock from a thread other than the main thread or the web thread. This may be a result of calling to UIKit from a secondary thread. Crashing now...
What is wrong with my code? Crashes appear randomly.
When I work on my application I discovered very good thing - NSOperation. It's thread safe. You can simply add you operations to the queue without fear to crash. There are a lot of tutorials about NSOperation
http://www.icodeblog.com/2010/03/04/iphone-coding-turbo-charging-your-apps-with-nsoperation/
http://www.cimgf.com/2008/02/16/cocoa-tutorial-nsoperation-and-nsoperationqueue/
I strongly recommend you use NSOperation-s instead of threads in your project.

How can UI elements in iOS be updated from a loop without threads?

I want to update a label from a loop, for example like this:
- (void)viewDidLoad {
[super viewDidLoad];
int i=0;
while (1) {
i++;
[NSThread sleepForTimeInterval:0.05]; // do some computation,
[myLabel setText:[NSString stringWithFormat:#"%d", i]]; // show the result!
[self.view setNeedsDisplay];
}
}
Assume that instead of sleep some heavy computation is done.
I do not want the overhead of doing the computation in the background.
The Windows equivalent for handling this problem would be .DoEvents, as this example shows:
http://www.tek-tips.com/viewthread.cfm?qid=1305106&page=1
Is there a similar solution for this in iOS?
[self.view setNeedsDisplay] does not work at all!
There must be some way to process application events from iOS on a controlled schedule in the main thread... Like .DoEvents in windows, despite all its shortcomings is quite useful for some simple applications.
I guess this is like a game-loop but with UI components.
The way you would do this is as follows:
-(void) incrementCounter:(NSNumber *)i {
[myLabel setText:[NSString stringWithFormat:#"%d", [i intValue]]]; // show the result!
[self performSelector:#selector(incrementCounter:) withObject:[NSNumber numberWithInt:i.intValue+1] afterDelay:0.05];
}
- (void)viewDidLoad {
[super viewDidLoad];
// start the loop
[self incrementCounter:[NSNumber numberWithInt:0]];
}
The basic idea here is to increment the counter after a slight delay 0.05 to give the main UI thread a chance to flush all UI events, which makes up for explicitly calling .DoEvents in the Windows world.
I assume you want to implement a counter using a label? You can use NSTimer to call a method that updates your counter every X milliseconds, for instance.
Use NSTimer in iOS if you want to update UI components.
NSTimer* myTimer = [NSTimer scheduledTimerWithTimeInterval: 60.0 target: self
selector: #selector(callAfterSomeSecond:) userInfo: nil repeats: YES];
Implement the callAfterSomeSecond: as below :
-(void) callAfterSomeSecond:(NSTimer*) timer
{
static int counter = 0;
if(counter == 100)
{
[timer invalidate];
}
[myLabel setText:[NSString stringWithFormat:#"%d", counter ]];
[self.view layoutSubviews];
counter++;
}
in your code, the while loop runs in the Main thread, and the UI update also should be done in Main thread, so while the loop is running, the Main thread is kind of 'blocked'(busy), so UI update cannot be performed.
I think what I want to say is not what you want. to resolve it, you have to put the heavy computing in another thread, using NSOperation or GCD for example.
The best way is to do your computation on a different thread, but when that's not feasible, you can wrap the code that affects the UI in a CATransaction. In my tests this will update the UI immediately rather than needing to wait till the next run loop.
while (1) {
[CATransaction begin];
//Your UI code here
[CATransaction commit];
}