UIAlertController takes 15 seconds to display when adding text field - ios

I am having a new weird error. The project is super simple, just a UINavigationController with a root view controller.
My problem is that when the user clicks on a toolbar item, it calls the following code:
- (IBAction)configurePressed:(id)sender
{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Capture Area"
message:#"Enter the name of the area that is being captured."
preferredStyle:UIAlertControllerStyleAlert];
[alertController addTextFieldWithConfigurationHandler:nil];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
UIAlertAction *defaultAction = [UIAlertAction actionWithTitle:#"Set"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
UITextField *captureArea = alertController.textFields.firstObject;
_captureArea = captureArea.text;
}];
[alertController addAction:cancelAction];
[alertController addAction:defaultAction];
[self presentViewController:alertController animated:true completion:nil];
}
Super simple right? Well, it takes 15 seconds to display the first time, then instantly on subsequent times. What is even weirder is that if I remove the line:
[alertController addTextFieldWithConfigurationHandler:nil];
so there is no text input box, it displays instantaneous. I have also confirmed that it is being performed on the main thread.
UPDATE #1: New observation, when it is waiting to display, the main thread seems to be blocked. The AlertController is being displayed over an MKMapView.
UPDATE #2: This does not happen on the simulator.
Anyone else experience this?

Related

Objective C - I would like UIAlertController to not be removed when button clicked

I would like to present a UIAlertController with 2 buttons.
One button should close the alert and the second should perform an action but remain the alert on screen. Is there some configuration that can be done to action that it won't close the alert?
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"title"
message:#"message"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"Do Something"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//Pressing this button, should not remove alert from screen
}]];
[alert addAction:[UIAlertAction actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//Regular functionality- pressing removes alert from screen
}]];
[alert show];
This (Prevent UIAlertController to dismiss) was suggested as possible answer, but the question deals with text field. I need one of the action buttons to not close the alert when pressed.
You can't do this.
Only one solution is to create a custom view controller that will look like native UIAlertController.
You can't do it with default UIAlertViewController, if you want to do this you need to create custom view controller whose look like UIAlertController.
You can use this custom view controller.
https://github.com/nealyoung/NYAlertViewController
Speaking from a user-perspective, a button press that doesn't follow through with an action would make me wonder if something was broken. If you're trying to use this as an opportunity to get further information, or some detail about the button press intention, I think a solution that adheres to user-expectation (though maybe a little annoying?) is to simply present a second dialog box after closing out the first. Basically, a "one-dialog-box-per-question" way of handling it.
Otherwise, I'll agree with the other suggestions and say that you need a custom view here, not a stock UIKit solution.
Try this code.
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"title"
message:#"message"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:#"Do Something"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
NSLog(#"Do Something Tapped");
}]];
[alert addAction:[UIAlertAction actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
NSLog(#"Close Tapped");
}]];
[self presentViewController:alert animated:YES completion:nil];
How about this code:
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"title"
message:#"message"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *doSomethingAction = [UIAlertAction actionWithTitle:#"Do Something"
style:UIAlertActionStyleDefault
handler:nil];
doSomethingAction.enabled = NO;
[alert addAction:doSomethingAction];
[alert addAction:[UIAlertAction actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
//Regular functionality- pressing removes alert from screen
}]];
[self presentViewController:alert animated:true completion:nil];
set NO to enabled property of UIAlertAction. It works well.

Show UIAlertController from a common utility class

I am working on a project that uses UIAlertView, now the problem is I have to replace all of the UIAlertView's with UIAlertController's and there are around 1250 of them in the code. I am planning to uses the existing Utility class to create a function that does this, following is what I am planning to do:
+(void)showAlert:(NSString *)title errorMessage:(NSString *)msg {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil]];
[self presentViewController:alertController animated:YES completion:nil];
}
I have the following questions before doing this:
1 - Is this worth the efforts (Using UIAlertController instead of UIAlertView) ?
2 - How do I handle UIAlertView's that had tags and different delegate implementation across hundreds of files ?
3 - The fourth line the function above gives an error : No known class method for selector 'presentViewController:animated:completion:'
Any help is greatly appreciated.
You've to use UIAlertController as UIAlertView is deprecated.
Apple Doc says:
In apps that run in versions of iOS prior to iOS 8, use the
UIAlertView class to display an alert message to the user. An alert
view functions similar to but differs in appearance from an action
sheet (an instance of UIActionSheet). UIAlertView is deprecated in iOS
8. (Note that UIAlertViewDelegate is also deprecated.) To create and manage alerts in iOS 8 and later, instead use UIAlertController with a
preferredStyle of UIAlertControllerStyleAlert. Availability
Alternatively, you can do this way. In your Utils Class, do this:
+ (void)showAlertWithTitle:(NSString *)title message:(NSString *)msg controller:(id)controller {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[controller presentViewController:alertController animated:YES completion:nil];
}
Usage from your ViewController class:
[Utils showAlertWithTitle:#"Camera" message:#"It seems that your device doesn't support camera. " controller: self];
If your existing UIAlertView is having tags then you've to use the UIAlertViewController class instead of the Util method.
Hope it helps.
You can use a common alertcontroller class method. Here is an example how you can solve the no.4 error. Basically you pass also the viewController that wants to call the alertview.
+(UIImage *)showAlert:(NSString *)title errorMessage:(NSString *)msg inViewController:(id)vc {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil]];
[vc presentViewController:alertController animated:YES completion:nil];
}
On the issue of alertviews with tags that require user input, to handle those inputs you might want to put the handler in and not let it nil and turn your method into a completionBlock method:
+(UIImage *)showAlert:(NSString *)title message:(NSString *)msg inViewController:(id)vc completedWithBtnStr:(void(^)(NSString* btnString))completedWithBtnStr {
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title message:msg preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completedWithBtnStr(#"Yes");
}]];
[alertController addAction:[UIAlertAction actionWithTitle:#"No" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
completedWithBtnStr(#"No");
}]];
[vc presentViewController:alertController animated:YES completion:nil];
}
An example of calling this is
[YourClass showAlert:#"Sure?" message:#"Delete this record?" inViewController:self completedWithBtnStr:^(NSString* btnString) {
if ([btnString isEqualToString:#"Yes"]) {
// delete record
}
}];
The parameter btnString can be anything really. The code is not tested so if you found error, do inform me.
Answering your questions:
It's definitely worth doing that. You have to use UIAlertController anyway in near future as of UIAlertview is deprecated.
Use blocks or protocol to get a callback when delegate methods in utility class are fired.
Looks like you are presenting that on NSObject. Present on view class. In this case, you can use window rootViewController to present on
+(void) showAlert
{
UIWindow* topWindow = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
topWindow.rootViewController = [UIViewController new];
topWindow.windowLevel = UIWindowLevelAlert + 1;
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"title" message:#"message" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* okButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle your yes please button action here
topWindow.hidden = YES;
//your ok button action handling
}];
[alertController addAction:okButton];
[topWindow makeKeyAndVisible];
[topWindow.rootViewController presentViewController:alertController animated:YES completion:nil];
}

Show Multiple Alert Controller on particular button click?

I have a label which asks a questions and user needs to answer by tapping on "YES" or "NO" button. if user clicks directly on NEXT Button the alert pops up saying Choose Yes or No if user selects Yes then he gets a new sub question there itself , if user clicks on NEXT without answering the sub question another alertView should pop up. On both the Condition only one alert view is displayed. help plz
heres the code
-(IBAction)btnNextClicked:(id)sender {
activeButton = sender;
if (([_txtOwnerShipPercentage.text isEqualToString:#" "] || _txtOwnerShipPercentage.text==nil || _txtOwnerShipPercentage.text.length==0) && (activeButton.tag == 1)) {
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Warning" message:#"Select Ownership Percentage" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:YES completion:nil];
} else if (activeButton.tag == 0){
UIAlertController *alertController2 = [UIAlertController alertControllerWithTitle:#"Warning" message:#"Select 'YES' or 'No'" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController2 addAction:okAction];
[self presentViewController:alertController2 animated:YES completion:nil];
}
}
and i have set tags to YES and No Button... 1 & 2 respectively.
activebutton is nothing but globally declared UIButton
Here is the code of how you can manage two or multiple alert controllers in one context according to your requirement.
//declare first alert controller
UIAlertController *alertviewcontroller1 = [UIAlertController alertControllerWithTitle:#"My Alert 1"
message:#"This is an action sheet of alert 1"
preferredStyle:UIAlertControllerStyleActionSheet];
//declare second alert controller
UIAlertController *alertviewcontroller2 = [UIAlertController alertControllerWithTitle:#"My Alert 2"
message:#"This is an action sheet of alert 2"
preferredStyle:UIAlertControllerStyleActionSheet];
//declare first alert controller action with hendler
UIAlertAction *firstActionOfAlert1 = [UIAlertAction actionWithTitle:#"YES"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
//Present second alert controller when user taps on 'YES' of first alert controller.
[self presentViewController:alertviewcontroller2 animated:YES completion:nil];
}];
//declare second alert controller action with hendler
UIAlertAction *firstActionOfAlert2 = [UIAlertAction actionWithTitle:#"YES"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
}];
//add both actions relatively
[alertviewcontroller1 addAction:firstActionOfAlert1];
[alertviewcontroller1 addAction:firstActionOfAlert2];
//Present first alert controller
[self presentViewController:alertviewcontroller1 animated:YES completion:nil];
the above solution will not work in case if previous alert is not fully presented or without tapping an action in alertView. Use this method for that,
if (self.presentedViewController & !self.presentedViewController.isBeingPresented) {
[self.presentedViewController presentViewController:alert animated:YES completion:nil];
}

UIAlertControllerStyleActionSheet in modal view

I am trying to show a UIAlertControllerStyleActionSheet anchored to a toolbar button. I have searched the web and found out I have to do this:
alertController.popoverPresentationController.barButtonItem = myButton;
however this does not work for me. The UIAlertController is shown as on iPhone on the iPad. Centered in the bottom middle of the window.
Here is my code:
// create action sheet
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:#"test title" message:nil
preferredStyle:UIAlertControllerStyleActionSheet];
// Delete Button
UIAlertAction *actionDelete = [UIAlertAction
actionWithTitle:#"button"
style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
// Delete
}];
// Cancel Button
UIAlertAction *actionCancel = [UIAlertAction
actionWithTitle:#"Avbryt"
style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
// Cancel code
}];
// Add Cancel action
[alertController addAction:actionCancel];
[alertController addAction:actionDelete];
// show action sheet
alertController.popoverPresentationController.barButtonItem = self.downloadAllButton;
alertController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
[alertController setModalPresentationStyle:UIModalPresentationPopover];
[self presentViewController:alertController animated:YES completion:nil];
By the way I am doing this in a Document Provider extension in the Documentpicker. So the UIAlertControllerStyleActionSheet is in a modal view already. I gues this is my problem, but I can not figure out how to change this for the iPad. This drives me crazy!!! Please Help!!
Regards,
Markus

UIAlertController dismissing his presentingViewController

I am trying to understand a weird behavior of my app, here is a description (tested in a trivial project).
ViewControllerA is presenting modally ViewControllerB
ViewControllerB contains a button, this button is presenting a UIAlertController specified this way
alert = [UIAlertController alertControllerWithTitle:#"Test" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
[alert addAction:[UIAlertAction actionWithTitle:#"Action" style:UIAlertActionStyleDefault handler:^(UIAlertAction *handler) { NSLog(#"Action"); }]];
The ViewControllerB is presenting alert this way
- (IBAction)button:(id)sender {
alert.popoverPresentationController.sourceView = self.button;
alert.popoverPresentationController.sourceRect = self.button.bounds;
[self presentViewController:alert animated:YES completion:nil];
}
Now, if you click on the button, the alert appears, if you click outside the alert, the alert disappears (I am on iPad). You can do it as many times as you want...
Here is the bug: When the alert is presented, if you click twice outside (quickly enough, ~0,2s interval), the alert disappears AND ViewControllerB is dismissed. At the end we can see ViewControllerA but we never asked for it.
There is also a warning message:
Warning: Attempt to dismiss from view controller <UIViewController: 0x7f85ab633f70> while a presentation or dismiss is in progress!
Thank you for your help.
I would prefer to add a UITapGestureRecognizer at the end, on your UIAlertController. like :
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:#"Test"
message:#"Test Message."
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *closeAction = [UIAlertAction actionWithTitle:#"Close"
style:UIAlertActionStyleDefault
handler:nil];
UIAlertAction *someAction = [UIAlertAction actionWithTitle:#"Action"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * _Nonnull action) {
....
}];
[alertController addAction:closeAction];
[alertController addAction:someAction];
[self presentViewController:alertController animated:YES completion:^{
[alertController.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action:nil]];
}];

Resources