Cambiar el título del dialog de alerta de JavaScript en iOS

¿Es posible cambiar el título de alerta de JavaScript en una UIWebview en iPhone?

Básicamente no puedes cambiar el título, pero recientemente encontré una manera de mostrar un dialog de alerta sin título.

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

Presento tres soluciones separadas para este problema. La mejor solución para el código de producción es la que describe @Martin H. Mi única adición a esto es un ejemplo concreto que muestra cómo implementarlo.

Mis otras soluciones dependen de los comportamientos indocumentados de UIWebView, pero no requieren ningún cambio en el contenido / JS.

Solución 1 : Este es un ejemplo concreto que demuestra la técnica presentada por @Martin H. Esta es probablemente la mejor solución, ya que no se basa en los comportamientos no documentados de UIWebView / UIAlertView .

 @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; } 

Solución 2: Primero, permítame decirle que nunca utilizaría personalmente la siguiente solución en el código de producción. La mejor manera de lograr esta funcionalidad es hacer lo que @Martin H sugiere en su respuesta, o posiblemente lo que @twk sugiere si un título vacío es realmente lo que se desea. Si no tiene control sobre el contenido web en sí, quizás pueda inyectar el código de @Martin H después de que se haya cargado el contenido.

Dicho esto, esto es posible de otro modo si hacemos algunos supuestos. Primero, que el método de alert() JavaScript alert() hecho se asigna a un UIAlertView real. (¡De hecho lo hace!) En segundo lugar, podemos encontrar un mecanismo para discernir un UIAlertView UIAlertView UIAlertView en aplicaciones alert() UIAlertViews alert() UIAlertView en aplicaciones. (¡Podemos!)

Mi enfoque es swizzle el UIAlertView . Lo sé, chupar y tener todo tipo de inconvenientes . No me gustan las aplicaciones de producción si puedo ayudarlo. Pero para este proyecto de ciencia, vamos a UIAlertView setDelegate: método UIAlertView setDelegate:

Lo que encontré es que 1) el UIWebView buildá un UIAlertView para mostrar la alerta de javascript, y 2) el título de UIAlertView se establece antes de que se establezca el delegate UIAlertView. Al swizzling UIAlertView setDelegate: con mi propio método puedo lograr dos cosas: 1) Puedo determinar que el UIAlertView se está comisionando en nombre de un UIWebView (simplemente inspeccionar al delegado …) y 2) Tengo la oportunidad de volver a establecer Se muestra el título antes de la vista de alerta.

Podrías preguntar "¿Por qué no simplemente swizzle UIAlertview -show method?" Eso sería genial, pero en mi experiencia encontré que UIWebView nunca invocaba el show . Debe llamar a otro método interno, y no investigé más.

Mi solución implementa una categoría en UIAlertView, que agrega un par de methods de class. Básicamente, registra una UIWebView con estos methods y proporciona un título de reemploop para usar. Alternativamente, puede proporcionar un bloque de callback que devuelve un título cuando se invoca.

Utilicé la class iOS6 NSMapTable para mantener un set de asignaciones UIWebView-to-Title, donde el UIWebView es una key débil. De esta forma, nunca tengo que anular el logging de mi UIWebView y todo se limpia muy bien. Por lo tanto, esta implementación actual es solo para iOS6.

Aquí está el código, que se muestra en uso en un controller de vista básico:

 #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 registenetworking 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 

Solución 3 : Tras reflexionar, aquí hay una técnica más simple que la que describí inicialmente en la Solución 2. Aún depende del hecho indocumentado de que la alerta de javascript se implementa mediante un UIAlertView que tiene su delegado configurado para la fuente de UIWebView. Para esta solución, simplemente subclass UIWebView e implemente su propio método de delegado para UIAlertView willPresentAlertView :, y cuando se le llame, restablezca el título a lo que desee.

 @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, no puedes hacerlo en una alerta de Javascript.

Pero si el Javascript es suyo, entonces, en lugar de llamar a la alerta, podría llamar a una function que llama al Objetivo C e invocar una alerta nativa de iOS.

Si el Javascript no es suyo, entonces usando UIWebView puede inyectar algo de Javascript para anular el comportamiento pnetworkingeterminado y cambiarlo para llamar a una alerta nativa de iOS, es decir, algo como esto

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

Luego busque myScheme y extraiga el post en UIWebView's shouldStartLoadWithRequest

Aquí está el método estándar para invocar Objective-C desde Javascript

¿Cómo llamar a Objective-C desde Javascript?

Por lo que vale, Phonegap / Cordova ofrece una function navigator.notification.alert . Permite especificar un título personalizado.

Simplemente haga una class para su UIWebView y haga que la function willPresentAlertView (alertView: UIAlertView) funcione en ella. Después de eso, establezca alertView.title = "your fav title" y está hecho.

  import UIKit class webviewClass: UIWebView { func willPresentAlertView(alertView: UIAlertView) { alertView.title = "my custum title" } } 

Después de eso, cuando alguna vez una alerta o confirme y … se muestre, se mostrará su título personalizado.

Saliendo de lo que @twk escribió, simplemente sobrescribí la function de alerta. Funciona muy bien en 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); } 

Use cordova-plugin-dialogs. Permite crear dialogs nativos.

Debajo del código se creará una alerta nativa:

 navigator.notification.alert(message, alertCallback, [title], [buttonName])