Using UIAlertView while waiting for calculations/processing data - iphone

I've set up my iphone application in a tab layout, and I would like to perform some rather intense calculations (can take several seconds to get a result) when the user selects one of the tabs.
Originally, it would appear the iphone would just hang on the original tab while doing the number crunching.
I tried adding an UIAlertView as some eye-candy, but I'm getting a fade to grey for a few seconds, then after the computations are done, a quick appearance/disappearance of the View. What I want to see is the UIAlertView appear/animate when the user touches the tab, and then disappear once the calculations are done
- (void)viewDidAppear:(BOOL)animated
{
UIAlertView *baseAlert = [[[UIAlertView alloc] initWithTitle:#"Calculating" message:nil delegate:self cancelButtonTitle:nil otherButtonTitles:nil, nil]autorelease];
[baseAlert show];
UIActivityIndicatorView *aiv = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
aiv.center = CGPointMake(baseAlert.bounds.size.width /2.0f, baseAlert.bounds.size.height - 40.0f);
[aiv startAnimating];
[baseAlert addSubview:aiv];
[aiv release];
/*** calculation and display routines***/
[baseAlert dismissWithClickedButtonIndex:0 animated:YES];
}
I've already seen this post, but I can't seem to figure out how apply it to my case.

The easiest way to solve this is with blocks; First schedule calculations to separate thread using first block and when done dismiss alert view via block dispatched on main thread:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
// Do calculations
dispatch_async(dispatch_get_main_queue(), ^
{
[baseAlert dismissWithClickedButtonIndex:0 animated:YES];
});
});

You need to understand how the event loop works. When you call [baseAlert show], the alert view is added to the view hierarchy, but it isn't actually drawn to the screen until the current code block ends and control returns to the event loop. By doing your computation immediately after asking the alert view to show, you are preventing the alert from ever appearing.
It's kind of like writing a letter telling somebody you plan to paint your house, spending a week painting your house, then writing another letter saying you've done it, and THEN taking both letters and dropping them in the mailbox to be delivered at the same time.
If you have an expensive computation, the easiest way to handle it in iOS 4 and later is to place a block of code in a dispatch queue, so the work will be done in a background thread, and the main thread can still update the screen and respond to finger taps.
[baseAlert show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
// we're on a secondary thread, do expensive computation here
// when we're done, we schedule another block to run on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
// this code is back on the main thread, where it's safe to mess with the GUI
[baseAlert dismissWithClickedButtonIndex:0 animated:YES];
});
});

What is probably happening is that the "intense calculations" you're doing are being run in the same thread as where you're calling the UIAlertView. Setting a delegate for UIAlertView would set that up in a separate thread so that you don't need to worry about contention and whether the UIAlertView will show up before the calculations.
Alternatively, using a UIAlertView is a rather heavy handed approach - perhaps you could use some other interface element to indicate progress instead of rendering the app useless while you crunch some numbers?

Related

Obj-C, thread safe process with alertview / progress indication in a view, can be called from applicationWillEnterForeground <= iOS4?

I need to run some database processing when my app first starts once a day.
I elected to do this in my first view and showed an alertview with an activity indicator. I tried using NSThread detachNewThreadSelector to run the db processing. At the end of the function I used dismissWithClickedButtonIndex to dismiss the alert view.
I then called the view controller from applicationWillEnterForeground if the date permitted.
However, I'm getting an error Tried to obtain the web lock from a thread other than the main thread or the web thread. Crashing now, on the dismissWithClickedButtonIndex line.
I believe this is occurring due to a flaw in using an object like an alertview with a thread.
I need this to be iOS 4 compatible.
Can anyone point me at an alternative approach ?
You won't be able to create and work directly with the UIAlertView in a background thread.
Here's a skeletal idea of what you could do:
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Title" message:#"Message" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
// create your progress indicator in your alert view
[alertView show];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// do your db update
dispatch_async(dispatch_get_main_queue(), ^{
// update your alert view here.
});
});
The idea is to do your processing on a background queue and message back to the main queue for objects that are main-thread only (like UIAlertView.)

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.

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?

Iphone: stop execution of code when alert is shown in a viewWillAppear implementation

I have a working application
before the first view is loaded, i put an alert in the viewWillAppear method:
- (void)viewWillAppear
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"MyAppp" message:#"Application will connect to Internet. Continue?"
delegate:self cancelButtonTitle:nil otherButtonTitles:#"No, quit", #"Yes", nil];
[alert show];
[alert release];
}
I can get the clicks on the two button (Yes/No) correctly...
But...I would like code execution to stop and wait for an answer, but instead the code goes on, connects to the internet and retrieves data...
How do I prevent a view to load, based on a user input?
The viewWillAppear is a notification which allows you to complete some stuff before the view is shown, you can't avoid the appearing of the view here. You have to review your implementation.
Just break your one viewWillAppear method into two methods. Don't try to do it all in one chunk of sequential code.
The first method will launch the alert and then just exit/quit/return.
The second method can be called by the alert button response handler, and then finish loading the view only after it's been called by the alert handler, after the user has responded.
You may or may not have to save extra state information (in extra properties or instance variables instead of method locals) between the first and second methods.
the below can be used, i know its little old question but might be useful for others
..
[alert show];
while ((!alert.hidden) && (alert.superview != nil))
{
[[NSRunLoop currentRunLoop] limitDateForMode:NSDefaultRunLoopMode];
}
I solved it by showing the answer in the "ViewDidLoad", got a delegate to get which button was pressed and then processing the data ONLY if the user pressed "Yes"

UIActivityIndicator not working properly?

I have a problem regarding UIActivityIndicator. I applied [spinner startAnimating] at the IBAction on a button and then doing some process. After the process activityindicator should be stopped and then navigate to another view. But the activity indicator does not appear. When I remove the line "[spinner stopAnimating]" then the indicator appears but not at the instant button is pressed. It appears just before the other view loads, and apparently does not appear, I mean it does not appear but if we see very carefully then only it appears for an instant.
Thanx in advance for any answer.
Ole is pretty much correct, but there is a trick of you don't mind synchronous processing (often that it why you want to display the activity indicator in the first place).
First move your code that you want to process while the spinner is up to its own method. Then do
[spinner startAnimating];
[self performSelector:#selector(methodname) withObject:nil afterDelay:0];
The afterDelay:0 means on the next time through the run loop. That way the spinner gets started.
The animation will not start until your code returns control to the run loop. 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 (e.g. by starting an NSOperation).
you should run in perform selector .
for ex:
[self performSelector:#selector(animation) withObject:nil afterDelay:0]
-(void)animation
{
NSAutoreleasepool *pool = [[NSAutorepleasepool alloc]init];
[indicatorView startAnimating];
[pool release];
}
This is an old question. I leaving my answer here, so that might help someone to solve their problem.