【问题标题】:Change JavaScript alert dialog title in iOS在 iOS 中更改 JavaScript 警报对话框标题
【发布时间】:2012-05-27 17:24:11
【问题描述】:

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

【问题讨论】:

  • 我想在ios中更改网页的javascript警报对话框标题。什么是bij?
  • 这是一个很好的问题,要找到关于这个主题的信息并不容易。如果您投了反对票,您能否提供一些有关如何改进问题的信息?

标签: javascript ios uiwebview


【解决方案1】:

基本上你不能改变标题,但最近我找到了一种方法来显示一个没有标题的警报对话框。

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

【讨论】:

  • 太棒了!谢谢你。
  • 在 ios safari 的浏览器中运行良好......但在应用程序中不太好。
  • 它在 iOS 7 中运行良好,但在 iOS8 中它以粗体显示文本。有什么想法吗?
  • 它在 mozilla(版本 41.0.2)中抛出“未捕获的异常:内存不足”。有什么想法吗?
  • 当“removeChild(iframe)”时,有时这会改变 ios webview 中 div 的顺序。
【解决方案2】:

我针对这个问题提出了三种不同的解决方案。生产代码的最佳解决方案是@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:首先,让我说我永远不会在生产代码中亲自使用以下解决方案。 实现此功能的最佳方法是执行@Martin H 在他的回答中建议的操作,或者如果实际上需要一个空标题,则可能是@twk 的建议。如果您无法控制 Web 内容本身,也许您可​​以在内容加载后注入 @Martin H 的代码。

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

我的方法是调配UIAlertView。我知道-swizzling sucks and has all sorts of drawbacks。如果我能提供帮助,我不会在生产应用程序中调皮捣蛋。但是对于这个科学项目,我们将混合UIAlertView setDelegate: 方法。

我发现 1) UIWebView 将构造一个 UIAlertView 来显示 javascript 警报,以及 2) 在 UIAlertView delegate 设置之前设置 UIAlertView 标题。通过使用我自己的方法调整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 实现的采购 UIWebView。对于此解决方案,只需将 UIWebView 子类化并为 UIAlertView willPresentAlertView: 实现您自己的委托方法,并在调用它时将标题重置为您想要的任何内容。

@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

【讨论】:

  • 从您的回答中,我了解到您非常精通 Objective-C。我真的不知道你是否曾经对 Web 视图做过任何工作,但如果你做过,你不认为如果你写一个 Cordova/Phonegap 用来制作的技术的简洁示例会更有用吗?从 JS 到 Objective-C 的桥梁?我很肯定他们使用的技术之一是 Javascript 中的 window.location,并观察 Objective-C 中 webview 位置的变化,读出嵌入在 URL 中的参数。然后可能会取消导航。我知道这项技术,但我不会编写有意义的 Objective-C。
  • 我说的原因是现在你有一个你自己说你不会使用它的答案。 Phonegap 技术肯定被广泛部署,所以它可能是一个合理的解决方案。
  • @MerynStol - 好的,你明白了。我为 window.location 技术添加了一个简洁的示例。也是我原始解决方案的一个更简单的变体。
  • 内容丰富!你应该得到赏金。 UIWebView 的子类化看起来也很吸引人。
  • @TomSwift 解决方案 1 适用于 alert() 函数,但不适用于位置权限警报!!!。不推荐解决方案 2。方案3不行,能拿到标题,但是改alertView的标题好像没有效果
【解决方案3】:

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

但是,如果 Javascript 是您的,那么您可以调用一个调用 Objective C 的函数并调用 iOS 本机警报,而不是调用警报。

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

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

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

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

How to call Objective-C from Javascript?

【讨论】:

    【解决方案4】:

    不管怎样,Phonegap/Cordova 提供了navigator.notification.alert 功能。它允许指定自定义标题。

    【讨论】:

      【解决方案5】:

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

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

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

      【讨论】:

        【解决方案6】:

        离开@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);
        }
        

        【讨论】:

          【解决方案7】:

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

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

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

          【讨论】:

            猜你喜欢
            • 2013-05-30
            • 2020-02-23
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-02-22
            相关资源
            最近更新 更多