How to solve UIThread bloack issue in iPhone - iphone

I am creating an iPhone application where I need to show a login screen for few minutes, hence I created the custom view and added to the custom view controller which is added to the window for display. Now at the same time I need to check for some background database so, I am creating that in separate delegate and while after database operation is in finished it gives an callback to the main thread to display the new screen. But the first view is never getting displayed and my application directly lands up in the new view.
Please find below my code snippet:
(void)CheckForExistingData : (DatabaseSource *)theDatabaseConnection
{
BOOL isRecordExist = theDatabaseConnection.isrecordExist;
// Release the connection....
[theDatabaseConnection release];
theDatabaseConnection = nil;
if (isRecordExist == FALSE)
{
textLabel.text = #"Preparing the application for first time use, please wait....";
[activityIndicator startAnimating];
[self setNeedsDisplay];
}
else
{
// Now all categories are successfully downloaded, launch the category screen...
sleep(2); // sleep for 1 second to allow to show the splash screen....
[self.viewController LaunchCategoryViewController:self];
}
}
Here CheckForExistingData is an callback mechanism which will be called from the other thread.

You need to exit your method to see anything displayed. Not sleep or wait on a synchronous network call.
That probably means you need to break your sequential code into multiple methods, the subsequent parts being called by a splash wait timer, the view button handler, or the async network activity completion callback.

sleep() blocks your main thread, thus the UI has no chance to update.
But you can always send messages delayed. In your case, it would look like this:
[self.viewController performSelector:#selector(LaunchCategoryViewController:) withObject:self afterDelay:2.0];

Related

IPhone UIActivityIndicator won't display until after process has completed

I've been researching this for a few days, but nothing I've found works.
Here's the desired process: User presses the enter button on a pop-up window -> ActivityIdicator appears -> saving process occurs -> ActivityIndicator disappears.
However, for some reason the ActivityIndicator does not show up until after the process is complete, rendering it completely useless.
I attempted to follow the process described here: UIActivityIndicator not working properly?
Here is the code for the pop-up window
-(void)alertView:(UIAlertView *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
// the user clicked one of the Enter/Cancel buttons
[self performSelector:#selector(DisplaySpinner) withObject:nil afterDelay:0];
if (buttonIndex == 1)
{
[self performSelector:#selector(EnterButtonClicked) withObject:nil afterDelay:0];
}
else
{
NSLog(#"Name Cancel Clicked");
}
[NameField resignFirstResponder];
}
Here is the code for the DisplaySpinner method:
-(void)DisplaySpinner{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
[self.view addSubview:loadingIndicator];
[loadingIndicator startAnimating];
[pool release];
}
The EnterButtonClicked Method contains the saving process. Despite running in seperate processes, The ActivityIndicator doesn't show up until after the process is complete.
Any suggestions?
The app doesn't update the screen to show the UIActivityIndicatorView until the main run loop regains control. If your processing task blocks the main thread, the no UI updates will take place until it is finished. You should do your processing asynchronously.
When a rotation event happens, the willRotate... and willAnimateRotation... methods are called in one pass through the main run loop. So you block on the method before displaying the activity indicator.
To make this work, you need to push the method task over to another thread. That method would call back to this view controller when the work is completed so the view can be updated. I would put show the activity indicator in the willAnimateRotation... method.
What you need to do is forcing the UIActivityIndicatorView to start displaying even though the run loop won't be ended. One way is -
[self performSelector:#selector(animation) withObject:nil afterDelay:0]
-(void)startSpinner
{
NSAutoreleasepool *pool = [[NSAutorepleasepool alloc]init];
[indicatorView startAnimating];
[pool release];
}
So essentially, performSelector sets up a timer to perform the animation message on the current thread’s run loop. The timer is configured to run in the default mode (NSDefaultRunLoopMode). When the timer fires, the thread attempts to dequeue the message from the run loop and perform the selector. It succeeds if the run loop is running and in the default mode; otherwise, the timer waits until the run loop is in the default mode.
Please note that specifying a delay of 0 does not necessarily cause the selector to be performed immediately. The selector is still queued on the thread’s run loop and performed as soon as possible.

Activity Indicator not showing up

I have two issues with activity indicator:
1. Activity Indicator not showing up on UIViewController
I have activity indicator added in .xib file. On button click it should start animating. and when response from server is received, before going to next page it should stop animating.
I am doing it as follows:
activityIndicator.hidden = NO;
[activityIndicator performSelector:#selector(startAnimating) withObject:nil afterDelay:0.1];
[self.view bringSubviewToFront:activityIndicator];
....rest of code here....
activityIndicator.hidden = YES;
[activityIndicator stopAnimating];
Activity Indicator not showing up on UITableView
For table view I am doing it same way but on didselectrowatindexpath...
For tableview I also tried adding activity view to cell accessory, but still not showing up
In both cases activity Indicator is not showing up.
Please help
Thanks
If all this code is in one method or in response to one event, then none of the changes to the views are going be visible until you return to the event loop. You set the activityIndicator.hidden to NO and then set it again to YES before the UI has an opportunity to even refresh.
You also apparently stop the animation before you start it.
What you need to do is make the activity indicator visible here and start its animation. Then schedule the work to be done (start an asynchronous network connection, or put some work into a queue, or whatever it is you need to get done) and return from this method so that the UI can refresh, the indicator can be drawn, and the animation can actually start.
Then later at some point after the work is complete, you can hide the indicator and stop the animation. But you can't do all of that on the main thread within one single turn of the event loop. None of your changes will be visible because no drawing at all will happen here while this method is executing (assuming this is on the main thread)
I hope that makes sense?
Now I modified the code to this:
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
[self performSelector:#selector(saveClicked) withObject:nil afterDelay:0.1];
[self.view bringSubviewToFront:activityIndicator];
and it worked :)
May be, in tableView, instead of self.view , it will be self.navigationController.view ??

UIProgressView not showing on iPhone but shows in simulator

I have a UIProgressView called progTimer that I placed on my View Controller via Interface Builder. On the iOS simulator, the UIProgressView shows up just fine, but when I test it on my iPhone it doesn't appear.
Here is the code that modifies it.
- (IBAction)scanbtn
{
[progTimer invalidate];//cancels timer if currently running
progTimer=nil;
progNum = 0;
progTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 //timer to update the progress view
target:self
selector:#selector(progIncrease)
userInfo:nil
repeats:YES];
[self scan];
}
-(void) progIncrease
{
progNum = progNum + 0.01;
scanProg.progress = progNum;
if (progNum >=1)
{
[progTimer invalidate];
progTimer=nil;
//[alert show];
}
}
Any ideas? Thanks in advance. .
ALL: For some reason, I changed the style of the Progress View to "Bar" and it shows up fine. No idea what caused the initial problem, but now it works albeit having a different style bar.
My guess would be that on the device, the scanning process is moving so fast that you just can't see it. To make sure it's working ad and NSLog in the ProgressIncrease method to see how fast it scans. I had the same problem with my preloading notifiers, that were showing on the simulator but not on the device.
You call [self scan]; which seems to be the culprit. How long does that scan operation run?
Its very important to note that while any long running operation runs in main thread which is UI thread, no other code in the main thread loop will be executed(until that long running operation finishes) which means your timer function or even pressing button after pressed once pressed won't work. You need to use either NSOperation and dispatch queue or create a thread and run the scan operation in the background and send notifications to main thread using NSMachPort or NSObject performSelectr:OnThread:... etc for it to update the progress bar.
By the way, the scanning process and the progress bar has no relation at all; The progress bar should ideally update how much of scanning is done which means the scanning process must inform progress bar to update that also in main thread if scanning is running in secondary thread.

Yet another question about showing UIActivityIndicator

I had the UIActivityIndicatorView working fine in simulator and other 3.0 devices in my app. But I found out that it was not spinning (or showing) in the new iphone 4. Basically I need to show the activity indicator when a button is clicked and hide it when the button click event is complete. I was using the approach below.
[NSThread detachNewThreadSelector: #selector(spinBegin) toTarget:self withObject:nil];
from this link. As mentioned, it correctly spins the activity indicator on all except 4.*.. not sure why. To get around this, I also followed another approach something like (from developer.apple.com)
`
(IBAction)syncOnThreadAction:(id)sender
{
[self willStartJob];
[self performSelectorInBackground:
#selector(inThreadStartDoJob:)
withObject:theJobToDo
];
}
(void)inThreadStartDoJob:(id)theJobToDo
{
NSAutoreleasePool * pool;
NSString * status;
pool = [[NSAutoreleasePool alloc] init];
assert(pool != nil);
status = [... do long running job specified by theJobToDo ...]
[self performSelectorOnMainThread:
#selector(didStopJobWithStatus:)
withObject:status
waitUntilDone:NO
];
[pool drain];
}
`
The problem with this was that, it is showing the acitivityVIewIndicator spinning correctly (at least on the simulator) but after it stops, the built in activity indicator in the top bar (where it shows the battery% etc) is still spinning.
I'm new to objective C. I have finished my app completely but for this silly thing. I realize there is no way to display UIActivityView without starting another thread. and finally, just to rant, I don't understand why they have to make it so complicated. I mean they knew it was going to have this problem, why not provide a sample code everyone can use rather than deriving their own solutions.
Finally, can anyone please provide me with a direction or some sample code. I would really appreciate it. I have been searching for a few hours now and have not found anything really that works!
Why are you starting/stopping the indicator on a separate thread? Any methods you send to your UIActivityIndicatorView must be sent on the main (UI) thread.
Any events sent by a button pressed will automatically be run on the main thread. If you're using background threads to complete the process, you could do something like:
- (IBAction)buttonPressed:(id)sender {
// This runs on the main thread
[activityIndicator startAnimating];
[self performSelectorInBackground:#selector(inThreadStartDoJob:) withObject:theJobToDo];
}
- (void)inThreadStartDoJob:(id)theJobToDo {
// Set up autorelease pool
...
// Run your long-running action
...
// Stop the spinner. Since we're in a background thread,
// we need to push this to the UI Thread
[activityIndicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
Edit: As for the activity indicator in the top bar (where the battery is), doesn't this automatically start/stop based on network activity?

Issues with NSOperationQueue and dealloc being called and crashing App

I've created an NSOperation in the queue like so:
ImageLoadingOperation *operation = [[ImageLoadingOperation alloc] initWithImageURL:url target:self action:#selector(didFinishLoadingImageWithResult:)];
[operationQueue addOperation:operation];
[operation release];
And this works fine but if the view gets popped before the operation finishes the App crashes with "EXC_BAD_ACCESS"
I've tried to cancel the the operation Queue by calling cancelAllOperations but as its already in process it doesn't prevent the App from crashing. The docos say that if the operation is running it is up to the operation to detect that it has been canceled and respond appropriately but not too sure how I would implement this?
Any ideas?
It is a general problem for View calling some network and then callback.
My solution is you can retain the view before you call the operation. And then, when the operation finishes, you release the view.
- (void)longTask {
[self retain];
}
- (void)longTaskDidFinish {
// do something if you want
[self release];
}
You will have to either override the "cancel" operation in your ImageLoadingOperation class, or have your ImageLoadingOperation add itself as KVO observer to the "cancelled" property. There - you can intelligently cancel your operation in such way that it won't crash.
Also, if your ImageLoadingOperation runs in the background, it would be wiser to defer your access to the views somehow to the main thread (where all drawing takes place). You could use a dispatch_sync(dispatch_get_main_queue(), ^{}); or even performSelectorOnMainThread for actual access to the related view.
You see - the whole point of using an operation queue is to remove dependencies, and let things run in parallel, but your operation must synchronize with the view-system changes, and that must be designed for completeness and robustness.
You could retain the view before the callback of operation is called, as vodkhang mentioned above. But that will prolong the life of the view unnecessarily because since the view is popped you don't want the operation to continue any more.
Here is a sketch about what you should do to respond to the cancel command:
- (void)start{
if(self.isCancelled){
[self markAsFinished];
return;
}
//start your task asynchronously
}
//If you want to cancel the downloading progress immediately, implement your own 'cancel' method
- (void)cancel{
[super cancel];
if(self.isExecuting){
{
......
cancel load process
......
}
[self markAsFinished];
}
}
- (void)markAsFinished{
......
change 'finished' to YES' generate KVO notifications on this key path
change 'executing' to 'YES'; generate KVO notification on this key path
......
}
This sketch is based on ASIHTTPRequest networking library, and
there is an official guide on how you should respond to cancel command.