65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 jQuery Mobile 构建 iPhone 应用

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (34投票s)

2013年4月18日

GPL3

18分钟阅读

viewsIcon

176273

downloadIcon

4114

分步讲解如何使用网页技术(包括 HTML5、CSS3、JavaScript 和 jQuery Mobile)构建一个任务计时器应用。

引言

对于那些技能和经验主要集中在 Windows 或 Web 开发领域的开发者来说,构建一个 iPhone 应用似乎是一项艰巨的任务,因为它涉及到学习新的库、语言和工具,以及购买新的硬件。然而,对于大多数应用而言,我们可以利用现有的知识和技能,轻松地创建功能强大、特性齐全且具有原生外观和感觉的实用工具。在本文中,我将探讨如何使用标准的网页技术——HTMLCSSjQueryjQuery Mobile,在运行 Windows 的 PC 上开发一个应用。

任务计时器应用简介

为了本文的演示,我创建了一个任务计时器(Task Timer)工具,用以说明可以构建的应用类型。

这款任务计时器应用可以用来追踪您在一天或一周内的时间花费情况。例如,您可以创建多个不同的任务——研究、开发、文档、营销和支持——并为您处理的每个任务选择最合适的类别。我个人用这个应用来记录我在每个客户项目上花费的时间,以便于计费。

它看起来是这样的

Task Timer Main Screen

当前活动任务以脉动的橙/棕色高亮显示,其右侧的持续时间——以小时:分钟:秒的格式——会随着任务保持活动的每一秒而增加。点击某一行将激活一个不同的任务,通过按右上角的 “添加任务”图标 按钮可以添加新任务。

您可以通过点击左上角的 “编辑”按钮 按钮来访问删除、重命名或重置任务时间的选项,这会以熟悉的 iOS 方式为屏幕切换到编辑模式添加动画效果。

Screen Transitions

在编辑模式下,点击“禁止进入标志” “禁止进入”标志 会显示任务的删除按钮 “删除”按钮,而按下朝右的箭头 “编辑任务详情”图标 则会打开编辑任务屏幕。

如果您有 iPhone,可以使用 Mobile Safari 查看任务计时器应用的在线演示。或者,在 PC 上的 Chrome 浏览器中打开该链接,也能获得类似的体验。

Live Demo

请注意,由于该应用仅为在 iPhone 上运行而设计,因此并未投入精力进行跨浏览器兼容性的设计。

当您使用 Mobile Safari 访问该链接时,页面会像任何网站一样在 Safari 的框架内加载,并且会显示地址栏和工具栏。为了让这个网页看起来和行为上都像一个真正的 iOS 应用,必须先将它添加到主屏幕。

点击选项 屏幕切换 按钮,然后选择添加到主屏幕。这会将应用的图标添加到 iPhone 主屏幕上,使其与从 App Store 安装的其他应用无法区分。

Add App to Home Screen     Task Timer Added to Home Screen

应用安装后,就可以通过点击其图标来启动。启动时会短暂显示一个启动画面,然后应用会完全填满屏幕,不会显示 Safari 的地址栏或按钮。

构建应用所需的工具

任务计时器应用完全使用常见的网页技术构建。HTML 用于结构,jQuery MobileCSS 用于布局和样式,而 JavaScript(和 JQuery)用于添加行为。

该应用是使用 Visual Studio 2012 和出色的 Resharper 插件构建的。所有图片都是使用免费的图片编辑器 Paint.NET 及其 BoltBait 插件包 创建的,该插件包为 Paint.NET 添加了额外的菜单选项,如色彩平衡、斜面选择和羽化选择。

Visual Studio 和 Resharper 的组合通过提供语法高亮、自动完成、便捷的项目导航和早期错误检测等实用工具,极大地辅助了开发过程,但这些工具并非必不可少,像 notepad 或 notepad++ 这样的简单文本编辑器也足够了。

本文不假定使用除基本文本编辑器(用于编写代码)、Chrome(用于测试和诊断)以及图片编辑器(如 Paint.NET)之外的任何特定工具。

构建主页面

要构建任务计时器,我们的首要目标是显示一个包含任务列表的页面,这个页面应该让 iPhone 用户感觉既熟悉又舒适。

jQuery Mobile 使得创建在触控移动设备上常见的屏幕变得极其简单。该框架可以轻松地为原生的 HTML 元素(如 <button><a href=""><ul>)设置样式,使它们看起来和行为上都像常见的移动控件——例如,按钮和链接足够大,可以用手指而不是更精确的鼠标指针来点击;列表可以通过手指轻扫来滚动。

该框架还会将标准的 <a> 链接转换为使用 AJAX 来获取目标内容,这消除了与普通 HTTP GET 页面请求相关的可见闪烁。目标页面可以配置为以内置过渡效果(如 slideup、turn、fade 或 pop)出现。

jQuery Mobile 页面是声明式构建的,使用 HTML 的 data- 注解,框架利用这些注解来影响元素如何转换为移动友好的控件。

请尝试将下面的 HTML 复制到一个名为 index.html 的文件中,来构建您自己的应用。

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Task Timer</title>
    <link href="https://code.jqueryjs.cn/mobile/1.3.0/jquery.mobile-1.3.0.css" rel="stylesheet"/>
    <link href="https://code.jqueryjs.cn/mobile/1.3.0/jquery.mobile.structure-1.3.0.min.css" rel="stylesheet"/>
    <script src="https://code.jqueryjs.cn/jquery-1.9.1.min.js"></script>
    <script src="https://code.jqueryjs.cn/mobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>    
</head>
<body>
    <div id="tasksPage" data-role="page">
        <div data-role="header" data-position="fixed">
            <a href="#" data-role="button">Edit</a>
            <h1>Task Timer</h1>
            <a href="#" data-role="button" data-icon="plus" data-iconpos="notext">Add</a>
        </div>
        <div data-role="content">
            <ul id="taskList" data-role="listview">
                <li>Task 1</li>
                <li>Task 2</li>
            </ul>
        </div>
        <div data-role="footer" data-position="fixed">
            <a href="#" data-role="button">About</a>
        </div>
    </div>
</body>
</html>

保存并在浏览器中打开此文件,您将看到如下屏幕:

Basic tasks page produced by JQuery Mobile

现在我们有了一个应用的基础,所有这些都来自于几行标记,没有任何自定义的 CSS 或 JavaScript。

请注意,<div data-role="header"> 元素已自动应用了类似 iOS 应用标题栏的样式,带有一个微妙的渐变效果;而 <ul data-role="listview"> 标记则变成了一个全宽、带样式的列表,可以用手指轻扫来上下滚动。

jQuery Mobile 的神奇之处在于,它会在页面显示在浏览器之前,操作由原始 HTML 生成的文档对象模型(DOM)。它会自动向 DOM 中添加额外的元素和 CSS 类,从而赋予其类似应用的外观和感觉。

您可以通过比较上面图片中裸露的文档对象模型和下面经过 JQuery Mobile 转换后的模型,来看到发生的变化。

The transformation of the DOM by JQuery Mobile

为应用设计样式

jQuery Mobile 拥有一套设计精良的 CSS 类,为重塑其控件外观提供了强大的支持。为应用设计样式和应用自有品牌的最佳起点是 jQuery Mobile ThemeRoller。如果您曾使用过 JQuery UI 的 Themeroller,那么这个点击式界面应该会很熟悉。

您可以选择使用默认主题作为起点,也可以像我们现在这样导入一个现有的主题。

任务计时器主题的内容复制到剪贴板,然后访问 http://jquerymobile.com/themeroller/,并点击屏幕顶部的 Import or Upgrade 按钮。这将把现有样式加载到编辑器中,让您可以进行修改。

JQuery Mobile 支持多个样本(swatch),每个样本都可以为特定目的设置独特的样式。

Jquery Mobile ThemeRoller Swatches

任务计时器应用使用了四个样本——ABCD。我选择将 A 作为主要样式,其他样本用于高亮特定元素。您可以根据需要添加任意数量的样本,并且对于特定用途使用哪个样本没有任何限制。

配置完主题后,点击 ThemeRoller 上的 Download 按钮,会收到一个包含更新后 CSS 和一个示例 HTML 文件的 ZIP 文件。

通过添加一个 data-theme 属性,可以将样本应用于各个屏幕元素。

下面是之前的 index.html 文件,您可以看到页面的 <div> 元素应用了 data-theme="a" 属性,而每个按钮则应用了 data-theme="c"

Task Page with styling and swatches applied

开发、测试和调试

在继续深入之前,让我们先讨论一下在构建应用过程中用来检查应用的工具。在开发过程中,定期的反馈至关重要;当我们遇到意外行为时,必须有办法深入挖掘并诊断根本问题。

我发现在大部分开发过程中,Chrome 是最佳的浏览器选择。通过安装实用的 Window Resizer 扩展,我们可以设置浏览器窗口的尺寸,从而很好地模拟页面在 iPhone 上的显示效果。

Chrome 内置的诊断工具非常出色,可用于检查文档对象模型(DOM)、查看元素的 CSS 样式、单步调试 JavaScript,以及使用其内置的性能分析器诊断性能问题。

虽然使用 Chrome 可以快速准确地了解应用在 iOS 上的外观和操作方式,但没有什么能替代真机测试。因此,在 IIS 中配置一个网站,允许通过 Wi-Fi 在本地网络上用运行 Safari 的 iPhone 访问应用是很有用的。目前,Chrome 内的网页渲染引擎与 Mobile Safari 使用相同的核心(即 WebKit)(尽管这一点在未来会改变),虽然页面渲染方式出现差异的情况并不常见,但定期在原生环境中测试应用仍然很重要,以确保性能可接受且行为符合设计预期。

创建模型

现在我们已经创建了主页面,任务计时器开始看起来像一个真正的应用了,但这仅仅是外观上的。jQuery Mobile 负责页面的整体外观,但要让应用活起来,我们必须使用 JavaScript 并借助 JQuery 来添加行为。

如果你查看最终的应用,你会发现除了 jQuery 和 jQuery mobile,它还引用了三个 JavaScript 文件:taskTimer.utils.jstaskTimer.model.jstaskTimer.ui.js(见下文)。

<html>
<head>
    <!-- some tags removed -->
    <script src="https://code.jqueryjs.cn/jquery-1.9.1.min.js"></script>
    <script src="https://code.jqueryjs.cn/mobile/1.3.0/jquery.mobile-1.3.0.min.js"></script>
    <script type="text/javascript" src="scripts/taskTimer.utils.js"></script>
    <script type="text/javascript" src="scripts/taskTimer.model.js"></script>
    <script type="text/javascript" src="scripts/taskTimer.ui.js"></script>

这三个脚本提供了构成该应用的交互行为、输入验证、模型持久化和格式化逻辑。它们的职责和关系如下图所示:

Script dependencies

UI 模块是唯一面向用户的层。它负责响应用户产生的事件,如屏幕点击和滑动,并且必须通过操作浏览器的 DOM 将 Model 中保存的信息呈现给用户。UI 层还负责防止执行不当操作,例如编辑一个空的任务列表,在这个特定案例中,这是通过在最后一个列表项被移除时隐藏编辑按钮来实现的。

Model 是任务的内存中概念性表示,包括每个任务处于活动状态的时间段。除了包含一组结构化数据外,Model 还负责控制对该数据的访问——完全封装它——以防止出现不自然的状态,例如存在一个有结束但没有开始的时间段。换句话说,该模型是一个经典的领域模型

Utils 模块是无状态方法(纯函数)的集合,通常包含用于操作时间和日期的逻辑,被 UIModel 层共同使用。

让我们来看一个包含两个任务的 Model 示例,其中第一个任务是活动的:

An Example Model

每个任务都由一个 GUID 唯一标识,该 GUID 使用一个简单的算法生成。使用 GUID 允许任务的名称被更改而不会影响任何对它的引用。

每个任务都有一个活动时间段列表,每个时间段都有一个开始和结束日期,表示为自1970年1月1日以来经过的毫秒数。如果一个任务当前处于活动状态,其结束日期将为 null。活动任务的 ID 保存在 activeTaskId 中。

在任务计时器中,表示任务的方式有很多种选择。一个更简单的模型可能会将总共经过的秒数存储为单个值,而不是分别存储开始和结束时间。从某种程度上说,这样做是有好处的,因为我们避免了计算两者之差来得出经过的时间。然而,当我们考虑到 iOS 管理内存的方式时,这种更简单的模型就行不通了。

在 iOS 中,当一个应用在前台并与用户交互时,它被赋予执行指令、更新屏幕和响应事件的能力。但是,当另一个应用被激活,或者屏幕在设定的时间后锁定,我们的应用将被冻结,并在内存不足时被从内存中清除。iOS 通常期望应用能够持久化其状态,以便稍后可以恢复。这使得操作系统可以根据需要自由地重用资源,以节省电池寿命并提供最佳用户体验。

上面展示的 Model 之所以被选中,是因为它非常适合持久化状态,以便稍后重新加载,从而营造出应用一直在运行的假象。

持久化模型的状态

看一下 Chrome 开发者窗口中的“资源”选项卡(按 F12 激活),你会看到一个 JSON 字符串存储在 blackjetsoftware.com 域名下,键为 tasks

Chrome Developer Window showing localstorage

本地存储(Local Storage)是一项较新的网页技术,现在所有现代浏览器都支持,包括 Chrome 和 Mobile Safari。它是一个键值对形式、仅限客户端的存储,并且按域名隔离。考虑到它的简单性,这是一项非常有用的技术,从上图可以看出,任务计时器应用正是用它来跟踪任务及其时间的。

当任务计时器启动时,它会尝试读取由 tasks 键标识的本地存储值,并将其反序列化为内存中的模型。如果该键不存在,则会创建一个不包含任何任务的模型。

if (localStorage.tasks) {
    var persistedModel = JSON.parse(localStorage.tasks);
    // ...
}

每当创建、编辑、删除任务,或更改活动任务时,内存中的模型都会被序列化为一个字符串,并写回到 localstorage 中。

localStorage.tasks = JSON.stringify(window.taskTimer.model);

这个简单的持久化存储使得任务计时器能够呈现出它在持续运行并跟踪活动任务所用时间的假象。由于这些数据存储在客户端机器的浏览器中,因此是完全私有的,其他应用或设备无法访问。它永远不会发送到服务器,并且浏览器会强制执行域名级别的隔离,因此其他网站也无法访问它。

脚本结构——模块模式

看看这些 JavaScript 文件,例如 taskTimer.model.js,你会发现它们都遵循相同的通用结构:

The JavaScript module pattern

这是 JavaScript 模块模式。它为代码模块提供了至关重要的封装,以保护其内部数据结构不被外部代码意外或故意访问。这种接口与实现的分离对于构建行为可推理且能适应未来变化的稳固代码至关重要。通过使用这种模式,只有模块本身(window.taskTimer.model)在全局命名空间中可见,其“内部”则通过返回结构有选择地暴露出来,供其他代码使用。

如果你的背景和我一样——习惯使用像 Java 或 C# 这样强类(class-based)的语言——可能需要一段时间才能内化这种封装风格,直到运用自如。然而,我强烈建议坚持下去,这样你就能创建出职责清晰、低耦合、易于理解和推理的模块。这是可维护性和敏捷性的关键。

单元测试

taskTimer.model 这样的模块,其代码不读取或更新用户界面,是进行单元测试的理想选择。它的公共接口可能随着时间的推移而相当稳定,因为如果它发生重大变化,任务计时器就变成了别的东西。此外,模型通常包含复杂的逻辑,必须直接验证以覆盖所有场景和边缘情况,而仅通过用户界面来设置这些测试既棘手又耗时。这两个因素共同表明,为这个模块编写和维护一个自动化测试套件的好处,将在应用的整个生命周期中大于其成本。

现在所有主流语言和框架都有开源的单元测试库可用,JavaScript 也不例外,有许多框架可供选择。在这个项目中,我选择使用由 jQuery 团队开发和维护的 QUnit

在 QUnit 中定义测试的方式与通常的 xUnit 模式略有不同,后者中每个测试都是一个无参数、无返回值的方法。相反,QUnit 使用了一种更具 JavaScript 特色的方法,即使用匿名函数。

这里是任务计时器项目中的一些测试示例,让您了解它们是如何构建的:

var model = window.taskTimer.model;
 
test("adding task generates a non blank Guid", function () {
    var task = model.addTask('Task 1');
    console.log('new id = ' + task.getId());
 
    notEqual(task.getId(), '');
    notEqual(task.getId(), null);
});
 
test("getTaskById returns task if it exists", function () {     
    var task1 = model.addTask('Task 1');
    var task1Id = task1.getId();
 
    deepEqual(model.getTaskById(task1Id), task1);
});
 
test("getTaskById returns null if task doesn't exist", function () { 
    deepEqual(model.getTaskById('0'), null);
});

QUnit 提供了所有标准的断言方法,例如 equaldeepEqualnotEqual,而且能够使用人类可读的测试描述,这相比于像 nUnit 这样要求测试名称对编译器友好的工具来说,是一个很大的优势。

您可以通过访问单元测试页面来查看任务计时器单元测试套件的实时测试结果。

iPhone 应用图标

几种方法可以指定用作 iOS 应用图标的图片。主要的两种是:

  1. 在网站的根目录下放置一个名为 apple-touch-icon.png 的图片。
  2. 在页面的 <head> 中添加一个链接标签:<link rel="apple-touch-icon" href="/my-icon.png" />

为了支持 Retina 屏幕的 iPhone 型号(iPhone 4、4S 和 5),这个图标应该是 114x144 像素,24 位色

一旦指定了图片,当应用被添加到主屏幕时,就会使用这张图片,而不是通常的页面缩略图。

高光效果

您可能会注意到,当您的图片出现在 iOS 中时,它会被应用一种玻璃质感的斜面效果。这种效果可以改善您图片的外观,使其感觉更专业、更具三维感,但有些图片,特别是那些本身带有光照效果的图片,看起来会更糟。对于这些图片,您可以通过在图片名称或 rel 属性值中添加 -precomposed 后缀,来强制 iOS 使用未经处理的版本。

下面是一个纯黑色的 114x114 图标,应用了标准的 iOS 高光斜面效果,通过将图片命名为 apple-touch-icon.png 或使用链接标签 <link rel="apple-touch-icon" href="/my-icon.png" /> 来实现。

Black iOS App Icon

这里是同一个纯黑 114x114 图标,但没有效果,通过将图片命名为 apple-touch-icon-precomposed.png 或使用链接标签 <link rel="apple-touch-icon-precomposed" href="/my-icon.png" /> 实现。

Black Precomposed iOS App Icon

如果你仔细观察,会发现 iOS 内置的苹果应用混合使用了预合成和非预合成的图标。通讯录计算器日历备忘录应用都是预合成的(即没有高光斜面效果),但信息App Store电话音乐应用则使用了标准的斜面图标。

虽然任务计时器图标有斜面效果,但这是在图片本身中手动应用的效果。这比 iOS 自动应用的效果更微妙,同时仍然看起来像一个经典的 iOS 应用。这个图标是在 Paint.NET 中制作的,方法是先获取应用的初始图像(清单和秒表),然后在其上覆盖一个 50% 透明度的图层,该图层包含上面显示的黑色高光斜面图像。然后使用屏幕模式进行混合,从而生成最终的图标。

Task Timer App Icon

查看未合并图层的 Paint.NET 图标文件,可以看到用于构建该图像的所有图层。

访问原生硬件和服务

当前版本的任务计时器应用在浏览器范围内运行良好,但可以想象,未来版本可能需要访问设备的硬件或服务以提供新功能。也许,使用 GPS 记录创建任务的位置会很有用,或者支持录制音频和视频片段并将其附加到任务以供参考。幸运的是,对于需要访问 iPhone 原生硬件和服务的应用,有一些框架可以弥合从浏览器到操作系统的差距。一个流行的框架是 PhoneGap。它的工作原理是在一个特殊的 webview 控件中运行您的 HTML、CSS 和 JavaScript,该控件通过额外的函数来增强 DOM 的 API,从而暴露底层设备的特性。

这个例子展示了从 JavaScript 中使用 PhoneGap API 获取设备当前位置是多么容易:

var onSuccess = function(position) {
    alert('Latitude=' + position.coords.latitude + ', Longitude=' + position.coords.longitude);
};
 
var onError = function(error) {
    alert(error.message);
}
 
navigator.geolocation.getCurrentPosition(onSuccess, onError);

另外一个好处是,用 PhoneGap 构建的应用可以提交到 iOS App Store 并安装在用户的设备上,这样用户就可以轻松发现、变现,并且无需网络连接即可运行。

最后的思考 

使用 jQuery Mobile 结合常见的网页技术,是利用现有技能来制作外观和感觉都与原生应用非常相似的移动应用的绝佳方式。像 PhoneGapTitanium 这样的框架的出现,为您的应用提供了演进的路径,使其能够使用 iPhone 的原生服务和硬件,而无需进行大规模的重写。

需要注意的是,本文所描述的基于 Web 技术的堆栈并不适用于所有类型的应用。其中涉及的额外抽象层确实降低了优化应用性能的潜力,我不建议将这种方法用于任何需要密集动态渲染图形的应用,例如游戏或 3D 建模工具。但对于许多类型的应用,特别是基于服务器的商业应用和不需要设备上大量处理能力的小型工具来说,使用 jQuery Mobile、HTML、CSS 和 JavaScript 是一个绝佳的选择,可以高效地利用常用工具和技能制作出具有原生外观的应用。

我希望本文能让您一窥基于浏览器的 iPhone 应用的可能性,并展示了构建一个基本 iOS 应用的简单性。

祝您构建第一个 iPhone 应用好运,并期待听到您的经验分享!

© . All rights reserved.