Est-il possible de changer le titre de l'alerte JavaScript dans un UIWebview
sur iPhone?
Réponses
Trop de publicités?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);
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
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
Phonegap / Cordova propose une fonction navigator.notification.alert . Il permet de spécifier un titre personnalisé.
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);
}