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.
Related
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.
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.
The object is deallocated in ARC mode and causes crash. My code below;
BlockAlertView* alert = [BlockAlertView alertWithTitle:title message:message];
[alert setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
[alert addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
}];
[alert show];
It appears the correct alert view(which is custom UIView) but when I click one of the button it crashes.
The crash log;
2015-04-07 22:28:17.065 Test[3213:781570] <BlockAlertView: 0x16bb4160>
2015-04-07 22:33:01.746 Test[3213:781570] *** -[BlockAlertView performSelector:withObject:withObject:]: message sent to deallocated instance 0x16bb4160
Here is the source code of BlockAlertView;
BlockAlertView on Github
For now I can't estimate any of the clues for this and makes me old.
Any input will be much appreciated!
Presumably, that code worked correctly before it was converted to ARC.
To fix it, you'll need to create a strong reference to self in the -show method, and release this reference in -dismissWithClickedButtonIndex:animated: (where you see [self autorelease] commented out).
You can do this with a simple instance variable:
id _selfReference;
Assign self to _selfReference in -show:
- (void)show
{
_selfReference = self;
...
and then set _selfReference to nil in -dismissWithClickedButtonIndex:animated: in the two places where you see [self autorelease] commented out.
Assign your alert object to live somewhere beyond your current function. One simple possibility is to just make it an instance variable. If that's not practical, create an instance NSMutableArray *retainedItems; which you allocate/init, and stuff this into.
Looks like a design flaw in that project. The class is poorly named BlockAlertView as it's not actually a subclass of UIView. If it were a view and it was added to the view hierarchy then the view hierarchy would ensure it stayed alive whilst being viewed. As it is the view is kept alive but the object that created the view BlockAlertView is not held onto by anything and by the time the actions are called BlockAlertView is long gone.
This will require you to keep a strong ivar around to reference this "controller" object and it would be sensible to nil out that ivar in the completion blocks.
BlockAlertView *alertController = [BlockAlertView alertWithTitle:title message:message]; {
[alertController setCancelButtonWithTitle:NSLocalizedString(#"No", #"No Button") block:nil];
__weak __typeof(self) weakSelf = self;
[alertController addButtonWithTitle:NSLocalizedString(#"Yes", #"Yes Button") block:^{
//Do Something
weakSelf.alertController = nil;
}];
[alertController show];
}
self.alertController = alertController;
I have a parent detail view controller that provides common custom functions to child detail views.
The parent includes two custom functions.
One function triggers a background save to two NSManagedObjectContexts that saves a main MOC immediately to free up UI, then saves a private MOC. Fairly standard setup pre iOS 8.
The following function presents a UIAlertView to confirm the save was successful. This includes code to automatically dismiss after a set amount of time (about half a second).
This all works fine running iOS 7, both on device and on simulator.
This causes a crash when running iOS 8, both on device and on simulator.
The problem exists for only one of the five child detail views. Following a detailed side by side comparison I confirm that these each have identical code blocks and methods.
I have break points inserted into the two custom functions. The save works fine, but the code crashes after trying to present the UIAlertView, as mentioned only when running iOS 8. The debugger steps into machine code that I do not understand. The attempted save does not persist.
If I comment out the alert view, the save persists, but obviously I no longer have the alert view for the user.
Any suggestions?
UPDATE
Think I may have found a solution... of sorts... not definite yet...
UIAlertController and this article by NSHipster
Augmenting the UIAlertView code in my parent detail view controller resolves the problem.
Within my message method I now check whether iOS responds to the UIAlertController class, and if it does instantiate a UIAlertController, otherwise instantiate a UIAlertView.
- (void)message {
...other code...
if ([UIAlertController class]) { //checking whether iOS responds to the UIAlertController class
UIAlertController *alert = [UIAlertController alertControllerWithTitle:titleComplete
message:messageComplete
preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:alert animated:YES completion: ^(void){
[self performSelector:(#selector(dismissAlertController:)) withObject:alert afterDelay:durationMessageCompleteSave];
}];
// [self dismissViewControllerAnimated:YES completion:nil];
// display time on screen too short using just the dismissViewController above,
// so add into completion handler in call to presentViewController...
} else {
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:titleComplete
message:messageComplete
delegate:self
cancelButtonTitle:nil//self.localisedAlertButtonRemain
otherButtonTitles:nil];//buttonOther, nil];
[alertView setTag:010];
[alertView show];
[self performSelector:(#selector(dismissAlertView:)) withObject:alertView afterDelay:durationMessageCompleteSave];
}
}
...
- (void)dismissAlertView:(UIAlertView *)alert {
[alert dismissWithClickedButtonIndex:0 animated:NO];
}
- (void)dismissAlertController:(UIAlertController *)alert {
[alert dismissViewControllerAnimated:YES completion:nil];
}
This is my code
audioViewController *voiceRecorder = [audioViewController sharedManager];
[voiceRecorder stopRecording];
NSString *msg = [NSString stringWithFormat:#"Want to logout?"];
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Info"
message:msg
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
alert.tag = 100;
[alert show];
I am calling sharedManager in one of my view controller. The problem is, my alertview runs before sharedManager method executes, if you check my code, i have called "StopReording" method, but when i run the code, it works after showing alert. Anyone has idea, how do I show alert only after the method returns something.?
You seem to be confusing yourself about method run order and alert presentation order. The methods run in the order specified by your code, they must. What you see on screen is 2 alerts, one (stop) presented first, the the other (logout) presented immediately after.
Generally, you shouldn't show 2 alerts at the same time. Certainly not if they relate to different things.
Present your first alert, then wait for the answer to be received (using the delegate methods). Once you have the users answer, then decide what to do next and present the second alert or continue with some other operation.