Est-il possible de changer le titre de l'alerte JavaScript dans un UIWebview
sur iPhone?
Fondamentalement, vous ne pouvez pas modifier le titre, mais récemment, j'ai 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);
Je présente trois solutions distinctes à ce problème. La meilleure solution pour le code de production est celle décrite par @Martin H. Mon seul ajout à cela est un exemple concret montrant comment l'implémenter.
Mes autres solutions dépendent des comportements non documentés d'UIWebView, mais ne nécessitent aucune modification du contenu/JS.
Solution 1 : Voici un exemple concret démontrant la technique proposée par @Martin H. C'est probablement la meilleure solution car elle ne repose pas sur UIWebView
/UIAlertView
comportements non documentés.
@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'utiliserais jamais personnellement la solution suivante dans le code de production. La meilleure façon d'atteindre cette fonctionnalité est de faire ce que @Martin H suggère dans sa réponse, ou peut-être ce que @twk suggère si un titre vide est réellement ce qui est souhaité. Si vous ne contrôlez pas le contenu Web lui-même, vous pouvez peut-être injecter le code de @Martin H après le chargement du contenu.
Cela dit, cela est par ailleurs réalisable si nous faisons certaines hypothèses. Premièrement, que la méthode Javascript alert()
correspond bien à un vrai UIAlertView
. (En fait, c'est le cas!) Deuxièmement, nous pouvons trouver un mécanisme pour discerner une alert()
- source UIAlertView
à partir d'une autre source d'application UIAlertViews
. (Nous pouvons!)
Mon approche est de balayer le UIAlertView
. Je sais - le swizzling craint et a toutes sortes d'inconvénients . Je ne me précipite pas dans les applications de production si je peux l'aider. Mais pour ce projet scientifique, nous allons accélérer la méthode UIAlertView setDelegate:
.
Ce que j'ai trouvé, c'est que 1) le UIWebView
va construire un UIAlertView
pour afficher l'alerte javascript, et 2) le titre UIAlertView
est défini avant l'UIAlertView delegate
est défini. En accélérant UIAlertView setDelegate:
Avec ma propre méthode, je peux accomplir deux choses: 1) Je peux déterminer que le UIAlertView
est mis en service au nom d'un UIWebView
(il suffit d'inspecter le délégué. ..) et 2) J'ai la possibilité de réinitialiser le titre avant d'afficher l'alerte.
Vous pourriez demander "Pourquoi ne pas simplement utiliser la méthode UIAlertview -show
?" Ce serait formidable, mais lors de mes expérimentations, j'ai trouvé que l'UIWebView n'a jamais appelé show
. Il doit appeler une autre méthode interne, et je n'ai pas enquêté davantage.
Ma solution implémente une catégorie sur UIAlertView, qui ajoute quelques méthodes de classe. Fondamentalement, vous enregistrez un UIWebView avec ces méthodes et fournissez un titre de remplacement à utiliser. Alternativement, vous pouvez fournir un bloc de rappel qui renvoie un titre lorsqu'il est appelé.
J'ai utilisé la classe iOS6 NSMapTable
pour conserver un ensemble de mappages UIWebView-to-Title, où UIWebView est une clé faible. De cette façon, je n'ai plus besoin de désinscrire mon UIWebView
et tout est bien nettoyé. Ainsi, cette implémentation actuelle est uniquement iOS6.
Voici le code, affiché en cours d'utilisation dans un contrôleur de vue de base:
#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 celle décrite initialement dans la solution 2. Elle dépend toujours du fait non documenté que l'alerte javascript est implémentée à l'aide de une UIAlertView dont le délégué est défini sur la source UIWebView. Pour cette solution, il suffit de sous-classer UIWebView et d'implémenter votre propre méthode déléguée pour UIAlertView willPresentAlertView:, et lorsqu'elle est appelée, réinitialisez le titre à votre guise.
@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
Non, vous ne pouvez pas le faire dans une alerte Javascript.
Mais si le Javascript est à vous, au lieu d'appeler l'alerte, vous pouvez plutôt appeler une fonction qui appelle Objective C et invoquer une alerte native iOS.
Si le Javascript n'est pas le vôtre, alors en utilisant UIWebView, vous pouvez injecter du Javascript pour remplacer le comportement par défaut et le changer pour appeler une alerte native iOS, c'est-à-dire quelque chose comme ça
window.alert = function(message) {
window.location = "myScheme://" + message"
};
Recherchez ensuite myScheme et extrayez le message dans shouldStartLoadWithRequest d'UIWebView.
Voici la méthode standard pour invoquer Objective-C à partir de Javascript
Créez simplement une classe pour votre UIWebView et faites-y la fonction willPresentAlertView (alertView: UIAlertView). Après cet ensemble alertView.title = "your fav title"
et c'est fait.
import UIKit
class webviewClass: UIWebView {
func willPresentAlertView(alertView: UIAlertView) {
alertView.title = "my custum title"
}
}
Après cela, chaque fois qu'une alerte ou une confirmation et ... s'affiche, votre titre personnalisé s'affiche.
Utilisez les boîtes de dialogue du plugin cordova. Il permet de créer des dialogues natifs.
Le code ci-dessous créera une alerte native:
navigator.notification.alert(message, alertCallback, [title], [buttonName])
En partant de ce que @twk a écrit, j'écrase simplement la fonction d'alerte. Fonctionne très bien sur ios7.
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);
}