Hi I am using SQllite in my application. In that I want to show activity indicator during the time of db process starts and ends.
Here is my code:
[activityIndicator startAnimating];
// DB Open
// DB close
// DB process ends
[activityIndicator stopAnimating];
When I try this, Its not working properly. Does sqllite code blocking animation of indicator? I am using activity indicator in scrollview.
The explanation as to why it doesn't work is simple: the UI only gets updated after the current runloop has terminated. The runloop encompasses all calls you're making in a single thread (currently, the main thread of your app).
So for example, if you call something like for (int i=1; i<1000; i++) { label.text = i } (in rough pseudo-code), your label won't display 1000 changes of its text, it'll only display the final value.
It's done this way by UIKit so that the interface can be smooth and jag-free.
If you really, really want to perform multiple updates on the UI in the same method, you have to perform your computation in a background thread. Other answers mention the use of a delayed call (0.1 seconds), but this is useless and it generates huge lags if you call this repeatedly, say a hundred times. The correct solution is this:
- (void)doSomethingVeryLong
{
[spinner startAnimating];
// or [SVProgressHud showWithMessage:#"Working"];
[self performSelectorInBackground:#selector(processStuffInBackground)];
}
- (void)processStuffInBackground
{
for (int i = 0; i < 1e9; i++)
{
/* Make huge computations,
* wait for a server,
* teach a neurasthenic French (no offence, I'm French)
*/
}
[self performSelectorOnMainThread:#selector(workDone)];
}
- (void)workDone
{
[spinner startAnimating];
// or [SVProgressHud dismiss];
}
If you wanna mess with the technical stuff, take a look a the Thread programming guide or the NSRunloop reference.
try below code :
[[activityIndicator startAnimating];
[self performSelector:#selector(DB_process) withObject:nil afterDelay:0.1];
create DB process method
- (void)DB_process
{
// DB close
// DB process ends
[activityIndicator stopAnimating];
}
Try to call process part after a delay of few second.
[activityIndicator startAnimating];
// call this part after a delay in different method (DB Open)
// DB close
// DB process ends
[activityIndicator stopAnimating];
It will work.
Related
I am accessing the photo library on the iphone and it takes a long time to import the pictures i select in my application, how do i run the process on a secondary thread , or what solution do i use to not block the user interface?
I did a full explanation with sample code using performSelectOnBackground or GCD here:
GCD, Threads, Program Flow and UI Updating
Here's the sample code portion of that post (minus his specific problems:
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];
}
Use an asynchronous connection. It won't block the UI while it does the fetching behind.
THIS helped me a lot when I had to do download images, lot of them.
One option is use performSelectorInBackground:withObject:
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.
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];
}
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!
Kinda new to iPhone programming and was experimenting with threads
- (void)viewDidLoad {
[super viewDidLoad];
[NSThread detachNewThreadSelector:#selector(changeMain) toTarget:self withObject:nil];
[NSThread detachNewThreadSelector:#selector(changeThread) toTarget:self withObject:nil];
}
- (void)changeMain{
NSAutoreleasePool* arp = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 1000000; i++) {
[mainValue setText:[NSString stringWithFormat:#"%d",i]];
[self.view setNeedsDisplay];
}
[arp release];
}
- (void)changeThread{
NSAutoreleasePool* arp = [[NSAutoreleasePool alloc] init];
for (int i = 0; i < 1000000; i++) {
[threadValue setText:[NSString stringWithFormat:#"%d",i]];
[self.view setNeedsDisplay];
}
[arp release];
}
mainValue and threadValue are both just UILabels. I expected this to run and see both labels run up to 999999 but instead it starts at some low number (what it is when the screen initally refreshing i assume), pauses for a bit, then updates to 999999. I'm thinking the screen just isn't refreshing.
Is this correct? Am I doing it wrong?
You have to perform any Cocoa Touch operations in main thread, in other case results are unpredictable.
You don't have to call setNeedsDisplay manually.
So I'd recommend to use the following construction:
[threadValue performSelectorOnMainThread:#selector(setText:) withObject:[NSString stringWithFormat:#"%d",i] waitUntilDone:YES];
Additional notes:
1. 100000 runs can overflow the main thread queue so some values will disappear
2. You can use waitUntilDone:NO too
The setNeedsDisplay message triggers a redraw, but it only happens during the next time the main thread becomes active. So your side threads trigger a million redraws but they are queued. As soon as the main thread continues, it "collapses" all requests into one redraw.
Most likely setNeedsDisplay just sets a flag that is checked once during each run of the main loop, so setting it 1000000 to true doesn't influence anything. Try to let the "worker threads" sleep after each iteration to give the main thread some time to redraw.
Don't use for() for animations. The for will process in the same "frame". Maybe just have an ivar i and in changeMain you can have if (i<10000) { mainValue.text = [NSString stringWithFormat:#"%d",i]; i++;} or something like that. This way the setText only happens once per "frame".
I'm not sure if this will work, but you could try to force the setNeedsDisplay method to be executed on the main thread, using e.g. [self performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone:YES]. This should (hopefully, i didn't test it!) update the view after every increment. You could also try to set waitUntiDone:NO, but I'm unsure what will happen then.
See here