如何在 iOS 7 的 UIWebView 中从 JavaScript 调用 Objective-C?

Posted

技术标签:

【中文标题】如何在 iOS 7 的 UIWebView 中从 JavaScript 调用 Objective-C?【英文标题】:How to invoke Objective-C from JavaScript within UIWebView in iOS 7? 【发布时间】:2014-05-15 18:12:54 【问题描述】:

请注意,这不是重复的,因为我的问题的“in ios 7”部分以及之前的行为(以及之前问题的答案)已经改变的事实。

我想知道通过 UIWebView 调用的 javascript 代码如何在 iOS 7 中调用 Objective-C 代码。这是我以前在过去的项目中广泛使用的东西,但是 iOS 7 上的行为发生了变化,结果是webView 委托的 didFailLoadWithError 之前没有被调用。

我曾经使用自定义 url 技巧来调用 Objective-C 功能:

function invokeObjectiveC(action, target, data) 
   var iframe = document.createElement("IFRAME");
   iframe.setAttribute("src", "MY-URLScheme:" + action + ":" + target + ":" + data);
   document.documentElement.appendChild(iframe);
   iframe.parentNode.removeChild(iframe);
   iframe = null;

导致 didFailLoadWithError: 被调用的特定行是这一行:

 document.documentElement.appendChild(iframe);

互联网上流传的另一种常见的替代方法是:

window.location = "MY-URLScheme:" + action + ":" + target + ":" + data;

这适用于 iOS 7,因为 didFailLoadWithError 不会被调用,但是它有一个(已知的)问题,如果连续快速调用两次,第一次调用就会丢失。

所以我想知道 Cordova 是如何做到这一点的,所以我下载了源代码并查看了一下,我认为他们的做法与我类似(第 3 行不同):

var iframe = document.createElement("IFRAME");
iframe.setAttribute("src", "MY-URLScheme:" + action + ":" + target + ":" + data);
document.body.appendChild(iframe);
iframe.parentNode.removeChild(iframe);
iframe = null;

但是,这仍然会导致 didFailLoadWithError 被调用。这让我很困惑为什么 Cordova 没有做任何事情来纠正这个问题(假设我正在正确地查看他们的代码)。

didFailLoadWithError 被调用的事实似乎并不有害,因为页面仍在加载并且 Objective-C 仍然被调用。但是,代码导致错误方法被调用是非常非常不令人满意的,所以我想找到一种替代方法。

所以问题是:有没有一种方法可以从加载到 UIWebView 中的 JS 中可靠地调用 Objective-C,并且不会在 iOS 7 上调用 didFailLoadWithError?

有人可以确认 Cordova 使用的是上述方式还是其他方式?

在 UIWebView(相对于 UIWebView 外部)中使用 JavaScriptCore 的解决方案在 iOS/脆弱中看起来没有文档/不受支持?例如:

Access the JavaScriptCore engine of a UIWebView

Trigger objective C method from javascript using JavaScriptCore in iOS 7 in ViewControllers

【问题讨论】:

【参考方案1】:

iframe 解决方法有点错误,而且不是很可靠。 JavaScriptCore 是一种很好的方法,但该框架在 iOS 6 和 iOS 5 上是私有的。有一种更好更快的方法可以从 UIWebView 调用 Objetive-C。您可以为警报功能实现类别方法。

@implementation UIWebView (JavaScriptAlert)
- (void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(WebFrame *)frame 

    if ([message hasPrefix:CUSTOM_SCHEME_STRING]) 
        //process your message
    
    else 
        UIAlertView * alert = [[UIAlertView alloc] initWithTitle:@"Alert" message:message delegate:nil cancelButtonTitle:@"Ok" otherButtonTitles: nil];
        [alert show];
        [alert release];
    
 
@end

当您从 javascript 调用 window.alert() 方法时,会调用上述 Objetive-C 函数。为您的消息使用自定义前缀,并在您需要标准警报时回退到 UIAlertView。

此方法适用于 iOS 5、6 和 7,没有问题。它非常可靠。我已经连续测试了 10000 次调用警报,并且没有消息丢失。 iframe 解决方案并非如此,如果您太快地创建 iframe,许多消息会丢失,因此您已经在 J​​S 代码上实现了一个批处理队列,并以大约 1 毫秒的间隔同步它们。

【讨论】:

谢谢,我试试。如果您只在 iOS 7 上运行,是否有 JavaScriptCore 解决方案? @MortimerGoro 谢谢..我想知道您所说的 WebFrame 参数是什么意思,非常感谢 @MacGeek WebFrame 参数存在,因为它在 runJavaScriptAlertPanelWithMessage 方法的签名中。您不必在方法实现中使用它,只需忽略它即可。 谢谢@MortimerGoro 我会尝试的:)

以上是关于如何在 iOS 7 的 UIWebView 中从 JavaScript 调用 Objective-C?的主要内容,如果未能解决你的问题,请参考以下文章

IOS6 横向在仅纵向 iPhone 应用程序中从 uiwebview 播放嵌入的 youtube 视频

在 UIWebView 中从 JS 返回一个 var

如何在 iOS 7 中从 WebApp 打开 Safari

在目标 c 中从 UIWebView.scrollView 写入 PDF 文件

iOS 7 - UIWebView 扩展菜单

如何在IOS中从经纬度查找地址?