WebKit on the iPhone

简介: 如果你开发一个应用程序,它是显示一个web页面或HTML文件,您可以使用WebKit框架,这是MacOS和iPhone OS的一部分。 但是即使再mac上,webkit的框架也提供了奖金160个公共的头文件甚至有更多的类和方法,你可以使用他控制很多东西,包括加载,渲染显示和修改页面。但iPhone上仅仅给我们了一个类(UIWebView)来控制这一系列错作。尽管UIW

如果你开发一个应用程序,它是显示一个web页面或HTML文件,您可以使用WebKit框架,这是MacOSiPhone OS的一部分。

但是即使再mac上,webkit的框架也提供了奖金160个公共的头文件甚至有更多的类和方法,你可以使用他控制很多东西,包括加载,渲染显示和修改页面。但iPhone上仅仅给我们了一个类(UIWebView)来控制这一系列错作。尽管UIWebView用的是相同的WebKit组建,但mac是作为公有的API,而iPhone是作为私有的API,所以不能使用。很少的UIWebView的方法足够漂亮的文本,但是对于一个浏览器(iCab Moblie)或者其他基于网页的应用这是不够的,缺失很多的必要的方法。


一些例子:

UIWebView没有提供一个方法来获取当前显示的标题web页面,

它只是忽略所有试图打开链接,旨在打开新窗口或选项卡

它不允许访问HTML

WebKit本身为所有这些任务提供了许多类,但他们都是私有的和iPhone所不具备的。


一些在应用商店的更改的浏览器都刚刚宣布这些限制作为一种特性(例如他们广告无法打开新窗口或标榜为没有烦人的弹出式窗口”)。这听起来很不错,但现实使用中,这并不使这样的浏览器很有用。


所以我们能做些什么来克服这些局限性的UIWebView?MaciPhone没有违反与苹果iPhone SDK协议下,我们可以(重新)实现所有很酷的特性是可用的WebKit框架,iPhonemac一样。不幸的是,我们不能。但我们可以实现许多丢失的特性。


如果你看一下可用的方法,只有一个,这将允许访问web页面的内容,这是或多或少的惟一办法回丢失的特性。和这种方法是

- (NSString *)stringByEvaluatingJavaScriptFromString:(NSString *)script;

这个方法是用来执行JavaScript代码上下文中的当前web页面,并返回一个字符串作为结果。这意味着我们必须使用JavaScript代码来实现我们需要的特性。

让我们先从一些非常容易。我们实现方法来获取标题和URL的当前显示的web页面。我们实现这个作为一个Objective-C“类别”,所以我们不需要子类UIWebView:

File: MyWebViewAdditions.h

@interface UIWebView (MyWebViewAdditions)
- (NSString*)title;
- (NSURL*)url;
@end

File: MyWebViewAdditions.m


#import "MyWebViewAdditions.h"

@implementation UIWebView (MyWebViewAdditions)

- (NSString*)title
{
    return [self stringByEvaluatingJavaScriptFromString:@"document.title"];
}

- (NSURL*)url
{
    NSString *urlString = [self stringByEvaluatingJavaScriptFromString:@"location.href"];
    if (urlString) {
        return [NSURL URLWithString:urlString];
    } else {
        return nil;
    }
}

@end

我们现在在做什么呢?

JavaScript的观点是,一个网页所代表的文档对象有几个属性。一个属性是“title”包含页面的标题。所以用“document.title”我们可以在JavaScript访问文档的标题。而这正是我们需要传递参数的方法”stringByEvaluatingJavaScriptFromString:“获得文档标题。

对于URL我们做类似的事情。

所以每当我们需要得到titleURLweb页面,显示在一个UIWebView对象,我们只需要调用“title”“URL”方法:

NSString *title = [anyUIWebViewObject title];

接下来我们想做的限制可能是想地址是不能打开链接将打开一个新窗口。在Mac上的WebKit只会调用一个委托方法的主机应用程序请求一个新的WebView对象创建一个URL请求。应用程序将创建一个新的WebView对象并加载新页。但是在iPhone的UIWebView并不支持这样一个委托方法,因此所有试图打开一个链接只是忽略。

这些链接做通常看起来像这样:

<a href="destination" target="_blank">Link Text</a>

The “target” attribute defines where the link will open. The value can be a name of a frame (if the web page has frames), the name of a window or some reserved target names like “_blank” (opens a new window), “_self” (the window itself), “_parent” (the parent frame, if there are nested frames) and “_top” (the top-level or root frame, or identical to “_self” if the page doesn’t use frames).

As a first step, we want to tap on a such a link in our iPhone App, and the link should open like any other normal link in the same UIWebView object. What we need to do is simple: we need to find all links with a “target” attribute set to “_blank” and change its value to “_self“. Then the UIWebView object will no longer ignore these links. To be able to modify all of the link targets we have to wait until the page has finished loading and the whole web page content is available. Fortunately UIWebView provides the delegate method

- (void)webViewDidFinishLoad:(UIWebView *)webView;

which will be called when the web page has finished loading. So we have everything we need: We get notified when the page has loaded, and we know a way to access and modify the web page content (using “stringByEvaluatingJavaScriptFromString:“).

First we write our JavaScript code. Because this will be a little bit more code than what was needed to get the document title, it’s a good idea to create an extra file for our JavaScript code and then we add this file to the resources of our project in XCode:

File: ModifyLinkTargets.js:

function MyIPhoneApp_ModifyLinkTargets() {
    var allLinks = document.getElementsByTagName('a');
    if (allLinks) {
        var i;
        for (i=0; i<allLinks.length; i++) {
            var link = allLinks[i];
            var target = link.getAttribute('target');
            if (target && target == '_blank') {
                link.setAttribute('target','_self');
            }
        }
    }
}

What is this JavaScript function doing, when called?
It gets an array of all links (“a” tags) and then loops through all of these tags, checks if there’s a target attribute with the value “_blank“. If this is the case it changes the value to “_self“.

Note: There are other tags which can have a “target” attribute, like the “form” tag and the “area” tag. So you can use the “getElementsByTagName()” call to get these tags as well and modify their target attributes in the same way as I’ve done this for the “a” tag.

In our iPhone App we need to define a delegate for the UIWebView object and this delegate object will be called whenever the web page has finished loading. This is the method that is called in the delegate by the UIWebView object:

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSString *path = [[NSBundle mainBundle] pathForResource:@"ModifyLinkTargets" ofType:@"js"];
    NSString *jsCode = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];

    [webView stringByEvaluatingJavaScriptFromString:jsCode];

    [webView stringByEvaluatingJavaScriptFromString:@"MyIPhoneApp_ModifyLinkTargets()"];
}

What is this code doing?
At first the access path of the JavaScript file we created before is retreived from the application bundle and we load the content of the file (the JavaScript code) into a string. Then we execute/inject this code into the web page and finally we call out JavaScript function
which is modifying the link targets.

Some notes:

  • Getting the JavaScript file from the application bundle and loading it into a string should be usually done somewhere in the init methods of your UIWebView delegate object. This way the string with our JavaScript code is only loaded once and can be simply reused whenever a new link is clicked and a new page is loaded.
  • Using a long name for our JavaScript function which also includes a prefix like “MyIPhoneApp_” makes it unlikely that the code we inject into a web page will interfere or confict with functions and variables which the web page itself has already defined for its own purposes. This is especially important when we modify web pages we haven’t created ourselves and where we can not predict which function or variable names the JavaScript code of the web page is already using.
  • Using separate calls of “stringByEvaluatingJavaScriptFromString” to first injecting our own JavaScript code and then calling our own JavaScript function to start modifying the link targets seems to be more complicated that necessary. And for this simple example you would be right. But it is likely that you’ll define much more additional JavaScript functions for many different tasks as well. Some of the tasks are started when the page has finished loading (like modifying the link targets), but some tasks will be started later and maybe even multiple times. And so it makes much sense that injecting the code and calling the functions are done in separate calls.
  • The delegate method “webViewDidFinishLoad:(UIWebView *)webView” is called for each frame, not only when the page itself has finished loading. This means that this delegate method can be called multiple times while a single web page is loaded. I think that this can be called a bug in the iPhone OS, but nevertheless it is important to know. When you modify the web page, be aware that this might be done multiple times and so make sure that none of your modifications will have bad side effectes when being modified a second time.

What next?

  • The above example code does not cover web pages where new windows are opened using JavaScript.
  • The links will open in the same window, which is fine because they are no longer ignored. But they still don’t open in a new window or Tab.

More about this topic and the cases which are not yet covered will come in the second part of the “WebKit on the iPhone” article.

Feel free to ask questions and write comments. I’d like to get some feedback.




目录
相关文章
|
存储 大数据 数据库
元数据驱动的微服务架构(上)
传统的模型方式的核心目标是能够自动生成代码,故定义过于复杂。而微服务间的“语言”的目标与传统不同,用元数据作为“语言”驱动整个微服务架构是不错的选择。本文为普元软件产品部副总兼大数据产品线总经理王轩在云计算架构设计群的微课堂分享。
8327 0
|
弹性计算 固态存储 Linux
云服务器ECS,你真的懂吗?
云服务器ECS是阿里云最基础的弹性计算服务,总计规划了十余种规格族,几十种规格。用户在面对阿里云众多的产品种类,颇有种挑花了眼不知如何入手的感觉,本文就对阿里云最基础的云服务器ECS进行一下详细的介绍。
24054 5
|
Ubuntu Linux Shell
Docker和孤儿进程、僵尸进程
#Docker和孤儿进程、僵尸进程 ##前言 在unix/linux系统中,正常情况下,子进程是通过父进程fork创建的。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。 ### 孤儿进程 * 父进程先于子进程退出,那么子进程将
10501 0
|
SQL 算法 Java
一线架构师带你玩性能优化
系统优化一个方面是系统化的对IT系统或交易链上的每个环节进行分析并优化,另一个是对单一系统进行瓶颈点分析和调优。优化的目标无非是:提高系统的响应速度、吞吐量、降低各层耦合,以应对灵活对边的市场。
10025 0
|
API Docker 容器
使用dvm解决Docker的Daemon和Client版本不一致问题
服务通过Docker Swarm部署后,如果在开发机本地执行类似如下命令: ``` docker -H tcp://swarm.alibaba.net:8443 exec -it $containerId /bin/bash ``` 如果开发机本地Docker版本高于服务器上的版本(这是很容易发生的,稍微Geek的同学是无法容忍本地软件不是最新这一事实的!),就会得到类似如下错误:
7399 0
|
弹性计算 负载均衡 安全
业务需要全球部署?来看看企业级全球网络架构与解决方案
在阿里云云栖社区举办的在线分享直播中,来自阿里云的企业级网络产品经理李泉带来了《企业级全球网络架构和解决方案》的分享,分享中他主要从企业级全球网络、典型场景与方案、典型案例三个方面介绍了阿里云企业级全球网络架构与解决方案。
19775 0
|
SQL 分布式计算 MaxCompute
MaxComputeSql性能调优
 转载自xiaorui         部分用户(尤其对外输出)使用MaxCompute(原Odps)时,由于对产品的使用层面和执行层面了解程度不同,导致提交的任务执行时间过长、占用了较多集群资源;严重的会导致失败、不仅需要投入支持同学精力协助解决、也影响了用户正常业务。 合并整理部分性能提升方法方
8128 0
|
存储 分布式计算 大数据
开源大数据技术专场(下午):Databricks、Intel、阿里、梨视频的技术实践
本论坛第一次聚集阿里Hadoop、Spark、Hbase、Jtorm各领域的技术专家,讲述Hadoop生态的过去现在未来及阿里在Hadoop大生态领域的实践与探索。
8819 0
|
SQL Oracle 关系型数据库
Oracle之sqlldr sqluldr用法
Oracle之sqlldr sqluldr用法
5636 0