Change JavaScript alert dialog title in iOS Change JavaScript alert dialog title in iOS ios ios

Change JavaScript alert dialog title in iOS


Basically you cannot change the title, but recently I have found a way to show an alert dialog with no title.

var iframe = document.createElement("IFRAME");iframe.setAttribute("src", 'data:text/plain,');document.documentElement.appendChild(iframe);window.frames[0].window.alert('hello');iframe.parentNode.removeChild(iframe);


I present three separate solutions to this problem. The best solution for production code is the one described by @Martin H. My only addition to this is a concrete example showing how to implement it.

My other solutions depend on undocumented behaviors of UIWebView, but don't require any changes to the content/JS.

Solution 1: Here is a concrete example demonstrating the technique put forth by @Martin H. This is likely the best solution as it does not rely on UIWebView / UIAlertView undocumented behaviors.

@interface TSViewController () <UIWebViewDelegate>@end@implementation TSViewController- (UIWebView*) webView{    return (UIWebView*) self.view;}- (void) loadView{    UIWebView* wv = [[UIWebView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];    wv.delegate = self;    wv.scalesPageToFit = YES;    self.view = wv;}- (void) viewDidLoad{    [super viewDidLoad];    [self.webView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: @"http://www.craigslist.org"]]];}- (void) webViewDidFinishLoad: (UIWebView *) webView{    // inject some js to re-map window.alert()    // ideally just do this in the html/js itself if you have control of that.    NSString* js = @"window.alert = function(message) { window.location = \"http://action/alert?message=\" + message; }";    [webView stringByEvaluatingJavaScriptFromString: js];    // trigger an alert.  for demonstration only:    [webView stringByEvaluatingJavaScriptFromString: @"alert('hello, world');" ];}- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType{    NSURL* url = request.URL;    // look for our custom action to come through:    if ( [url.host isEqualToString: @"action"] && [url.path isEqualToString: @"/alert"] )    {        // parse out the message        NSString* message = [[[[url query] componentsSeparatedByString: @"="] lastObject] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];        // show our alert        UIAlertView* av = [[UIAlertView alloc] initWithTitle: @"My Custom Title"                                                     message: message                                                    delegate: nil                                           cancelButtonTitle: @"OK"                                           otherButtonTitles: nil];        [av show];        return NO;    }    return YES;}

Solution 2: First, let me say that I would never personally use the following solution in production code. The best way to achieve this functionality is to do what @Martin H suggests in his answer, or possibly what @twk suggests if an empty title is actually what is desired. If you don't have control over the web content itself, perhaps you could inject @Martin H's code after the content has been loaded.

That said, this is otherwise achievable if we make some assumptions. First, that the Javascript alert() method indeed maps to a real UIAlertView. (In fact it does!) Second, that we can come up with some mechanism to discern an alert()-sourced UIAlertView from other application-sourced UIAlertViews. (We can!)

My approach is to swizzle the UIAlertView. I know - swizzling sucks and has all sorts of drawbacks. I don't swizzle in production apps if I can help it. But for this science project, we're going to swizzle the UIAlertView setDelegate: method.

What I found is that 1) the UIWebView will construct an UIAlertView to display the javascript alert, and 2) the UIAlertView title is set before the UIAlertView delegate is set. By swizzling UIAlertView setDelegate: with my own method I can accomplish two things: 1) I can determine that the UIAlertView is being commissioned on behalf of a UIWebView (just inspect the delegate...) and 2) I have opportunity to re-set the title before the alertview is shown.

You might ask "Why not just swizzle UIAlertview -show method?" That would be great, but in my experimenting I found that the UIWebView never invoked show. It must be calling some other internal method, and I didn't investigate further.

My solution implements a category on UIAlertView, which adds a couple of class methods. Basically you register a UIWebView with these methods, and provide a replacement title to be used. Alternatively you can provide a callback block that returns a title when invoked.

I used the iOS6 NSMapTable class to keep a set of UIWebView-to-Title mappings, where the UIWebView is a weak key. This way I don't ever have to unregister my UIWebView and everything gets cleaned up nicely. Thus, this current implementation is iOS6-only.

Here's the code, shown in-use in a basic view controller:

#import <objc/runtime.h>typedef NSString*(^TSAlertTitleBlock)(UIWebView* webView, NSString* defaultTitle );@interface UIAlertView (TS)@end@implementation UIAlertView (TS)NSMapTable* g_webViewMapTable;+ (void) registerWebView: (UIWebView*) webView alertTitleStringOrBlock: (id) stringOrBlock{    static dispatch_once_t onceToken;    dispatch_once(&onceToken, ^{        g_webViewMapTable = [NSMapTable weakToStrongObjectsMapTable];        // $wizzle        Method originalMethod = class_getInstanceMethod(self, @selector(setDelegate:));        Method overrideMethod = class_getInstanceMethod(self, @selector(setDelegate_ts:));        method_exchangeImplementations(originalMethod, overrideMethod);    });    [g_webViewMapTable setObject: [stringOrBlock copy] forKey: webView];}+ (void) registerWebView: (UIWebView*) webView alertTitle: (NSString*) title{    [self registerWebView: webView alertTitleStringOrBlock: title];}+ (void) registerWebView: (UIWebView*) webView alertTitleBlock: (TSAlertTitleBlock) alertTitleBlock{    [self registerWebView: webView alertTitleStringOrBlock: alertTitleBlock];}- (void) setDelegate_ts: (id) delegate{    // call the original implementation    [self setDelegate_ts: delegate];    // oooh - is a UIWebView asking for this UIAlertView????    if ( [delegate isKindOfClass: [UIWebView class]] )    {        // see if we've registered a title/title-block        for ( UIWebView* wv in g_webViewMapTable.keyEnumerator )        {            if ( wv != delegate)                continue;            id stringOrBlock = [g_webViewMapTable objectForKey: wv];            if ( [stringOrBlock isKindOfClass: NSClassFromString( @"NSBlock" )] )            {                stringOrBlock = ((TSAlertTitleBlock)stringOrBlock)( wv, self.title );            }            NSParameterAssert( [stringOrBlock isKindOfClass: [NSString class]] );            [self setTitle: stringOrBlock];            break;        }    }}@end@interface TSViewController () <UIWebViewDelegate>@end@implementation TSViewController- (UIWebView*) webView{    return (UIWebView*) self.view;}- (void) loadView{    UIWebView* wv = [[UIWebView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];    wv.delegate = self;    wv.scalesPageToFit = YES;    self.view = wv;}- (void) viewDidLoad{    [super viewDidLoad];    // method 1: bind a title to a webview:    [UIAlertView registerWebView: self.webView alertTitle: nil];    /*    // method 2: return a title each time it's needed:    [UIAlertView registerWebView: self.webView                 alertTitleBlock: ^NSString *(UIWebView *webView, NSString* defaultTitle) {                     return @"Custom Title";    }];     */    [self.webView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: @"http://www.craigslist.org"]]];}- (void) webViewDidFinishLoad: (UIWebView *) webView{    // trigger an alert    [webView stringByEvaluatingJavaScriptFromString: @"alert('hello, world');" ];}@end

Solution 3: Upon reflection, here's a simpler technique than I initially described in Solution 2. It still depends on the undocumented fact that the javascript alert is implemented using a UIAlertView that has its delegate set to the sourcing UIWebView. For this solution, simply subclass UIWebView and implement your own delegate method for UIAlertView willPresentAlertView:, and when it is called, reset the title to whatever you wish.

@interface TSWebView : UIWebView@end@implementation TSWebView- (void) willPresentAlertView:(UIAlertView *)alertView{    if ( [self.superclass instancesRespondToSelector: @selector( willPresentAlertView:) ])    {        [super performSelector: @selector( willPresentAlertView:) withObject: alertView];    }    alertView.title = @"My Custom Title";}@end@interface TSViewController () <UIWebViewDelegate>@end@implementation TSViewController- (UIWebView*) webView{    return (UIWebView*) self.view;}- (void) loadView{    UIWebView* wv = [[TSWebView alloc] initWithFrame: CGRectMake(0, 0, 320, 480)];    wv.delegate = self;    wv.scalesPageToFit = YES;    self.view = wv;}- (void) viewDidLoad{    [super viewDidLoad];    [self.webView loadRequest: [NSURLRequest requestWithURL: [NSURL URLWithString: @"http://www.craigslist.org"]]];}- (void) webViewDidFinishLoad: (UIWebView *) webView{    // trigger an alert    [webView stringByEvaluatingJavaScriptFromString: @"alert('hello, world');" ];}@end


No you can't do this in a Javascript alert.

But if the Javascript is yours then instead of calling the alert you could instead call a function that calls Objective C and invoke an iOS native alert.

If the Javascript isn't yours then using UIWebView you can inject some Javascript to override the default behaviour and change it to call an iOS native alert i.e. something like this

window.alert = function(message) {  window.location = "myScheme://" + message"};

Then look for myScheme and extract message in UIWebView's shouldStartLoadWithRequest

Here's the standard method for invoking Objective-C from Javascript

How to call Objective-C from Javascript?