WKWebView completionHandler called before dismissal - ios

I am using WKUIDelegate this function to handle javascript alert
-(void)webView:(WKWebView *)webView runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(void))completionHandler
{
UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:NSLocalizedString(#"Test Alert", nil)
message:message
delegate:self
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil] autorelease];
[alert show];
completionHandler();
}
According to Apple documentation we should call compeletionHandler() of an alert after OK button is pressed on alert as mentioned here
How to call completionHandler() after press OK button is pressed? If I don't call completionHandler() expection is thrown
**[WKWebViewController webView:runJavaScriptAlertPanelWithMessage:initiatedByFrame:
completionHandler:]:
***** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Completion handler passed to -[WKWebViewController
webView:runJavaScriptAlertPanelWithMessage:
initiatedByFrame:completionHandler:] was not called'****
UPDATE:
The solution mentioned below by Stefan is working fine with JS Alert but not with JS Confirm. Following is the code I get same exception even if the completionHandler() is called in ok and cancel button.
-(void)webView:(WKWebView *)webView runJavaScriptConfirmPanelWithMessage:(NSString *)message initiatedByFrame:(WKFrameInfo *)frame completionHandler:(void (^)(BOOL))completionHandler
{
MKCLOG_DEBUG(#"%#", frame.request.URL);
UIAlertController* alert = [UIAlertController alertControllerWithTitle:
NSLocalizedString(#"Test", nil) message: message
preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction
actionWithTitle:NSLocalizedString(#"Cancel", #"")
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
MKCLOG_DEBUG(#"Cancel action");
completionHandler(NO);
}];
UIAlertAction *okAction = [UIAlertAction
actionWithTitle:NSLocalizedString(#"OK", #"OK action")
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
MKCLOG_DEBUG(#"OK action");
completionHandler(YES);
}];
[alert addAction:cancelAction];
[alert addAction:okAction];
}

The way your code is setup now, you display the UIAlertView and immediately run the completionHandler(). Both happen at the same time.
What you should do instead is something like this:
UIAlertController* alert = [UIAlertController alertControllerWithTitle:
NSLocalizedString(#"Test Alert", nil) message: message
preferredStyle: UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle: #"OK"
style:UIAlertActionStyleDefault handler:^(UIAlertAction * action) {
completionHandler();
}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
This will present the alert and call the completionHandler when the user dismisses it.
Note that I am using the UIAlertController, which is only available on iOS 8 and up, but that should be fine since you depend on WKWebView.

Related

Detect Alert Button Click Event

I have the following implementation to be reused entire app. I store the following code in the Utility.m class.
CustomViewController.m
How can I capture click event in the following
[self presentViewController:[Utility oneButtonDisplayAlert:#"Error" withMessage:#"Please try again later"] animated:YES completion:nil];
Utility.m
+ (UIAlertController *)oneButtonDisplayAlert : (NSString*)title withMessage : (NSString*) message
{
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle your yes please button action here
}];
[alert addAction:yesButton];
return alert;
}
Add a block parameter to your oneButtonDisplayAlert:withMessage: method. Call that block inside the alert action's handler.
+ (UIAlertController *)oneButtonDisplayAlert:(NSString *)title withMessage:(NSString *)message andOKHandler:(void (^)(void))handler
{
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
if (handler) {
handler();
}
}];
[alert addAction:yesButton];
return alert;
}
And then call it as:
UIAlertController *alert = [Utility oneButtonDisplayAlert:#"Error" withMessage:#"Please try again later" andOKHandler:^{
// whatever code you need when OK tapped
}];
[self presentViewController:alert animated:YES completion:nil];
Note: Code in this answer might have a typo. Syntax not verified.

UIAlertController Action isnt working?

I have added a UIAlterController to my view. IT triggers fine, looks fine, and has its action, however tapping the Cancel action does nothing, it doesnt run any code, even if i add a block it wont execute it, it just doesnt seem to recognise the tap at all.
The code looks fine to me, so is there some other cause?
- (void)connectionFailed:(NSString *)title {
NSString *message = nil;
if ([title isEqualToString:NSLocalizedString(#"Connection Refused", #"connection Refused")]) {
message = NSLocalizedString(#"You do not have permission to use this console", #"incorrect permissions message");
}
else {
message = NSLocalizedString(#"Connection Failed", #"connection failed error message");
}
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:nil];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
});
Edit: it appears the issue is down to view leaks causing problems, not sure of the fix but here is the breakdown in debugging:
debugging image of view stack
Use this:
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:nil];
Change UIAlertActionStyleDefault to UIAlertActionStyleCancel
Try this:
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style:UIAlertActionStyleCancel
handler:^(UIAlertAction * _Nonnull action) {
//your code here ...
}];
- (void)connectionFailed:(NSString *)title {
NSString *message = nil;
if ([title isEqualToString:NSLocalizedString(#"Connection Refused", #"connection Refused")]) {
message = NSLocalizedString(#"You do not have permission to use this console", #"incorrect permissions message");
}
else {
message = NSLocalizedString(#"Connection Failed", #"connection failed error message");
}
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
style: UIAlertActionStyleCancel
handler:nil];
[alertController addAction:cancelAction];
[self presentViewController:alertController animated:YES completion:nil];
});
just edited your cancel button alert style....
Try this :
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:#"Cancel"
UIAlertActionStyleDefault
handler:nil];

dispatch async code error - Objective C

I am new to Objective C but have been working on Swift for a while now. I assumed Objective c to be logically similar to swift. I have to present an alert controller while I am processing a json data request; so I had to use dispatch async to get it to work in swift. Here's the code I used in Swift:
func alertMessage(message : String) -> Void {
let alert = UIAlertController(title: "Alert", message: message, preferredStyle: .Alert)
let okAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alert.addAction(okAction)
dispatch_async(dispatch_get_main_queue(),{
self.presentViewController(alert, animated: true, completion: nil)
})
}
But I tried to do the same thing in objective C like so
- (void)alertMessage : (NSString*) message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:NULL];
[alert addAction:okAction];
[dispatch_async(dispatch_get_main_queue(), ^(void){
[self presentViewController:alert animated:true completion:NULL];
})];
}
I'm getting an error "expected identifier". What am I doing wrong?
Its just syntax error. Try following
- (void)alertMessage : (NSString*) message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:NULL];
[alert addAction:okAction];
dispatch_async(dispatch_get_main_queue(), ^(void){
[self presentViewController:alert animated:true completion:NULL];
});
}
Because “dispatch_async” is a C function, you should call it like this.
- (void)alertMessage : (NSString*) message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:NULL];
[alert addAction:okAction];
dispatch_async(dispatch_get_main_queue(), ^{
[self presentViewController:alert animated:true completion:nil];
});
}
Can try now
- (void)alertMessage : (NSString*) message {
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Alert" message:message preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:NULL];
[alert addAction:okAction];
dispatch_async(dispatch_get_main_queue(), ^(void){
[self presentViewController:alert animated:true completion:nil];
});
}
Output:
Happy coding..

how to prevent later code getting executed first in UIAlertController

i am working in shouldPrepareForSegue method but i am stuck in a problem
__block BOOL Stat;
if([identifier isEqualToString:#"SignOut"]){
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Are You Sure?" message:#"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *can = [UIAlertAction actionWithTitle:#"CANCEL" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
Stat = NO;
[alert dismissViewControllerAnimated:YES completion:^{}];
}];
UIAlertAction *sign = [UIAlertAction actionWithTitle:#"SIGN OUT" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){
Stat = YES;
NSLog(#"%d",Stat);
}];
[alert addAction:can];
[alert addAction:sign];
[self presentViewController:alert animated:YES completion:^{}];
NSLog(#"%d",Stat);
return Stat;
}else{
return YES;
}
value of Stat always returns 0 as it is getting executed before i respond to UIAlertController as the later code are getting executed first how to prevent it.
you can create a function with completion block like below and call this where you need and just check flag
- (void)signOutWithcompletionHandler:(void (^)(BOOL flag))completionHandler
if([identifier isEqualToString:#"SignOut"]){
UIAlertController *alert = [UIAlertController alertControllerWithTitle:#"Are You Sure?" message:#"" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *can = [UIAlertAction actionWithTitle:#"CANCEL" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action){
completionHandler(NO);
[alert dismissViewControllerAnimated:YES completion:^{}];
}];
UIAlertAction *sign = [UIAlertAction actionWithTitle:#"SIGN OUT" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){
completionHandler(YES);
NSLog(#"%d",Stat);
}];
[alert addAction:can];
[alert addAction:sign];
[self presentViewController:alert animated:YES completion:^{}];
}else{
completionHandler(YES);
}
If you're running code that is essentially asynchronous, returning a value from the calling method is not going to work well (unless you set up some fairly complicated blocking).
Your best bet is to have the caller provide a delegate or listen for a notification instead of depending on the returned value. That way, you can trigger whatever you need to have happen in your completion handlers.

UIAlertView first deprecated IOS 9

I have tried several ways to use UIAlertController,instead of UIAlertView. I tried several ways but I cannot make the alert action work.
Here is my code that works fine in IOS 8 and IOS 9 but is showing up with deprecated flags. I tried the elegant suggestion below but I can't make it function in this context. I need to submit my app and this is the last thing to address. Thank You for any further suggestions. I am a newbie.
#pragma mark - BUTTONS ================================
- (IBAction)showModesAction:(id)sender {
NSLog(#"iapMade: %d", iapMade3);
// IAP MADE ! ===========================================
if (!iapMade3) {
//start game here
gamePlaysCount++;
[[NSUserDefaults standardUserDefaults]setInteger:gamePlaysCount forKey:#"gamePlaysCount"];
NSLog(#"playsCount: %ld", (long)gamePlaysCount);
if (gamePlaysCount >= 4) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Basic"
message: THREE_PLAYS_LIMIT_MESSAGE
delegate:self
cancelButtonTitle:#"Yes, please"
otherButtonTitles:#"No, thanks", nil];
[alert show];
NSString *path = [[NSBundle mainBundle] pathForResource:#"cow" ofType:#"wav"];
_pop =[[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:NULL];
[_pop play];
[self dismissViewControllerAnimated:true completion:nil];
} else {
if (gamePlaysCount == 1) {
// Create & store the next 5 mins when player gets 3 more lives
nextDateToPlay = [[NSDate date] dateByAddingTimeInterval:60*60*0.1];
NSLog(#"CURRENT DATE: %#", [NSDate date]);
NSLog(#"NEXT DAY: %#", nextDateToPlay);
[[NSUserDefaults standardUserDefaults]setObject: nextDateToPlay forKey:#"nextDateToPlay"];
NSLog(#"nextDateToPlay: %#", nextDateToPlay);
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Basic"
message: THREE_PLAYS_LIMIT_MESSAGE2
delegate:self
cancelButtonTitle:#"Got it!"
otherButtonTitles:#"Start", nil];
[alert show];
} else {
if (gamePlaysCount == 3) {
UIAlertView *alert = [[UIAlertView alloc]initWithTitle:#"Basic"
message: THREE_PLAYS_LIMIT_MESSAGE3
delegate:self
cancelButtonTitle:#"Yep, I Know!"
otherButtonTitles:#"Start", nil];
[alert show];
}
}
}
}
}
// IAP NOT MADE =============================
#pragma mark - ALERTVIEW DELEGATE ============================
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if ([[alertView buttonTitleAtIndex:buttonIndex] isEqualToString:#"Yes, please"]) {
UIStoryboard *storyboard = self.storyboard;
MenuViewController *svc = [storyboard instantiateViewControllerWithIdentifier:#"Store"];
svc.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[self presentViewController:svc animated:YES completion:nil];
}
}
From iOS8 Apple provide new UIAlertController class which you can use instead of UIAlertView which is now deprecated, it is also stated in deprecation message:
UIAlertView is deprecated. Use UIAlertController with a preferredStyle
of UIAlertControllerStyleAlert instead
So you should use something like this
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:#"Title"
message:#"Message"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"Yes, please"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle your yes please button action here
}];
UIAlertAction* noButton = [UIAlertAction
actionWithTitle:#"No, thanks"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle no, thanks button
}];
[alert addAction:yesButton];
[alert addAction:noButton];
[self presentViewController:alert animated:YES completion:nil];
//Calling
[self showMessage:#"There is no internet connection for this device"
withTitle:#"Error"];
//Method
-(void)showMessage:(NSString*)message withTitle:(NSString *)title
{
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
//do something when click button
}];
[alert addAction:okAction];
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
[vc presentViewController:alert animated:YES completion:nil];
}
If you want to use this alert in NSObject class you should use like:
-(void)showMessage:(NSString*)message withTitle:(NSString *)title{
dispatch_async(dispatch_get_main_queue(), ^{
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:[UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
}]];
[[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:alertController animated:YES completion:^{
}];
});
}
Swift version of new implementation is :
let alert = UIAlertController(title: "Oops!", message:"your message", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "Okay.", style: .Default) { _ in })
self.presentViewController(alert, animated: true){}
Xcode 8 + Swift
Assuming self is a UIViewController:
func displayAlert() {
let alert = UIAlertController(title: "Test",
message: "I am a modal alert",
preferredStyle: .alert)
let defaultButton = UIAlertAction(title: "OK",
style: .default) {(_) in
// your defaultButton action goes here
}
alert.addAction(defaultButton)
present(alert, animated: true) {
// completion goes here
}
}
Make UIAlertController+AlertController Category as:
UIAlertController+AlertController.h
typedef void (^UIAlertCompletionBlock) (UIAlertController *alertViewController, NSInteger buttonIndex);
#interface UIAlertController (AlertController)
+ (instancetype)showAlertIn:(UIViewController *)controller
WithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitle
tapBlock:(UIAlertCompletionBlock)tapBlock;
#end
UIAlertController+AlertController.m
#implementation UIAlertController (NTAlertController)
+ (instancetype)showAlertIn:(UIViewController *)controller
WithTitle:(NSString *)title
message:(NSString *)message
cancelButtonTitle:(NSString *)cancelButtonTitle
otherButtonTitles:(NSString *)otherButtonTitle
tapBlock:(UIAlertCompletionBlock)tapBlock {
UIAlertController *alertController = [self alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
if(cancelButtonTitle != nil) {
UIAlertAction *cancelButton = [UIAlertAction
actionWithTitle:cancelButtonTitle
style:UIAlertActionStyleCancel
handler:^(UIAlertAction *action)
{
tapBlock(alertController, ALERTACTION_CANCEL); // CANCEL BUTTON CALL BACK ACTION
}];
[alertController addAction:cancelButton];
}
if(otherButtonTitle != nil) {
UIAlertAction *otherButton = [UIAlertAction
actionWithTitle:otherButtonTitle
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action)
{
tapBlock(alertController, ALERTACTION_OTHER); // OTHER BUTTON CALL BACK ACTION
}];
[alertController addAction:otherButton];
}
[controller presentViewController:alertController animated:YES completion:nil];
return alertController;
}
#end
in your ViewController.m
[UIAlertController showAlertIn:self WithTitle:#"" message:#"" cancelButtonTitle:#"Cancel" otherButtonTitles:#"Other" tapBlock:^(UIAlertController *alertController, NSInteger index){
if(index == ALERTACTION_CANCEL){
// CANCEL BUTTON ACTION
}else
if(index == ALERTACTION_OTHER){
// OTHER BUTTON ACTION
}
[alertController dismissViewControllerAnimated:YES completion:nil];
}];
NOTE: If you want to add more than two buttons then add another
more UIAlertAction to the UIAlertController.
Use UIAlertController instead of UIAlertView
-(void)showMessage:(NSString*)message withTitle:(NSString *)title
{
UIAlertController * alert= [UIAlertController
alertControllerWithTitle:title
message:message
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
//do something when click button
}];
[alert addAction:okAction];
UIViewController *vc = [[[[UIApplication sharedApplication] delegate] window] rootViewController];
[vc presentViewController:alert animated:YES completion:nil];
}
I tried the above methods, and no one can show the alert view, only when I put the presentViewController: method in a dispatch_async sentence:
dispatch_async(dispatch_get_main_queue(), ^ {
[self presentViewController:alert animated:YES completion:nil];
});
Refer to Alternative to UIAlertView for iOS 9?.
-(void)showAlert{
UIAlertController* alert = [UIAlertController alertControllerWithTitle:#"Title"
message:#"Message"
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:#"OK" style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {}];
[alert addAction:defaultAction];
[self presentViewController:alert animated:YES completion:nil];
}
[self showAlert]; // calling Method
Check this:
UIAlertController *alertctrl =[UIAlertController alertControllerWithTitle:#"choose Image" message:nil preferredStyle:UIAlertControllerStyleActionSheet];
UIAlertAction *camera =[UIAlertAction actionWithTitle:#"camera" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
[self Action]; //call Action need to perform
}];
[alertctrl addAction:camera];
-(void)Action
{
}
UIAlertController * alert = [UIAlertController
alertControllerWithTitle:#"Are you sure you want to logout?"
message:#""
preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction* yesButton = [UIAlertAction
actionWithTitle:#"Logout"
style:UIAlertActionStyleDestructive
handler:^(UIAlertAction * action)
{
}];
UIAlertAction* noButton = [UIAlertAction
actionWithTitle:#"Cancel"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction * action) {
//Handle no, thanks button
}];
[alert addAction:noButton];
[alert addAction:yesButton];
[self presentViewController:alert animated:YES completion:nil];

Resources