UIAlertAction handler late response in Objective-C - ios

I want to change value of varibale(BOOL) in handler block when user tapp on any button yes or no. But my problem is that handler executed at last when after other code also executed and value of variable changed after code executed.Here is my code..
isValidated = NO;
self.lblError.hidden = NO;
// Alert style
NSLog(#"First log");
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Already have absense registration at same time." message:#"Do you want to save this registration?? " preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *YesAction = [UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){
isValidated = YES;
self.lblError.hidden = YES;
NSLog(#"Second log");
}];
[alertController addAction:YesAction];
UIAlertAction *NoAction = [UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){
//do something when click button
isValidated = NO;
self.lblError.hidden = NO;
}];
[alertController addAction:NoAction];
[self presentViewController:alertController animated:YES completion:nil];
NSLog(#"Third log");
required output:
First log
Second log
Third log
But here is output that recieved:
First log
Third log
Second log
Here "Second log" executed after "Third log" but i want that compiler wait for execution of handler block then go forward..
Please help me..

If you block the main thread, waiting for the alert to be dismissed, you will create a deadlock, as the touch events are processed on the main thread, and you are blocking it.
There are a few different ways to handle delayed execution, and you should choose whichever you think best fits your situation.
Method 1
The simplest thing to do is to put whatever code has to run after the user chooses right into the alert action handler.
Method 2
Use a dispatch group and a dispatch_group_notify() invocation to setup a block that will run as soon as an action is chosen.
Setup:
Before creating the alert:
dispatch_group_t postAlertGroup = dispatch_group_create();
dispatch_group_enter(postAlertGroup);
Inside both alert action handlers:
dispatch_group_leave();
After displaying the alert:
dispatch_group_notify(postAlertGroup, dispatch_get_main_queue(), ^{
// Post alert code goes here
});

This is the normal behaviour.
As the first log is printed before the object is created so it is being logged first, and the third log is printed after the ``alertviewcontroller is shown. Meanwhile the second Log is printed as the ACTION of the alertviewcontroller.
So as in runtime, this happens in a very short time, (alert being created and shown), so the time between first and third log is too little, BUT the second log is dependent on Users interaction.
So First and Third log is being printed just when the alert is drawn on screen, thus there is no way you can interact between that time, and log the second by your interaction.
Hope this clears up.

Related

HomeKit: "Error adding accessory The operation couldn’t be completed" and "Error adding accessory The operation couldn’t be completed"

EDIT: I get the following error codes:
Error adding accessory The operation couldn’t be completed.
(HMErrorDomain error 2.)
And:
Error adding accessory Failed to start pairing with the accessory [ name = xxxxx, providedName = xxxxx, uuid = xxxxx-xxxxx-xxxxx-xxxxx-xxxxx, identifier = xxxxx, configuration-app-id = (null), home = (null), bridge = (null) ]
Both with number 2.
What I don't understand is why on the HMCatalog app this works. What's wrong with my code? It works fine on the Accessory simulator but not on the real accessory (the real accessory is added only via the HMCatalog app but not my custom app).
Actual Behaviour:
add accessory from my app (works the first time)
reset accessory and then re-add it (does not work and gives pairing error in screenshot below). However when it does give those errors if I use the Apple example HMCatalog it does work.
And sometimes:
Expected results:
adds accessory from my app too without pairing error
This is my add accessory code:
[self.home addAccessory:self.accessory completionHandler:^(NSError *error) {
NSLog(#"in adding for accessory %#", self.accessory.name);
if (error) {
NSLog(#"Error adding accessory %# %li", error.localizedDescription, (long)error.code);
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"Pairing error"
message:error.localizedDescription
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:NSLocalizedString(#"OK", #"OK action")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
NSLog(#"OK action");
}];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
}
else{
// TODO: Tweak this
NSLog(#"Added to home");
[self dismiss:nil];
/**
[homeSweetHome assignAccessory:accessory toRoom:nil completionHandler:^(NSError *error) {
if (error) {
NSLog(#"Failed adding accessory %# to room %#", accessory.name, room.name);
}
}];**/
}
}];
EDIT: per Tushar Koul's comment above, it looks like you need to ignore the discoveredAccessories array on the browser and instead construct your own array of objects from the accessoryBrowserDelegate (-accessoryBrowser:didFindNewAccessory and -accessoryBrowser:didRemoveAccessory).
After telling the browser to start searching, any accessories currently available will be passed to those methods.
HMErrorCode 2 is not found (see apple docs). This means that the accessory pointer that you have isn't valid anymore. This can be caused by grabbing an accessory object and then telling the accessory browser to start looking for accessories. It might also happen if the browser is deallocated before you add the accessory.
Make sure that you are getting a new HMAccessory for the HMAccessoryBrowser before you try to add the accessory to your home. If you can share more of the code showing where the HMAccessory that you're adding is coming from, I might be able to help more.
After 5 different combinations and several hours of testing, here are my findings -
HMAccessoryBrowser's instantiation should happen way before we start searching for homekit devices.
I had this in my viewDidLoad in consecutive lines and it DID NOT work a 100% of the time
self.accessoryBrowser = [HMAccessoryBrowser alloc] init];
[self.accessoryBrowser startSearchingForNewAccessories];
E.g. If you use it in a view controller then that view controllers code should look like -
-(instanceType)init{
if(self = [super init]){
self.accessoryBrowser = [HMAccessoryBrowser alloc] init]; }
}
-(void)viewDidLoad{
[self.accessoryBrowser startSearching];
}
So I reckon you should initialise HMAccessoryBrowser before.
The instance of HMAccessoryBrowser should be alive until the addition of accessory to home is complete.
Do not call stopSearchingForNewAccessories until the addition of accessory to home is complete.
So talking about #2 and #3
you find the device after calling startSearchingForNewAccessories , then you call [self.home addAccessory: completion:] on it.
Until you complete this operation successfully, DO NOT call stopSearchingForNewAccessories and keep the instance of HMAcccessoryBrowser alive.
I was searching for my homekit accessory, on finding it I would stop searching and then try to add the accesory to my home. I would get the ErrorCode 2 almost every single time. Once I stopped calling stopSearching i was seeing better results
I have my doubts on using the object reference from the discoveredAccessories array. Instead, I would recommend using the object received from the delegate callback
- (void)accessoryBrowser:(HMAccessoryBrowser *)browser didFindNewAccessory:(HMAccessory *)accessory

How to handle displaying of UIAlertView iOS

Does UIView receive any events when an alert appears? Are there existing events like lostFocus?
I know that it is possible to override the show method of the UIAlertView, but I wonder whether there is an approach to handle it directly from the top view of a hierarchy?
I've had to do this, and it's a huge pain.
Put this in your View Controller. (UIAlertView was deprecated since the release of iOS 8)
UIAlertController *someController = [UIAlertController alertControllerWithTitle:#"someTitle" message:#"someMessage" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *someAction = [UIAlertAction actionWithTitle:#"someTitle" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
// your code here for THE ACTION
}];
[someController addAction:someAction];
[self presentViewController:someController animated:YES completion:^{
// your code here for AFTER THE ALERT PRESENTS
}];
You may add as many Actions as you would like. Just some notes to go over:
The AlertController's style is either UIAlertControllerStyleAlert if it's an Alert Box, or UIAlertControllerStyleActionSheet if you want an Action Sheet from the bottom of the screen.
The block for the someAction's handler parameter needs to have the parameter action as a UIAlertAction*.
The completion block takes no parameters, and is called when the alert presents.
The hanler block is called when the user taps on their choice in the alert box or action sheet.
Refer to the Apple Documentation for more info about the UIAlertController class or the UIAlertAction class.
For more info about Objective-C blocks, visit fuckingblocksyntax.com, or its more work-friendly counterpart, goshdarnblocksyntax.com.

How to know if my keywindow.rootViewController is an Alert?

I'm having a problem to find out if my keywindow.rootViewController is an UIAlertController object. This must be a very simple thing to do, but I don't know what is wrong with my code:
UIViewController *rootViewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if( [rootViewController isKindOfClass:[UIAlertController class]]){
// Do something
}
Why I never enter in that IF STATEMENT, even when I can see on the debug that the view of this controller is an _UIAlertControllerView*? There is any other way to check if my key window is an Alert?
Thanks.
I assume what you want to do is be able to check if an alert is showing from any view controller in your application, but you're not sure which controller is displaying the alert and don't have access to the alert itself.
I've had to deal with this a few times. In previous versions of iOS you were able to iterate through all the subviews of a window to check if an UIAlertView was on the top, but with the changes to alerts in iOS8 this not longer works because all the delegates are deprecated and apple now recommends you use UIAlertController instead of UIAlertView. In any case, all the techniques were dependent on using certain versions of iOS and I've found them to be extremely unreliable.
What I use now is a singleton that keeps track of how many alerts are displaying. The singleton has a method that returns the current number of alerts displaying, a method for adding one, and a method for subtracting one.
This is implemented by adding one to the singleton when you present the UIAlertController:
[self presentViewController:alert animated:YES completion:^(){
AlertSingleton *muhInstance = [AlertSingleton sharedInstance];
[muhInstance addOne];
//Anything else for completion
}];
And then subtracting one with every possible action choice you add to the alert like this:
UIAlertAction *myAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
AlertSingleton *muhInstance = [AlertSingleton sharedInstance];
[muhInstance removeOne];
//Any Other alert actions
}];
Now you can know anywhere in your application if an alert is showing by checking if the count in the singleton is greater than zero like this:
if ([[AlertSingleton sharedInstance] alertCount] > 0) {
//There is an alert showing
//Your code here
}
I've found this technique to be very reliable for tracking alerts.

Fatal error handler in iOS app

When something goes so horribly wrong that my app can't continue and needs to exit, I want to pop up an alert box to the user, and then close the app when they tap the OK button. Sounds simple enough, right?
But here's the problem: my fatal error handler gets called by a 3rd party library (I don't have their source code). I give them a pointer to my fatal error handler on initialization, and when they encounter a fatal error they simply call that routine and expect it to never return. If it returns, the 3rd party library will assume I've handled the error and it will continue on its way (possibly corrupting data because things are now in an inconsistent state). I could just exit the application at the end of my error handler (which is what they expect), but I want to be able to display a message to the user first to tell them what the problem is.
Unfortunately, if I just do:
-(void)fatalErrorHandler:(NSString *)msg
{
// Log the error and shut down all the things that need
// to be shut down before we exit
// ...
// Show an alert to the user to tell them what went wrong
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened. This is no good.
I need to NOT return from fatalErrorHandler. Ever. But since I'm on the main thread, the UIAlertView won't appear until fatalErrorHandler returns. A catch-22.
Any ideas on how I can show an alert to the user without returning from my fatal error handler?
I don't know if this would work, but what about starting a while loop with a sleep in its body for, let's say, 1 second each cycle? The while would exit when a Bool variable would have been set to YES, maybe from the alertViewDelegate.
From what you wrote "fatalErrorHandler returns right after [alert show], which tells the 3rd party library that I've handled the error and it will continue on as if nothing has happened."
I guess what you actually need is to pause everything when the fatalErrorHandler method is called. To achieve this, you can stop all NSTimer, queued methods etc. before displaying alertView.
Alternatively, you can display alertView via a different thread, and then use usleep(long long time) to pause the thread where fatalErrorHandler is in.
Okay, wesley6j's answer gave me an idea. Here's what I came up with:
-(void)fatalErrorHandler:(NSString *)msg
{
// Pop up an alert to tell the user what went wrong. Since this error
// handler could be called from any thread, we have to make sure this happens
// on the main thread because it does UI stuff
[self performSelectorOnMainThread:#selector(showMessage:) withObject:msg waitUntilDone:YES];
// Now, if we're NOT on the main thread, we can just sleep forever and never
// return from here. The error handler will exit the app after the user
// dismisses the alert box.
if (![NSThread isMainThread])
Sleep(0x7fffffff);
else
{
// OTOH, if we ARE on the main thread, we have to get a bit creative.
// We don't ever want to return from here, because this is a fatal error
// handler and returning means the caller can continue on its way as if
// we "handled" the error, which we didn't. But since we're on the main
// thread, we can't sleep or exit because then the user will never see
// the alert box we popped up in showMessage. So we loop forever and
// keep calling the main run loop directly to let it do its processing
// and show the alert. This is what the main run loop does anyway, so
// in effect, we just become the main run loop.
for (;;)
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate date]];
}
}
}
-(void)showMessage:(NSString *)msg
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error" message:msg delegate:self cancelButtonTitle:#"Close" otherButtonTitles:nil];
[alert show];
[alert release];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
{
exit(-1);
}
This works perfectly and does exactly what I need.

Displaying UI elements such as alerts from multiple threads

I have a simple application which was working fine until I added some code which launches a new thread at some point and then tries to show an alert from that thread. Now, the app crashes whenever the code for showing the alert is hit.
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
My question is: is it possible to display UI elements such as alerts from multiple threads on iOS?
You definitely don't want to be displaying an alert (or anything UI-related) from any thread other than the main thread. I'd suggest putting your alert code in a function and call one of the performSelectorOnMainThread calls.
- (void) showAlert
{
UIAlertView * addAlert = [[UIAlertView alloc] initWithTitle:#"New alert"
message:#"Example alert"
delegate:nil
cancelButtonTitle:#"Cancel", otherButtonTitles:#"OK", nil];
[addAlert show];
[addAlert release];
}
// ... somewhere in the worker thread ...
[self performSelectorOnMainThread:#selector(showAlert) withObject:nil waitUntilDone:NO];
I'm pretty sure that the main thread is the only thread that should be the one that handles UI recognition/drawing things to screen. What I would do if I were in your position would to be either use KVO notifications or implement a protocol that some class subscribes to. Going the protocol route, when you get to the alerting part of your code merely have that thread call its protocol method, the subscribing class will be alerted by having the delegate function triggered and you can easily present whatever you have to in that view via the main thread.
Hope that helps.
Better and simple and just one line approach is to call performSelectorOnMainThread method with alertView.
In your case try this line
[addAlert performSelectorOnMainThread:#selector(show) withObject:nil waitUntilDone:YES];
instead of
[addAlert show];
It will call show method of Alertview on main thread. No need to write any extra method.

Resources