如何构建Twitter iPad用户体验

Aaron Brethorst, 2011年5月16日

大约两个月前,我曾在Twitter上询问是否有人创建了Twitter iPad用户界面的开源版本。后来,我在GitHub上看到了三个独立的项目,提供了构建类似Twitter iPad界面的良好框架。今天,我们将分析我所发现的一个项目,看看如何在这之上尽可能完美地构建模仿Twitter iPad UI的克隆版本1

tl; dr

这是完成这个所需的GitHub仓库

StackScrollView项目

我们将使用StackScrollView,考虑到它所能做到的一切,这是一个相当简单的项目。您可以前往StackScrollView的Cocoa Controls页面查看,或者直接访问GitHub仓库

StackScrollView是由Raw Engineering创建的,这是一家在孟买和旧金山设有办公室的网页和移动咨询公司。

一旦您克隆了GitHub仓库,在Xcode中打开xcodeproj文件并在模拟器中运行它。您将看到一个左侧导航窗格和类似于Twitter iPad应用体验的滑动表格视图。

今天的课程

本篇将介绍以下四个主题

  1. 基本外观和感觉改变。
  2. 圆角表格视图角落。
  3. 左侧导航窗格的表格单元格、标题和页脚。
  4. 整合时间线。

基本外观和感觉改变

首先,让我们用苹果提供的带有纹理的滚动视图背景颜色替换StackScrollView项目中使用的背景,并删除导航窗格右侧垂直的1px白色线2

(查看这个提交)

圆角表格视图角落

接下来,我们将解决Twitter在堆栈中推入所有视图时可以看到的圆角。幸运的是,Cocoa Controls上还有一个项目显示如何做到这一点。Jeremy Collins的RoundedUITableView项目展示了如何复制苹果天气应用的体验,这对于我们的需求也完全适用。

(查看这个提交)

现在我们已经将RoundedUITableView类添加到项目中,我们需要使用它。它不太是“放入即走”,但几乎是这样。

DataViewController.h中,添加对RoundedUITableView的前向类声明并更改_tableView实例变量的类型。

同时,在DataViewController的实现文件中,#import "RoundedUITableView.h"并根据需要调整表格视图的实例化。当您在这里时,将表格视图的背景颜色从透明改为白色;否则,完成时看起来会很奇怪!

如果您现在运行项目,就会发现问题看起来不太对劲;表格视图的角落仍然会是方形。为了解决这个问题,我们需要修改 RoundedUITableView 中的一個 -init 方法。您会注意到 DataViewController 在调用 RoundedUITableView-initWithFrame:style: 时,但 RoundedUITableView 没有显式处理这个情况。打开 RoundUITableView.m 并将 -initWithFrame: 方法签名调整,包括一个样式参数,并相应地在其超类调用中添加该参数。

最后,我们需要删除在 StackScrollViewController.m 中设置的自定义背景颜色。这很简单,只需删除第 665 行的 backgroundColor 设置器调用。

(查看这个提交)

总结成就和一些错误修复

我们取得了很多成就,却不需要做太多工作。当然,已经有几个小问题开始出现

  • 错误的圆角半径 – 我们堆叠视图控制器的圆角半径略微不正确。目测后发现,圆角半径可能应该是 6 而不是 10。
  • 子级堆叠视图控制器应具有方形左边缘 – 如果您查看 iPad 版 Twitter 的屏幕截图,您会看到二级(以及三级)堆叠视图控制器的左边缘是方形的。这是一个小细节,但仍然很重要。

修复圆角半径问题非常简单。为此,我定义了一个名为 kCornerRadius 的预处理器宏,将其设置为 6,然后使用它替代之前代码中直接设置为 10.0 的圆角半径值。

(查看这个提交)

方形左边缘实现稍微复杂一些。作为第一步,让我们向 RoundedUITableViewMask 类添加一个属性,该属性用于确定是否应该绘制所有圆角,或者方形加圆角。由于我们是手动进行所有角落的遮罩,结果证明在 -setupView 中 -cornerRadius 属性可以移除。最后,我们将修改 -drawRect: 方法来绘制正确的圆角或方形半径,具体取决于情况。

(查看这个提交)

整体结合起来

让我们用我们新学会的正确绘制角落的能力来充分利用。我们将把 squareCorners 属性贯穿所有相关类。这不是最架构优雅的解决方案——当我们在应用中添加新的视图控制器类型时,它可能不会很好地扩展——但暂时来说应该足够有效。

(查看这个提交)

左侧导航面板表格单元

在我们进一步操作之前,我注意到我们应用的背景颜色是错误的。Twitter 使用纹理滚动视图的背景颜色,但它们将其显著加深。我们可以通过将 backgroundColorRootViewController 在第 99 行改为 [[UIColor scrollViewTexturedbackgroundColor] colorWithAlphaComponent:0.5] 来轻松模仿这种效果。

我们将使用来自(非常出色的)CC BY 许可证的 Glyphish 图标集 的图标作为导航面板。它可能没有我们所需要的一切,但对于我们的目的来说已经足够好了。一旦我们有了图标,我们将为客户单元打下基础。

导航面板中将有六个单元格

  • 时间线
  • 提及
  • 列表
  • 消息
  • 个人资料
  • 搜索

为此,最简单的方法是为填充单元格所需的所有信息创建并填充一个数组。我们将在 MenuViewController 中添加一个名为 _cellContentsNSMutableArray

在我们开始着手为 UITableViewCell 设计像素级的替代品之前,让我们花一点时间来确保一切按预期工作,并检查我们所做的更改。

(查看此提交)

继承 UITableViewCell

创建功能完善的 UITableViewCell 子类可能会遇到很多麻烦。我从 2008 年就开始做 iOS 开发了,这个过程中仍有部分让我感到困惑!为了让事情更容易,我们会尽可能重用默认的 UITableViewCell。这意味着我们不会指定新的视图,而只是将已经提供的 textLabelimageView 控件稍作位置调整。

此外,我们需要添加代表每个单元格顶部和底部 1px 线的新 UIViews,还有一个 UIImageView 来显示,“发光”效果,例如你查看时间线中有新推文、新的直接消息等情况。

表格单元格的内容应该是相当容易理解的。这里值得强调的一点是在 -layoutSubviews 方法中重新布局 textLabelimageView。这是在 UITableViewCell 子类中修改这类内容的正确位置,也是唯一一个可以通过编辑从超类提供的视图布局做这件事的位置。

最后,imageView 的确切宽度取决于堆叠视图控制器需要覆盖除单元格图像之外的所有内容。

此外,我们还需要对显示这些单元格的表格控制器进行一些修改

(查看此提交)

此外,这也是我们应用新表格单元格 UI 进展的一个简单查看(图标漂亮,对吧?3

表格部分标题

为了完成导航面板的体验,我们需要在表格中添加另外两个功能

  1. 自定义表格部分标题视图
  2. 水印页脚

我们首先处理部分标题。为了开始,我们需要在 MenuViewController 中实现两个新的 UITableViewDelegate 方法

- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section

- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section

-tableView:heightForHeaderInSection: 的实现很简单:只需返回值 70

-tableView:viewForHeaderInSection: 的实现并非那么简单。我们将创建一个自定义视图。让我们看看我们对其了解什么

  • 固定尺寸为 200×70px。
  • 用户头像图片尺寸为 48×48px,位于视图的原点 (11,11)(即左上角)。
  • 用户头像图片有一个 3px 的圆角。
  • 用户头像图片有阴影效果。
  • 一个文本标签,显示用户账户名称。从设计角度看,它看起来与导航面板中其他文本一致,只是字体大小看起来可能是 labelFontSize

因此,让我们再建一个自定义控件!我们将称之为 MenuHeaderView。这个控件大致按照你的期望运行

请注意,目前阴影和圆角是互斥的。我们先显示阴影,稍后再解决这个问题。

(查看此提交)

水印页脚

为了让导航面板的外观和感觉更加完整,让我们添加水印页脚。这将是一个简单自定义视图。它需要顶部的线条与我们在其他地方使用的相同半透明的白色颜色,然后在下面居中放置我们的徽标。与我们所做的其他一些东西相比,这应该很简单

最后,我们需要将我们的新水印页脚视图钩接到导航栏的表格视图中。只需两行更改,包括 #import

好的,我们离结束已经很近了。让我们再看看用户界面是如何进步的。看起来不错! 4

(查看此提交)

集成时间线

好吧,我们已经步入尾声:我们需要将时间线拉入并渲染到中心的表格中。为了演示目的,我们将避免异步网络操作,而是从项目中一个plist文件加载一些数据5

创建TweetTableViewCell

我们的TweetTableViewCell将是UITableViewCell的另一个子类,并且它将有四个相对直观的属性

  • imageView
  • 作者标签
  • 推文标签
  • 时间戳标签

创建单元格及其内容的布局和我们之前看到的基本相同,只是有一点新变化:由于推文长度可能不同,无法预先预测包含一个推文的单元格的高度。相反,我们必须在运行时调整单元格的高度。

为了完成这项任务,我们还需要给我们的应用添加两个新方法

  • -tableView:heightForRowAtIndexPath:DataViewController,和
  • +heightForTweetWithText:TweetTableViewCell

-tableView:heightForRowAtIndexPath:UITableViewDelegate的一个方法,允许其UITableView确定给定单元格的高度。在我们的情况下,我们通过调用+heightForTweetWithText:来确定的高度,并返回它的值。

+heightForTweetWithText:也是相对直接的,尽管它是硬编码的

我们计算单元格中每个固定元素的大小。然后,我们使用NSString分类方法-sizeWithFont:constrainedToSize:lineBreakMode:根据我们想要的字体确定推文正文的大小。最后,我们将该值截断为整数值(为了避免在屏幕上的非整点渲染字符串时可能出现的难看的模糊文本问题)。

(查看此提交)

瞧,我们现在有不同高度的行!

对单元格的最后一件事情是我们需要让时间戳正常工作。我们将从在TweetTableViewCell中填写timestampLabel属性的细节开始,然后深入核心:从控制器提供相对时间戳到单元格。

为了完成这项任务,我们将转向称为DistanceOfTimeInWordsNSDate类的一个分类。这个分类允许我们将一个NSDate转换为“大约5小时前”这样的字符串。为了在项目中使用这个分类,我们首先需要从GitHub获取相关的文件并将它们添加到项目中。其次,按照这个diff的步骤来查看我们如何将NSDates转换为有意义的相对字符串

以下是短网址6

  • 在头文件中添加一个新实例变量,命名为formatter
  • 初始化formatter,提供它的一个区域设置,并——最重要的是——使用Stack Overflow上可以方便找到的Unicode日期格式化魔法对它的-setDateFormat:方法调用。
  • 使用formatter上的-dateFromString:方法将每个推文的created_at字符串值转换为NSDate对象。
  • -distanceOfTimeInWords的结果分配给timeStampLabel's文本属性。

重要注意事项: NSDateFormatters 创建起来并不便宜,所以如果这是一段生产代码,我可能会修改 -distanceOfTimeInWords 以接受一个日期格式化器作为参数,这样它就不需要在每次通过 -tableView:cellForRowAtIndexPath: 时都创建新的格式化器。

(查看此提交)

总结

嘿,看那:我们搞定了!从我们开始,在外观和感受方面我们已经取得了很大的进步,但如果不利用所有那些优秀且免费可用的组件,我们绝对不可能达到这里。

当然,您可以从我的个人GitHub账户中检索已完成的项目的完整项目(链接)

请告诉我您对此的看法,无论是通过Twitter还是下面的评论。如果感兴趣的人足够多,我很乐意扩展这一系列到第二部分!

1模仿,奉承等。

2我们最终将用2px的etch线替换这个,但那可以稍后再处理。

3坦白讲:我做的图标改变并不漂亮。我是一个软件开发者,并且对用户体验相当痴迷,但不是图形设计师。

4除了水印的图形设计,但我们在那方面已经明确了我的不足。

5尽管值得提一下,创建这个plist文件的方式很酷。出于这个原因,我爱Ruby...

6哈,我有时候真的很痛苦。

7我写过很多无助于SO的Unicode日期格式字符串,这从来都不是我乐在其中的经历。