Why does my UIAlertView only appear after a delay? - iphone

In example code below UIAlertView is show after delay, but i need display it immediately
//metoda zapisuje komentrz na serwerze
-(void) saveAction {
UIAlertView *progressAlert = [[UIAlertView alloc] initWithTitle:NSLocalizedString(#"imageGalleries.sendAction", #"") message:#" " delegate:self cancelButtonTitle:NSLocalizedString(#"alert.cancel", #"") otherButtonTitles:nil];
[progressAlert addSubview:progressView];
[progressAlert show];
// some long performance instructions
}
- (void)loadView {
[super loadView];
self.navigationItem.rightBarButtonItem = [NavButton buttonWithTitle:NSLocalizedString(#"sendImage.saveButtonTitle", #"") target:self action:#selector(saveAction)];
progressView = [[UIProgressView alloc] initWithFrame: CGRectMake(30.0f, 80.0f - 26, 225.0f, 10.0f)];
}
Why UIAlertView do not show immediately when I call saveAction?

If the “long performance instructions” following the alert code run on the main thread, they will block the alert from appearing. Read something about Cocoa run loops, that should make things more clear. (Essentially it could be said that all the UI instructions in your method are not performed immediately – they have to wait for the method to end, and then the main run loop picks them up and runs them.)
The code could better look like this:
- (void) startSomeLongOperation {
[self createAndDisplayProgressSpinner];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_NORMAL, 0), ^{
// …do something that takes long…
dispatch_async(dispatch_get_main_queue(), ^{
[self dismissProgressSpinner];
});
});
}
This moves the long operation into background, so that the main thread can continue executing immediately.

Have you considered using MBProgressHUD for long lasting operations? It'll wrap those calls into separate threads for you and is very flexible from functionality and UI perspectives

I don't think you need to call
[progressAlert addSubview:progressView];
Aside from that, it really should just appear...

If you show the alert in background thread, it will also have a delay.

Related

UIActionSheet taking long time to respond

I am creating a UIActionSheet on actionSheet:clickedButtonAtIndex delegate method.
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex
if(buttonIndex == 1){
[self.myFirstView removeFromSuperview];
if (!self.mySecondView) {
[[NSBundle mainBundle] loadNibNamed:#"MySecondView" owner:self options:nil];
}
[self.mySecondView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview: self.mySecondView];
UIActionSheet * action = [[UIActionSheet alloc]initWithTitle:#""
delegate:self
cancelButtonTitle: nil
destructiveButtonTitle: deleteContacts
otherButtonTitles: cancel, nil];
action.tag = 102;
[action showInView:self.view];
[action release];
}
I handle the click event of this UIActionSheet in the exact same method as above.
if(actionSheet.tag == 102){
if(buttonIndex == 0){
if([[NSBundle mainBundle] loadNibNamed:#"MyThirdView" owner:self options:nil]) {
[self.myThirdView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview:self.myThirdView];
}
[self.mySecondView removeFromSuperview];
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
}
}
The problem I am facing is that, the UIActionSheet takes too much time to respond. When I click on the UIActionSheet button, its in a frozen state for 2 or 3 seconds, before myThirdView loads. I am not able to understand, whats the response delay in this case as the first thing I do in the UIActionSheet button click event method is to load myThirdView. The rest of the code is executed only after the code to load the myThirdView. But even the first line of code seems to execute after a delay.
Any suggestions?
this is perhaps due to this
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
make an other methods and do this in that method. like this
[self viewRemover];
and in viewRemover
-(void) viewRemover
{
[self performSelector:#selector(RemoveView) withObject:self afterDelay:3.0];
}
so your code will be like this now
if(actionSheet.tag == 102){
if(buttonIndex == 0){
if([[NSBundle mainBundle] loadNibNamed:#"MyThirdView" owner:self options:nil]) {
[self.myThirdView setFrame:CGRectMake(0, 0, 320, 480)];
[[UIApplication sharedApplication].keyWindow addSubview:self.myThirdView];
}
[self.mySecondView removeFromSuperview];
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
[self performSelectorInBackground:#selector(viewRemover) withObject:nil];
}
}
User interface actions run in the main thread and only occur when your method ends. So, MyThirdView will not appear until the other instructions have finished. The only thing I can figure is delaying that is:
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton.target];
If you are doing any heavy calculation or net conection, for sure that is the reason.
OTOH, I think you'd better modify that line:
[self.doneButton.target performSelector:self.doneButton.action withObject:self.doneButton];
if you want to simulate a button touch action.
Question. Does the UIActionSheet freeze, or does it disappear and the 3rd view isn't visible for 2-3 seconds?
This could be due to 1 of 2 problems.
If the entire action sheet freezes, then you are doing some heavy lifting when you init that 3rd view, you are loading some core data, or a lot of assets, or something that is taking a long time. If this is the case, you'll need to reformat HOW you load that 3rd view. I'd suggest pushing any heavy loading to the background (this means if you have a lot of images in your xib, you may need to load them in code).
The other possibility, is you are adding the 3rd view BELOW the 2nd view, and then not hiding the 2nd view for 3 seconds (done by performing the selector with a delay). If this is the case, simply remove the delay.
I made a couple of classes to help me time executions and find the bottlenecks in my code, it seems like they might help you now.
http://forrst.com/posts/Code_Execution_Timer_for_iOS_Development-dSJ
How big is your third view. if the nib file needs to load too much, you may be waiting for a lot to happen, also if you have to change some UI elements and your blocking the UI thread, you will hault your app until a timeout occurs and the app will shift some things to compensate..
the route I take with this is dispatch_async and dispatch_sync
// This will release the UI thread which may be blocking your calls.
// You can use any of the DISPATCH_QUEUE_PRIORITY_.... values to set how important this block is.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
// this command will get added to the stack and return without a hitch.
// the block will be run the next time the main runloop ends.
dispatch_async(dispatch_get_main_queue(), ^{
// do some UI work that can happen without a hitch, and does not impact the calling block
});
// this command will act much the same. but will pause the background execution
// of this block until the runloop has executed this and returned.
dispatch_sync(dispatch_get_main_queue(), ^{
// do some UI work that must be completed to continue.
});
});
doing too much in the UI thread will pause the execution of things that get stacked on the queue. Shipping all code to the background thread and only skipping to the UI thread when you need to alter the UI is a better and more responsive way to code your iphone app.
I hope this helps :)
in Swift 4:
I wrapped the code with this block
DispatchQueue.main.async {
// your code to show action sheet.
}
for example
DispatchQueue.main.async {
let alert = UIAlertController(title: "Options", message: String("What do want to do?"), preferredStyle: UIAlertController.Style.actionSheet)
alert.addAction(UIAlertAction(title: "Open", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
self.myOpen(code: self.codes[indexPath.row])
}))
alert.addAction(UIAlertAction(title: "Delete", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
self.myDelete(indexPath: indexPath)
}))
alert.addAction(UIAlertAction(title: "Cancel", style: UIAlertAction.Style.default, handler: {(action:UIAlertAction!) in
print("Cancel")
}))
self.present(alert, animated: true, completion: nil)
}

Multithreading in iOS - how to force a thread to wait for a condition?

I'm creating an application that fetches a set of results from a database - I use MBProgressHUD to show the progress of the query with an animation. The method I use calls the animation while executing a method in another thread and once it's done, it hides the animation. My question is, after calling:
[HUD showWhileExecuting:#selector(getResults) onTarget:self withObject:nil animated:YES];
I would like to, if there are no results, display an alert stating this, and if there are, load the next view. So far, I have this code:
[HUD showWhileExecuting:#selector(getResults) onTarget:self withObject:nil animated:YES];
if(self.thereAreEvents) {
[self performSegueWithIdentifier:#"searchResults" sender:self];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No results" message:#"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
self.thereAreEvents gets set at the end of the getResults method. However, since that method gets called in another thread this line of execution continues and shows the alert, even though there are events in the database.
So, from here, I have two questions: What is the easiest way to implement a wait-signal mechanism in iOS and what is the most efficient way to implement this sort of mechanism in iOS?
Thanks!
You can use a busy wait loop for a quick and dirty solution:
__block BOOL finished = NO;
dispatch_async(/* global queue */, ^{
// …
finished = YES;
});
while (!finished) /* waiting */;
In “real” code it’s better to use a semaphore:
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_async(/* global queue */, ^{
// …
dispatch_semaphore_signal(semaphore);
});
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
dispatch_release(sempahore);
This is better than the busy loop because the blocked thread does not eat CPU time.
The best solution is to keep from blocking and redesign your code to work asynchronously. In your case, you should display a spinner and start downloading data. When the data is finished downloading, you should receive an asynchronous callback (either through a block or a target/action callback) and display the results or show the error alert. Blocking with a busy loop or a semaphore is a poor man’s solution in this case.
You could also consider an NSConditionLock.
So it'd be something like this on thread 1:
[conditionLock lockWhenCondition:kConditionOkayToProceed];
[conditionLock unlockWithCondition:kConditionGettingResults];
[HUD show...]
[conditionLock lockWhenCondition:kConditionResultsFetched];
[conditionLock unlockWithCondition:kConditionOkayToProceed];
And in the HUD:
- (void)show...
{
[conditionLock lockWhenCondition:kConditionGettingResults];
// stuff here
[conditionLock unlockWithCondition:kConditionResultsFetched];
}
Though a much better solution would be to pass a block or a target/selector to the HUD that it is to perform when results are fetched.
EDIT: so you'd end up with code like:
[HUD showWhileExecuting:#selector(getResults)
onTarget:self
withObject:nil
animated:YES
performWhenFinished:
^{
if(self.thereAreEvents) {
[self performSegueWithIdentifier:#"searchResults" sender:self];
} else {
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"No results" message:#"Sorry, there are no results for your search. Please try again." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[alert show];
[alert release];
}
}];
And in the HUD:
- (void)showWhile... performWhenFinished:(dispatch_block_t)block
{
// all the other stuff you were going to do here, then
// eventually...
// if no guarantees, maybe just:
block();
// otherwise, if promised to dispatch to the main queue:
dispatch_async(dispatch_get_main_queue(), block);
}
With HUD having the extra intelligence to take a dispatch_block_t as a final argument and to call it when results are in (whether guaranteeing dispatch back to the main thread or otherwise).
This is your way:
Concurrency Programming Guide
Also: Synchronization
More sharp: Using Locks. I think the last one could give the best help.
Another simple approach, it's bad but works
NSAssert(![NSThread isMainThread], #"Do not run on MAIN thread");
while (yourCondition) { [NSThread sleepForTimeInterval:0.2]; }
Not sure if this is the best way to do it but I'd try using a while loop to observe some boolean with something like [NSThread sleepForTimeInterval:1]; inside the loop.
Maybe set a timeout as well.
you can use dispatch_time_t as..
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
<#code to be executed on the main queue after delay#>
});

Why doesn't the UIAlertView work?

I am testing this piece of code but when I run it, it does not trigger the UIAlertView. When the code hits If (ongoingGame = YES) and NSLog it jumps directly to the 'otherButtonTitles:nil' without executing the UIAlertView.
Can someone please explain to me why it does not trigger it?
-(IBAction)continueGame_button:(id)sender {
//=====CHECK IF THERE IS AN ON-GOING GAME, IF SO CONTINUE=====//
AccessCurrentGameData *isThereAnOngoingGameFunction = [AccessCurrentGameData new];
BOOL ongoingGame = [isThereAnOngoingGameFunction checkIfGameOngoing];
[isThereAnOngoingGameFunction release];
NSLog(#"+ + +continueGame_button+ + +");
NSLog(#"ongoingGame = %#\n", (ongoingGame ? #"YES" : #"NO"));
if (ongoingGame == YES) {
NSLog(#"++++++++++++++++++");
NSLog(#"++++++++++++++++++");
NSLog(#"++++++++++++++++++");
NSLog(#"++++++++++++++++++");
NSLog(#"++++++++++++++++++");
//
UIAlertView *continueGame = [[UIAlertView alloc] initWithTitle:#"Fortsätta spel"
message:#"Det finns ett aktivt spel, klicka Spela eller Tillbaka"
delegate:self
cancelButtonTitle:#"Tillbaka"
otherButtonTitles:nil];
[continueGame show];
[continueGame release];
}
exit(0);
}
You are assigning onGoingGame to YES, not comparing it to YES. Use == instead of =.
Your alert code is just fine I use that form (three lines - init, show, release) all of the time to do alerts.
I suggest that the exit(0) is the root of the problem. If you want to exit after the user closes the alert, you should assign a delegate which will close the app when the user taps on the close button. Use your code, but remove the exit(0). Then implement the UIAlertViewDelegate as follows:
-(IBAction)continueGame_button:(id)sender {
//=====CHECK IF THERE IS AN ON-GOING GAME, IF SO CONTINUE=====//
AccessCurrentGameData *isThereAnOngoingGameFunction = [AccessCurrentGameData new];
BOOL ongoingGame = [isThereAnOngoingGameFunction checkIfGameOngoing];
[isThereAnOngoingGameFunction release];
NSLog(#"+ + +continueGame_button+ + +");
NSLog(#"ongoingGame = %#\n", (ongoingGame ? #"YES" : #"NO"));
if (ongoingGame == YES) {
NSLog(#"+++++++++ ONGOING GAME +++++++++");
//
UIAlertView *continueGame = [[UIAlertView alloc] initWithTitle:#"Fortsätta spel"
message:#"Det finns ett aktivt spel, klicka Spela eller Tillbaka"
delegate:self
cancelButtonTitle:#"Tillbaka"
otherButtonTitles:nil];
[continueGame show];
[continueGame release];
}
}
- (void) alertViewCancel:(UIAlertView *)alertView{
//If you have other alerts, you may want to check the title of the alert to
//make sure that you only exit when THIS alert is dismissed
exit(0);
}
Dont' forget to add the <UIAlertViewDelegate> code to your header (.h) file.
You can also use - (void) alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex, if you want multiple buttons, with one of them being a specific "Quit" button.
Please note that Apple discourages using exit() in apps that are released to the App Store, and using it might get your app rejected.
You can try this line instead.
[[[[UIAlertView alloc] initWithTitle:#"this is my message" message:nil delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil] autorelease] show];
Also, I believe Apple does not advice using exit() within your application. They always want the user to use the "Home" button to exit an app. The exit() call is a hard exit and this might be the reason you are not seeing the Alert.
You should not release it immediately. And you exit the app even before the alert view gets a chance to display itself. :)
Your code will continue to run even when the alert view is visible.
Fixage
Remove the exit call
Don't release the alertview. Release it in it's owner's dealloc method.
Make the alert view an instance variable and add a retain property to it.
Initialize the alertview in its getter if it's not yet available.
Set it's attributes in the IBAction and show it.
Add the appropriate delegate methods.
If I wasn't writing this answer on an iPod touch I'd post some example code. You can find lots of such code with Google.
Also, if your app isn't English-only you should always use localization provided by Foundation. Otherwise you can get English text with default error messages and other UI elements.

view hierarchy refresh timing

I'm trying to add a progress meter, or other "I'm busy right now" notification to my view hierarchy right before doing some intense computation that will block the UI. My code looks some thing like:
//create view
[currentTopView addSubView:imBusyView];
//some initialization for the intense computation
[computation startComputing];
Unfortunately, my progress meter doesn't display until after the computation completes. It appears like the views aren't re-drawn until the run loop completes. I'm pretty sure that setNeedsDisplay and setNeedsLayout will also wait until the run loop completes.
How do I get the view to display immediately?
Redrawing only occurs when your code returns control to the run loop. So the easiest way would be for you to schedule the startComputing call with a zero delay. That way, it will be executed during the next run loop iteration (right after redrawing):
[computation performSelector:#selector(startComputing) withObject:nil afterDelay:0.0];
Be aware, though, that unless you do your computation in another thread you will not be able to update the UI during the computation.
If you are doing heavy calculations maybe spawning a new thread is a good idea.
Here I have an activityIndicator displayed and starts a large XMLParse operation in a background thread:
- (void) setSearchParser {
activityIndicator = [[ActivityIndicatorView alloc] initWithActivity];
[self.view addSubview:activityIndicator];
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
}
then the getSearchResults method:
- (void) getSearchResults: (SearchResultParser *) parser {
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
[parser startParser];
[self performSelectorOnMainThread:#selector(searchResultsReady:) withObject:[parser data] waitUntilDone:NO];
[pool release];
}
So firstly make a new thread:
[NSThread detachNewThreadSelector:#selector(getSearchResults:) toTarget:self withObject:[searchParser retain]];
this means that all code inside the getSearchResults will be executed on a different thread. getSearchResults also get's passed a parameter of "searchParser" thats a large object that just needs startParse called on it to begin.
This is done in getSearchResults. When the [parser startParser] is done, the results is passed back to the main thread method called "searchResultsReady" and the threads autorelease pool is released.
All the time it took from my parser began to it had finished, a gray view covered the screen an an activityIndicator ran.
You can have the small activityIndicator class I wrote:
-(id) initWithActivity {
[self initWithFrame:[self bounds]];
[self setBackgroundColor:[UIColor blackColor]];
[self setAlpha:0.8];
activityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityView.center = CGPointMake(160, 240);
[self addSubview:activityView ];
[activityView startAnimating];
return self;
}
- (void) dealloc {
[activityView release];
[super dealloc];
}
Hope it helps you out, even though threads seems a bit confusing, they can help to make the UI not freeze up, which is especially important on the iPhone.

Threading works in simulator but not on iPhone

I'm trying to display a UIActivityIndicatorView while background processing takes place.
The below simplified code works when I try it in the simulator(the alert is displayed)..but when I download it to my phone from Xcode, the background thread does not seem to get called at all. (the alert never gets displayed)
Any ideas?
-(void)viewDidLoad {
[self performSelectorInBackground:#selector(runInAnotherThread) withObject:nil];
}
-(void) runInAnotherThread {
NSAutoreleasePool *pool = [ [ NSAutoreleasePool alloc ] init ];
int i;
for(i=0;i < 1000 ;i ++){
NSLog(#"INDEX = %d", i);
}
[self performSelectorOnMainThread : # selector(backToMainThread ) withObject:nil waitUntilDone:NO];
[ pool release ];
}
-(void) backToMainThread {
UIAlertView *completeAlert = [[UIAlertView alloc]
initWithTitle:#"Back to main "
message: #"Success"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[completeAlert show];
[completeAlert release];
}
Have you you tried cleaning your build? I just ran your code on my device and simulator and it works as expected in both cases
Use NSOperation instead of raw thread manipulation. It abstracts all sorts of stuff for you (priority, autoreleasepools etc...). ? You can simply add some kind of delegate to your NSOperation subclass to get a callback when you need.
Thanks for replying so quickly!
It turned out that the issue was not in this code fragment at all. I was executing this code dependent on a value in the keychain. While my simulator's keychain has that value, my test iphone did not have this value.
Feel so silly for troubling all of you. But following up on the reply from nduplessis helped me narrow down the issue.