35 votes

Modifier le titre de la boîte de dialogue d'alerte JavaScript dans iOS

Est-il possible de changer le titre de l'alerte JavaScript dans un UIWebview sur iPhone?

57voto

twk Points 437

En gros, vous ne pouvez pas changer le titre, mais j'ai récemment trouvé un moyen d'afficher une boîte de dialogue d'alerte sans titre.

 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);
 

15voto

TomSwift Points 22012

Je vous présente trois solutions à ce problème. La meilleure solution pour le code de production est celle décrite par @Martin H. Mon seul plus c'est un exemple concret montrant comment la mettre en œuvre.

Mes autres solutions reposent sur les sans-papiers comportements de UIWebView, mais ne nécessite aucune modification du contenu/JS.

Solution 1: Voici un exemple concret illustrant la technique mis de l'avant par @Martin H. C'est probablement la meilleure solution, car il ne repose pas sur UIWebView / UIAlertView sans-papiers comportements.

@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: tout d'Abord, permettez-moi de dire que je n'aurais jamais personnellement utiliser la solution suivante dans le code de production. La meilleure façon d'obtenir cette fonctionnalité est de faire ce que @Martin H suggère, dans sa réponse, ou éventuellement ce @twk suggère si un vide titre est en fait ce qui est souhaité. Si vous n'avez pas de contrôle sur le contenu web lui-même, vous pourriez peut-être injecter @Martin H du code après que le contenu a été chargé.

Cela dit, c'est le contraire réalisable si nous faisons quelques hypothèses. Tout d'abord, que le Javascript alert() méthode, en effet, correspond à un réel UIAlertView. (En fait, il le fait!) La seconde, que nous pouvons arriver à un mécanisme de discerner un alert()-sourcés UIAlertView à partir d'une autre application-sourcés UIAlertViews. (On peut!)

Mon approche est de swizzle l' UIAlertView. Je sais - swizzling suce et a toutes sortes d'inconvénients. Je n'ai pas swizzle en production les applications, si je peux l'aider. Mais pour ce projet de science, nous allons swizzle l' UIAlertView setDelegate: méthode.

Ce que j'ai trouvé, c'est que 1) l' UIWebView construire une UIAlertView pour afficher l'alerte javascript, et 2) l' UIAlertView titre est fixée avant la UIAlertView delegate est réglé. Par swizzling UIAlertView setDelegate: , avec ma méthode, je peux faire deux choses: 1) je peux déterminer que l' UIAlertView est demandé sur le compte d'un UIWebView (juste inspecter le délégué...) et 2) j'ai la possibilité de re-définir le titre avant de le alertview est indiqué.

Vous pourriez vous demander "Pourquoi ne pas simplement swizzle UIAlertview -show méthode?" Ce serait génial, mais dans mon expérimentation, j'ai trouvé que la UIWebView jamais invoqué show. Il doit être l'appel de certains autres méthode interne, et je n'ai pas d'autres investigations.

Ma solution met en œuvre une catégorie sur UIAlertView, ce qui ajoute un couple de méthodes de la classe. Fondamentalement, vous inscrire à une UIWebView avec ces méthodes, et de fournir un titre de remplacement à être utilisé. Sinon, vous pouvez fournir un rappel bloc qui retourne un titre lorsqu'il est invoqué.

J'ai utilisé le iOS6 NSMapTable classe de maintenir un ensemble de UIWebView à Titre mappages, où la UIWebView est une faiblesse de la clé. De cette façon, je ne jamais avoir à annuler l'inscription de mon UIWebView et tout est nettoyé bien. Ainsi, cette mise en œuvre est iOS6 seule.

Voici le code, illustré en usage dans une vue de base de contrôleur:

#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: Après réflexion, voici une technique plus simple que j'ai d'abord décrit dans la Solution 2. Il dépend toujours de le sans-papiers fait que l'alerte javascript est mis en œuvre à l'aide d'un UIAlertView qui a son délégué ensemble de l'approvisionnement UIWebView. Pour cette solution, il suffit de la sous-classe UIWebView et de mettre en œuvre votre propre méthode du délégué pour UIAlertView willPresentAlertView: et quand on l'appelle, réinitialiser le titre de ce que vous voulez.

@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

10voto

Mr H Points 3109

Non, vous ne pouvez pas faire cela dans une alerte Javascript.

Mais si le Javascript est le vôtre alors au lieu d'appeler l'alerte que vous pourrait plutôt appeler une fonction qui s'appelle Objective C et invoquer un iOS natif d'alerte.

Si le Javascript n'est pas à vous ensuite à l'aide de UIWebView vous pouvez injecter un peu de Javascript pour remplacer le comportement par défaut et de le modifier pour appeler un iOS natif d'alerte c'est à dire quelque chose comme ceci

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

Ensuite, regardez pour myScheme et de l'extrait de message dans UIWebView de shouldStartLoadWithRequest

Voici la méthode standard pour l'invocation d'Objective-C à partir de Javascript

Comment appeler Objective-C à partir de Javascript?

4voto

Meryn Stol Points 4099

Phonegap / Cordova propose une fonction navigator.notification.alert . Il permet de spécifier un titre personnalisé.

0voto

socca1157 Points 67

En quittant ce que @twk a écrit, je remplace simplement la fonction d'alerte. Fonctionne très bien sur iOS 7.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X