在 iOS 中更改 JavaScript 警报对话框标题

Change JavaScript alert dialog title in iOS

本文关键字:对话框 标题 JavaScript iOS      更新时间:2023-09-26

是否可以在iPhone的UIWebview中更改JavaScript警报标题?

基本上您无法更改标题,但最近我找到了一种显示没有标题的警报对话框的方法。

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

我为这个问题提出了三个单独的解决方案。 生产代码的最佳解决方案是 @Martin H 描述的解决方案。 我对此的唯一补充是一个具体示例,展示了如何实现它。

我的其他解决方案依赖于UIWebView的未记录行为,但不需要对内容/JS进行任何更改。

解决方案1:这是一个具体的例子,演示了@Martin H提出的技术。 这可能是最好的解决方案,因为它不依赖于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;
}
解决方案

2:首先,我要说的是,我个人永远不会在生产代码中使用以下解决方案。 实现此功能的最佳方法是按照 H @Martin 在他的答案中建议的操作,或者如果确实需要空标题,则可能执行@twk建议的操作。 如果您无法控制 Web 内容本身,也许您可以在加载内容后注入 @Martin H 的代码。

也就是说,如果我们做出一些假设,这是可以实现的。 首先,Javascript alert()方法确实映射到一个真正的UIAlertView。 (事实上确实如此! 其次,我们可以想出一些机制来区分alert()来源UIAlertView与其他应用程序来源UIAlertViews。(我们可以!

我的方法是刷UIAlertView. 我知道 - 旋转很糟糕,并且有各种缺点。 如果我能提供帮助,我不会在生产应用程序中切换。 但是对于这个科学项目,我们将采用UIAlertView setDelegate:方法。

我发现 1( UIWebView将构造一个UIAlertView来显示 javascript 警报,2( UIAlertView标题是在设置 UIAlertView delegate之前设置的。 通过使用我自己的方法UIAlertView setDelegate:,我可以完成两件事:1(我可以确定UIAlertView是代表UIWebView委托的(只需检查委托人......(和2(我有机会在显示警报视图之前重新设置标题。

您可能会问"为什么不直接旋转UIAlertview -show方法? 那太好了,但是在我的实验中,我发现UIWebView从未调用过show。 它一定是在调用其他内部方法,我没有进一步调查。

我的解决方案在 UIAlertView 上实现了一个类别,它添加了几个类方法。 基本上,您使用这些方法注册 UIWebView,并提供要使用的替换标题。 或者,您可以提供一个在调用时返回标题的回调块。

我使用 iOS6 NSMapTable 类来保留一组 UIWebView-to-Title 的映射,其中 UIWebView 是一个弱键。 这样我就不必注销我的UIWebView并且一切都得到了很好的清理。 因此,此当前实现仅限 iOS6。

下面是在基本视图控制器中显示的代码:

#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
解决方案

3:经过思考,这里有一种比我在解决方案 2 中最初描述的更简单的技术。 它仍然取决于一个未记录的事实,即javascript警报是使用UIAlertView实现的,该UIAlertView将其委托设置为源UIWebView。 对于此解决方案,只需对 UIWebView 进行子类化,并为 UIAlertView 实现您自己的委托方法,即可呈现警报视图:,并在调用时将标题重置为所需的任何内容。

@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

不,你不能在 Javascript 警报中执行此操作。

但是,如果 Javascript 是你的,那么与其调用警报,不如调用调用 Objective C 并调用 iOS 本机警报的函数。

如果Javascript

不是你的,那么使用UIWebView,你可以注入一些Javascript来覆盖默认行为,并将其更改为调用iOS本机警报,即像这样的东西。

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

然后在UIWebView的shouldStartLoadWithRequest中查找myScheme并提取消息

这是从Javascript调用Objective-C的标准方法

如何从Javascript调用Objective-C?

对于它的价值,Phonegap/Cordova提供了一个navigator.notification.alert功能。它允许指定自定义标题。

只需为UIWebView创建一个类,并在其中生成willPresentAlertView(alertView:UIAlertView(函数。之后alertView.title = "your fav title"设置并完成。

    import UIKit
class webviewClass: UIWebView {
func willPresentAlertView(alertView: UIAlertView) {

    alertView.title = "my custum title"
}
}

之后,当出现警报或确认并显示时,将显示您的自定义标题。

离开@twk写的内容,我只是覆盖了警报功能。在 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);
}

使用 cordova-plugin-dialogs。它允许创建本机对话框。

以下代码将创建本机警报:

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