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.
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.
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.
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];
}
I'm writing unit tests for an application and want to check if a UIAlertController is presented in a specific scenario.
-(void)testBadLogin {
// enter username and password in UITextFields
self.viewController.usernameField.text = #"test#test.com";
self.viewController.passwordField.text = #"incorrect_pass";
[loginButton sendActionsForControlEvents: UIControlEventTouchUpInside];
// this isn't right
XCTAssertNotNil([self.viewController alertController], #"alertController should appear");
}
How do I check if a UIAlertController has been presented on top of the current view?
"XCTest is not meant to be used to test UI components." is not truly accurate. I am using XCTest for almost every UI testing and it works just fine. The correct answer shall be "Mocking".
I would use OCMock for mocking the tested view controller and "verify" that the method presentViewController... is called with the alert controller. This is a neat solution and works just fine. (You can even ignore that the alert controller is passed to this method and just test that the view controller has been passed the method presentViewController...)
It can also be done in this way:
Let's say we have a button which when tapped shows view controller:
- (void) didTapButton
{
UIAlertController* c = [UIAlertController alertControllerWithTitle:#"Title" message:#"Message"
preferredStyle:UIAlertControllerStyleAlert];
[self presentViewController:c animated:ANIMATED completion:nil];
}
Notice that ANIMATED parameter is not 'YES' or 'NO'. It is define in PrefixHeader as:
#define ANIMATED (getenv("runningTests") == NULL)
and runningTests is environment variable defined in Test target.
We don't want animation when executing unit/integration tests.
The test method looks like:
- (void) testButtonWillShowAlertView
{
UIApplication.sharedApplication.delegate.window.rootViewController = controller;
[controller.button sendActionsForControlEvents:UIControlEventTouchUpInside];
XCTAssertEqualObjects(controller.presentedViewController.class, UIAlertController.class);
}
Important line is
UIApplication.sharedApplication.delegate.window.rootViewController = controller;
Apparentely, rootViewController on UIWindow has to be set.
I have written a wrapper around UIAlertController for easier unit testing.
You can check if it is visible
XCTAssert(testableAlert.visible)
And you can also execute its actions
testableAlert.simulateAction("OK")
https://github.com/exchangegroup/TestableAlert
you can simply check the existence for UIAlertController By below code(objective c).
XCTAssertFalse(app.alerts.element.staticTexts[#"your alert message"].exists);
it will fail the test if alert is not presented, otherwise you can use
app.alerts.element.staticTexts[#"your alert message"].exists
with if or XCTAssertTrue .
XCTest is not meant to be used to test UI components.
Use Apple's UIAutomation JavaScript Library for this:
https://developer.apple.com/library/ios/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/UsingtheAutomationInstrument/UsingtheAutomationInstrument.html#//apple_ref/doc/uid/TP40004652-CH20
Docs for testing Alerts:
https://developer.apple.com/library/ios/documentation/ToolsLanguages/Reference/UIAAlertClassReference/index.html#//apple_ref/doc/uid/TP40009898
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.