下载Xcode 6.2 Beta

首先我们需要下载最新的Xcode 6.2 Beta

创建一个新项目

启动Xcode,选择Create a new Xcode project, 然后选择Single View Application创建一个单视图应用。

PIC

PIC

创建Watch App Target

选择菜单中的File -> New -> Target, 然后选择Apple Watch -> Watch App, 点击Next

PIC

在Target的基本属性设置界面中不要勾选Include Notification SceneInclude Glance Scene这两个选项,因为我们是在做一个Hello World, 最后点击Finish完成 Watch App Target的创建。

PIC

添加Label

打开HelloworldWatchKit Watch App目录下的 Interface.storyboard

PIC

PIC

然后将一个Label拖拽到InterfaceController中,在Label中输入文字“Hello,World!”,并将Label的Position属性设置为 Horizontal:Center,Vertical:Center,让Label居中。这里不再需要AutoLayout。

PIC

运行App

首先选择Active Scheme为HelloworldWatchKit Watch App, 然后选择任意型号的iPhone模拟器。

PIC

运行应用,这时你可能还看不到Apple Watch模拟器,别着急,这里还需要一个设置。选择iPhone模拟器,在菜单中选择Hardware -> External Displays -> Apple Watch - 38mm | Apple Watch - 42mm

PIC

然后重新运行应用,你就会看到你的第一个Apple Watch App了!

PIC

整体功能预览

总体而言,WatchKit第一版SDK提供的功能远远超出了我的期望。因为我原本以为第一版WatchKit可能侧重于对iOS Today Extension功能的延伸,而不是真正开发Watch App。所以当知道真相后,我即惊讶又感到高兴,第一版WatchKit并不是我所想的那样,它可以为Watch App创建全新的交互界面,而且可以通过iOS App Extension去控制他们。

pic

所以我们能做的并不只是一个简单的所谓iOS Apple Watch Extension的玩意,而是有很多新的功能需要我们去挖掘。目前提供的比如特定的UI控制方式 、Glance、可自定义的Notification、和Handoff的深度结合、图片缓存等等,作为开发者,这些功能已经让我为之兴奋了。

Apple Watch的架构

WatchKit带给我的惊讶之一是它的架构。目前Watch App的运行是由两部分相互结合进行工作的:

pic

  • 你的Apple Watch主要包含用户界面元素文件(Storyboard文件和静态的图片文件)和处理用户的输入行为。并不会真正在Apple Watch中运行代码。换句话说,Apple Watch仅是一个“视图”容器。
  • 你的iPhone包含所有的逻辑代码用于响应用户在Apple Watch上产生的诸如应用启动、点击按钮、滑动滑杆等行为。换言之,iPhone包含了控制器和模型。

有趣的是Apple Watch和iPhone的这种交互是在幕后自动完成的。你要做的工作只是在Storyboard中设置好UI的Outlet,然后其他的事都由WatchKit SDK在幕后通过蓝牙技术自动进行交互。即使iPhone和Apple Watch是两个独立的设备,你也只需要关注你本地的代码以及Outlet的连接情况,这真是一件很酷的事情。

WatchKit布局

一个令我惊讶的是Watch App的UI布局方式,我们不能再用AutoLayout进行布局了。取而代之的是一种新的布局方式Group,你需要将界面元素比如按钮、Label添加到Group中,然后Group会自动为你添加的界面元素在其内部进行布局。

pic

你可以将一个Group嵌入到另一个Group中,用于实现较为复杂一点的界面布局,并在Group中还可以设置背景色、边距、圆角半径等属性。

这让我想起了Java中的BoxLayout或者XAML中的StackPanel

Glances和Notifications

Watch App中最酷的功能我觉得就是Glances和Notifications了。我个人认为,Apple Watch的应用中最有用的功能之一就是能让用户很方便的(比如一抬手)就能看到自己感兴趣的事物的提醒通知,比如有人在Twitter中提及到了你或者比特币现在的价位等等。

pic

Glances和Notifications能为你带来什么呢?

  • Glances能让你在应用中快速预览信息,有点像iOS8中的Today Extension。
  • Notifications能让你在Apple Watch中接收到各类通知。Apple Watch中的通知分为两种级别。第一种是提示,只显示应用图标和简单的文本信息。当你抬起手腕或者点击屏幕时就会进入到第二种级别,你就可以看到该通知更多详细的信息,甚至有交互按钮。

在Glance和Notification这两种情形下,你都可以点击屏幕进入到对应的Watch App中,并且使用Handoff。你甚至可以将特定的View Controller作为Glance或Notification的内容发送给用户。

我预期在未来几个月我们会在Glance和Notification上面投入较大的时间。

动画

Apple Watch中不支持动画,这多少让人觉得有点奇怪。如果你希望某个UI元素具有动画效果,你必须要生成一堆图片,这些图片的内容是连续动画每一帧的内容,然后循环显示这些图片,感觉又回到了GIF动画时代。所以在Apple Watch中不支持iOS中UIView或CALayer这种动画,只支持帧动画。

举个例子,你们可以看看Apple的Lister example示例,你会发现Apple通过360张图片来实现一个画圆的动画效果。

pic

这显然是一个灾难性的工作量,不过如果需要,也可以借助某些工具去生成一个动画需要的所有图片。不过,因为Apple Watch的容量很悠闲,所以你要谨慎加入帧动画,因为需要大量图片占用内存,除非有画龙点睛的动画,否则还是尽量少使用动画。

设计资源

如果你是一名设计者(或者对设计有浓厚的兴趣爱好),一下两点建议对你有很好的帮助:

  1. 放下你手中所有的事情,阅读Apple提供的针对Apple Watch的Human Interface Guidelines (HIG)。里面有非常不错的指导和参考能帮助你理解Apple Watch UI设计中你有疑惑的地方,因为设计Apple Watch App是一个全新的世界。
  2. 你一定要下载Apple Watch Design Resources。这里有大量关于设计Apple Watch App的PSD素材,比如UI控制模型、颜色、元素间距的指导、笔画粗细的指导等等,有很多有用的东西,给我留下了非常深刻的印象,这里强烈推荐。

原文地址:WatchKit: Initial Impressions

由于Apple近几年在iOS系统的不断改进过程中添加了许多新的特性和功能,这使得iOS系统对文本的渲染能力有了大大的提升。在iOS7中我们就已经能感觉到在文本渲染方面有了很大改进和提升。现在iOS8发布了,在文本渲染方面在延续了之前强大功能的基础上,又提升了其易用性。简单纵观iOS文本渲染的发展史,你也许对目前文本渲染的强大能有更深刻的体会。

在iOS6之前,使用web视图渲染文本是当时最为容易的一种方式,因为它能较为有效的处理混合编排的文本,比如有粗体字、斜体字、有颜色的字等。

2012年,iOS6在UIKit框架中添加一些支持字符串编辑或渲染的控件。这使得在渲染文本时,使用web视图不再是唯一的选择了。并且在文本排版方面不用再依赖通过HTML布局这种方式了。

在iOS6中,UIKit中支持字符串编辑和渲染的控件是同时基于WebKit和Core Graphic的String drawing函数开发出的,整个如下图所示:

pic

注意:在这张图上有没有让你疑惑的地方呢?没错,UITextView是基于WebKit框架的。实际上,UIKit中与文本相关的这些控件在底层还是使用HTML渲染的。没有深入研究过相关框架的开发者是不太容易察觉这一点的。

尽管iOS6中提供的这些文本控件在实际开发中的确带来了不少便利,但是当遇到复杂的布局、多行混合渲染等这种高级应用场景时,这些控件就显得捉襟见肘了,此时虽然Core Text是相对底层而且用法繁复的框架,但使用它来解决问题仍是唯一可以选择的方法。

直到iOS7的问世,这种窘境得以改善。随着扁平化的设计思路,iOS的UI拚弃了沿用多年的拟物化风格,将重点和关注点集中在排版工艺上。比如UIButton在iOS7中去掉了整个外边框和阴影,只留下了按钮文字。所以Apple在iOS7中加入了用于文本编排和渲染的Text Kit框架就不足为奇了。

在iOS7中关于文本渲染的控件及框架结构就比较清晰和合理了。因为所有UIKit中的文本控件都基于Text Kit框架,而不像iOS6中还有基于Web Kit框架的:

pic

Text Kit在继承了Core Text所有强大功能的基础上,将功能封装为面向对象的API,让开发者们都乐开了花。

在这篇教程中,你要去探索Text Kit的各种功能特性,并且你要创建一个简单的但又功能丰富的iPhone笔记应用。

本教程包含一个初始的项目,里面含有事先创建好的UI部分,以便使同学们只关注于Text Kit的部分。在这里可以下载该项目。下载完成后解压并在Xcode中打开项目,编译运行后你会看到如下的界面:

pic

该示例应用创建了一个初始的数组用于存放笔记实例,然后在TableViewController中将其渲染出来,当你点击选择某条笔记时,Storyboard和segue会捕获到你的行为,然后处理视图转换的先关工作,使你看到该条笔记的详细信息。

Dynamic Type

Dynamic Type是iOS7中给我们的开发带来变化最多的特性之一,它的作用是让应用中的字体大小遵循你设置的字体大小和粗细。

在iOS7中,打开设置,可以在 通用/辅助功能(General/Accessibility)通用/字体大小(General/Text Size) 中查看和设置系统中应用显示字体的属性:

pic

在iOS8中,打开设置,可以在 通用/辅助功能/更大字体(General/Accessibility/Larger Text) 查看Dynamic Type的文本尺寸。

不管是增加文字粗细还是改变文字大小,在支持Dynamic Type的应用中这都能给用户带来极大的便利,增加了文字的可读性。

为了让应用支持Dynamic Type,你需要设置文本遵循某一风格,而不是明确的指定文本的字体名称和大小。在iOS7中已经为UIFont增加了一个新的方法preferredFontForTextStyle,它的作用是给创建出一个由用户在设置中根据自身需要设定的风格的字体。

下表中展示了六种不同字体样式的不同大小和粗细程度:

pic

表中最左边的字体是用户可选择的最小的字体,中间是可选择的最大的字体,最右边是选择了辅助功能中给字体加粗后的样式。

最基本的功能支持

实现动态文本的基本功能还是相对较简单的。应用中的字体不再是一个明确的字体,而是需要请求一个特殊的字体样式。在运行时,应用会根据用户在设置应用中对字体的设置以及请求到的字体样式中选择一个合适的字体样式。

到了iOS8,Apple让实现Dynamic Type变得比iOS7更加容易了。尤其是TableView中的默认Label自动支持Dynamic Type,这个很赞!但是如果还想适配iOS7的话那就要在TableView中使用自定义的Label了。所以首先同学们要学会如何在iOS7中处理Dynamic Type,然后你就会发现到了iOS8后,生活是多么美好,晴空万里,没有雾霾!

为什么iOS7是优秀的系统,而iOS8是趋近完美的系统

该教程中初始项目的设置和配置是基于iOS8的。在我们开始学习之前,先编译运行应用,然后尝试改变默认的文本字体大小,多试几次不同的字体大小。你会发现不光是字体大小变了,TableView列表的Cell高度也相应发生了改变。但是你对这个项目还没动过一根手指。同时你也应该发现了点击选择一条笔记后,该笔的详细信息的文本字体却没有发生变化。

但在iOS7中我们的确还要做一些额外的事,没有十全十美的事应该指的就是这个吧。如果你的编译环境是iOS7或iOS8(确保你使用的Xcode版本是6),那么本教程的绝大部分内容都是没问题的。现在我们需要将Xcode的编译环境设置为iOS7已经选择合适的iOS模拟器(iPhone5s)。如果你不打算支持iOS8之前的系统,那么你可以直接使用iOS8的编译环境。

现在咱们在iOS7下编译运行应用,然后重复之前修改字体的操作,你会发现什么?没错,发现了悲剧。应用中的字体会忽略你对字体的设置,根本不起作用。所以,同学们必须要做点什么让Dynamic Type在iOS7跑起来。

打开NoteEditorViewController.swift,在viewDidLoad方法中添加如下代码:


textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)

这里需要注意的是你并没有给textView.font设置具体的字体,比如Helvetica Neue之类。相反,你只是请求了一个适合主体文本的字体样式UIFontTextStyleBody

然后打开NotesListViewController.swift,在tableView(_:cellForRowAtIndexPath:)方法的return语句后添加如下代码:


cell.textLabel?.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)

你依然没有给字体具体的类型,而是请求了一个合适的字体样式。

使用语义接近的字体样式名称,比如UIFontTextStyleSubHeadline,可以避免在代码中对字体样式使用硬编码,并确保你的应用中的文本信息能正确的响应用户的设置。

现在再编译和运行应用,你会发现TableView和笔记详细信息页面中的文本字体都发生了变化。下面的截图是设置不同字体大小后的笔记详细信息页面:

pic

目前看起来一起都很完美,但是细心的读者可能会发现这种解决方法只能解决一半的问题。让我们回到设置应用然后再次更改字体大小,然后我们通过后台程序回到我们的笔记应用中,我们发现了什么?没错,文本字体没有响应我们刚才的设置从而发生变化。

我相信我们的用户是不会允许这种事情发生的。又一个挑战出现了,让我们来看看如何解决这个问题。

即时响应字体设置

打开NoteEditorViewController.swift,在viewDidLoad方法中添加如下代码:


NSNotificationCenter.defaultCenter().addObserver(self,
selector: "preferredContentSizeChanged:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)

上面这段代码的作用是将我们的NodeEditorViewController注册到通知中心里,当字体发生改变时会通知NodeEditorViewController中的preferredContentSizeChanged方法。

我们再来看看preferredContentSizeChanged方法:


func preferredContentSizeChanged(notification: NSNotification) {
textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
}

在这个方法中我们就可以改变文本的字体样式了。

注意:这里你可能会有疑惑,已经更改过字体样式了,为什么这里再次请求样式的时候还是UIFontTextStyleBody呢?当用户更改完字体样式后,你需要通过preferredFontForTextStyle方法重新请求一次字体样式,该方法的参数只代表文本的类型和所在位置,比如是Body里的还是Head中的等,所以UIFont.preferredFontForTextStyle(UIFontTextStyleBody)的意思就是请求类型和位置在Body中显示文本的字体样式,每次请求都是获取最新一次设置的字体样式。

打开NotesListViewController.swift,重写viewDidLoad方法:


override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self,
selector: "preferredContentSizeChanged:",
name: UIContentSizeCategoryDidChangeNotification,
object: nil)
}

同学们应该注意到了,我们刚才添加的方法和NoteEditorViewController.swift中添加的方法一样。没错,给NSNotificationCenter注册的方法是相同的,但是preferredContentSizeChanged方法会有点区别。

然后在NotesListViewController.swift中再添加preferredContentSizeChanged方法:


func preferredContentSizeChanged(notification: NSNotification) {
tableView.reloadData()
}

上面这段代码的作用是让Tableview重新加载可见的Cell,在更新时就会触发preferredFontForTextStyle()方法,将新设置的字体样式应用到Tableview的Cell中。

再次编译运行应用,设置字体样式,然后看看我们的应用有没有正确的响应你的设置。

使Cell高度自适应

响应字体设置这部分到目前为止我们已经处理完了,效果也不错,但是当你把字体设置为一个足够小的,或者最小的字体时,在TableView中看起来就不怎么好看了,因为Cell的高度没有变化,而字体很小,所以看起来很不美观,比如下面左侧的截图一样:

pic

这个问题是iOS7中在Dynamic Type中比较坑爹的一个问题。想要使你的应用在不同字体样式下都显示的比较完美,你必须要让Cell的高度根据字体样式的大小做出相应的调整。虽然Auto Layout能帮你解决大部分布局适配上的问题,但在这个问题上,它就比较无力了,这得需要你自己去解决了。

解决这个问题的原理很简单,就是让TableView的Cell高度根据字体样式的大小进行改变。那么可以通过实现UITextViewDelegatetableView(_:heightForRowAtIndexPath:)方法来解决该问题。

NotesListViewController.swift中加入如下代码:


let label: UILabel = {
let temporaryLabel = UILabel(frame: CGRect(x: 0, y: 0, width: Int.max, height: Int.max))
temporaryLabel.text = "test"
return temporaryLabel
}()

override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
label.font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
label.sizeToFit()
return label.frame.height * 1.7
}

上面的代码中,首先创建了一个UILabel的实例,用于计算TableView中Cell的高度,也就是这个UILabel的高度就是Cell的高度,然后在tableView(_:heightForRowAtIndexPath:)方法中设置该UILabel的字体样式,调用sizeToFit方法使Label的高度适配字体的高度,最后将Label的高度返回,也就相当于将Cell的高度设置为Label的高度了。这里需要注意的是当调用完sizeToFit方法后,Label与它里面字体之间几乎没有上下间隔,所以我们在返回高度的时候乘以一个上下间隔的比例系数。

再次编译和运行你的应用,多更改几次字体样式,你会看到Cell的高度随着字体样式也进行着调整:

pic

给文本添加印刷效果

给文本添加一点高亮和阴影来达到文本像嵌入纸张的那种印刷效果。

打开NotesListViewController.swift用下面的代码替换tableView(_:cellForRowAtIndexPath:)方法:


override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell? {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell

let note = notes[indexPath.row]
let font = UIFont.preferredFontForTextStyle(UIFontTextStyleHeadline)
let textColor = UIColor(red: 0.175, green: 0.458, blue: 0.831, alpha: 1)
let attributes = [
NSForegroundColorAttributeName : textColor,
NSFontAttributeName : font,
NSTextEffectAttributeName : NSTextEffectLetterpressStyle
]
let attributedString = NSAttributedString(string: note.title, attributes: attributes)

cell.textLabel?.attributedText = attributedString

return cell
}

上面的代码给TableView的Cell中的文本添加了颜色和NSTextEffectLetterpressStyle文字样式。

编译运行应用,你会看到更加立体的文本:

pic

这种印刷效果虽然只是一种较为细微的效果,但这并不代表你就能滥用这种效果,它虽然能使文本看起来有立体感,但是有可能会影响到文本的清晰度。

文本绕行排版

一般我们在常用的文本编辑器,比如Word中都能看到文本围绕图片或表格等其他元素的排版格式。TextKit也提供了能让文本按指定的路径排版或按一定形状排版的功能,叫做绕行路径。

下面我们希望在笔记详细页中添加一个圆形的视图,放在右上角,用于显示这篇笔记的创建时间。

当你创建完视图摆好位置后,你会发现这个圆形的视图会遮盖住笔记文本信息,所以我们需要给文本创建一个绕行路径来绕开这个圆形的视图。

添加圆形视图

打开NoteEditorViewController.swift,申明一个属性:


var timeView: TimeIndicatorView!

然后在viewDidLoad方法的最后添加如下代码:


timeView = TimeIndicatorView(date: note.timestamp)
textView.addSubview(timeView)

创建一个timeView的示例,然后将它作为一个子视图添加到textView中。

刚才创建的视图需要合适的尺寸大小,你需要一种机制在ViewController绘制子视图调用updateSize方法来计算尺寸。

所以我们加入如下两个方法:


override func viewDidLayoutSubviews() {
updateTimeIndicatorFrame()
}

func updateTimeIndicatorFrame() {
timeView.updateSize()
timeView.frame = CGRectOffset(timeView.frame, textView.frame.width - timeView.frame.width, 0)
}

这里的viewDidLayoutSubviews方法会调用updateTimeIndicatorFrame方法,该方法会处理两件事,一个是调用updateSize方法设置timeView的尺寸,另一个是设置timeView的位置,使它处于textView的右上角。

之后每次当ViewController接收到文本字体样式更改的时候都会调用updateTimeIndicatorFrame方法,以便将timeView的尺寸和位置调整到合适的状态。所以我们更改一下preferredContentSizeChanged方法:


func preferredContentSizeChanged(notification: NSNotification) {
textView.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
updateTimeIndicatorFrame()
}

现在编译运行应用,点击笔记条目进入详细信息页后你就会在右上角看到一个显示创建时间的圆形视图:

pic

你可以在设置中改变字体样式,显示创建日期的视图会根据字体样式自动调整到合适的大小。

但是你们会发现还有一些美中不足的地方,那就是日期视图把笔记文本信息给遮住了,幸运的是TextKit提供的文本绕行路径能帮我们解决这个问题。

创建绕行路径

打开TimeIndicatorView.swift,看看curvePathWithOrigin方法,日期视图在填充渲染背景色的时候会调用该方法,但是你可以通过该方法来决定文本的绕行路径,该方法会自己计算视图的贝塞尔曲线,也就相当于是文本的绕行路径了。

打开NoteEditorViewController.swift,在updateTimeIndicatorFrame方法末尾中添加如下两行代码:


let exclusionPath = timeView.curvePathWithOrigin(timeView.center)
textView.textContainer.exclusionPaths = [exclusionPath]

上面的代码通过日期视图的curvePathWithOrigin方法基于贝塞尔曲线计算出绕行路径,然后放在数组中赋值给textView.textContainter.exclusionPaths属性。这里要注意的是绕行路径的起点和坐标与文本信息是相对位置。

编译运行应用,现在你就可以看到文本信息都绕开了日期视图:

pic

这个简单的示例只是体现出了绕行路径强大功能的冰山一角,从上面的代码中你可能也注意到了,绕行路径是放在数组中进行赋值的,所以这就意味着每个容器,不管是文本容器还是其他容器都可以被设置多个绕行路径。

除此之外,绕行路径可以随着你的想法变得简单或者复杂,你甚至可以让文本围绕着星星形状或者蝴蝶形状排版。

每当绕行路径发生改变时,文本容器就会通知布局管理器,你可以让绕行路径进行动态的改变,比如从星星形状的路径变成蝴蝶形状的路径,此时文本容器也会动态的改变排版。但是这样做会得不偿失,因为用户在阅读的时候看到文字动来动去,估计会砸了手机!

未完待续……

原文地址:Text Kit Tutorial in Swift

Xcode6.1中Swift的最新版本是1.1,在该版本中引入了一个新的特性:可失败构造器。通过构造器初始化实际上是给classstruct的每一个存储属性(参数)提供初始值,进行对象实例化的过程。在一些情况下,初始化的过程是有可能失败的。比如,实例化一个对象,在实例化的过程中需要访问资源文件,就像从文件中加载图片一样:


NSImage(contentsOfFile: "swift.png")

如果该文件不存在或者因为某种原因不允许访问,那么NSImage的初始化过程就会失败。在Swift 1.1版本中,像这种情况可以通过可失败构造器进行捕获。如果在构造一个对象时使用可失败构造器,那么当对象构造成功时返回该对象,当对象构造失败时则返回nil。因此可以直接用条件判断语句使用可失败构造器来实例化对象:


if let image = NSImage(contentsOfFile: "swift.png") {
// loaded the image successfully
} else {
// could not load the image
}

init初始化方法可以通过在init关键字后面加上?!将其变为可失败初始化方法,这样就表示某对象的初始化方法会产生两种结果。比如,将Int类型的init初始化方法变为可失败初始化方法,然后执行String类型转换:


extension Int {
init?(fromString: String) {
if let i = fromString.toInt() {
// Initialize
self = i
} else {
// return nil, discarding self is implied
return nil
}
}
}

在可失败构造器或可失败初始化方法中,一旦返回nil就代表构造失败或初始化失败,不会再返回其他的值。在上述的示例中,当String不能解析为Integer时会导致初始化失败,返回nil,否则返回解析成功后的值。

可失败构造器/初始化方法解决了以前在Swift中只能通过工厂方法捕获构造或初始化失败情况的问题。比如,一个枚举,通过fromRaw工厂方法通过一个原始值来寻找它所对应的枚举成员,返回类型为可选枚举类型。即如果该原始值对应的枚举成员存在,那么返回该枚举成员,如果不存在则返回nil。现在,Swift编译器结合可失败构造器,通过可失败的初始化方法用switch语句判断原始值对应的枚举成员,如果没有对应的则返回nil


enum Color : Int {
case Red = 0, Green = 1, Blue = 2

// implicitly synthesized
var rawValue: Int { /* returns raw value for current case */ }

// implicitly synthesized
init?(rawValue: Int) {
switch rawValue {
case 0: self = .Red
case 1: self = .Green
case 2: self = .Blue
default: return nil
}
}
}

使用可失败构造器可极大程度的统一Swift中的构造对象语法,消除了构造器与工厂方法之间混乱、重复的冗余语法,使Swift更加简洁。随着可失败构造器这一特性的加入,Swift将对大多数Cocoa中带NSError参数的工厂初始化方法进行调整,从而加强Swift中构造对象语法的统一性,给开发者带来更好的开发体验。

原文地址:Failable Initializers

Size Classes

自适应布局的布局约束自然是好东西,但也不是万能的,有时候我们也需要使用最基本的布局,所以使用size classes将它们两者结合起来才能碰撞出更有激情的火花。

引用我上篇译文中的一段话:
Size Classes并不代表真正的尺寸,而是我们从感官上感觉尺寸的种类,通过这种种类的组合,表示出不同屏幕尺寸设备的横屏及竖屏。

我们在给storyboard中的对象添加约束时可以选择给某一个size class添加约束,所以在你选择特定的size class时,只能看到你添加的只适用于该size class的约束。

这些特性能使我们有效的在不同的设备和屏幕方向中定义、设计不同的用户界面。

Size Classes中的高和宽

Size Classes为高和宽分别提供了三种类型:紧凑型(compact)、普通型(regular)、任意型(any)。用这三种高和宽的类型就可以组合出9种size class,来表示不同的设备屏幕。

在实际运用中,我们发现并不是所有的尺寸都能在Size Classes中找到明确的组合(比如没有组合可以明确表示iPhone6 Plus的竖屏,iPad的横竖屏也不好区分),但是我们可以使用表示这个尺寸范围的组合。比如我们可以用Compact Width | Regular Height来表示iPhone6 Plus的竖屏。

图片

接下来还是用一个活生生的示例来向大家介绍Size Classes。

我们的目标

这个示例是实现Instagram的一个详细信息页面,并让它自适应更大的屏幕尺寸,在竖屏和横屏中都展现出最合适的布局。

我们的目标是让布局自适应iPhone6的屏幕(这里需要注意一下,我所说的自适应是指图片、文字信息的布局排版,对于个人信息的头像不会根据布局的变化而变化),在竖屏中各个信息按照堆栈方式从上到下布局,在横屏时各元素按照大小进行有序排列,而不再是堆栈的方式:

图片

给通用的size class添加约束

在这个示例中,我们不会像上个示例那样使用模拟器的实际尺寸来设计布局,我们使用Size Classes提供的抽象的屏幕尺寸,这里我们先使用通用的尺寸来设计布局,也就是w Any | h Any尺寸。我们要注意的是,在这个尺寸下添加的view或者约束必须是一些公用的,也就是说在任何尺寸,任何屏幕方向的情况下都适用的。

这些约束包括:

图片

除了之前介绍过的页边间距约束、水平间距约束、等高等宽约束、方向比例约束外还有顶部、底部间距约束。

添加完上述的view和约束后,storyboard里应该是这番景象:

图片

这里要注意一下,目前这个界面的布局还有很多关键点需要考虑横屏和竖屏的布局,但是这些工作不会在w Any | h Any尺寸下进行。大伙接着往下看。

为iPhone的竖屏添加布局约束

现在在Size Classes选择器中选择w Compact | h Regular尺寸,这个size class适用于所有iPhone设备的竖屏界面,不论尺寸是多少。

图片

当你在Size Classes选择器中选择某一个size class后,storyboard
中的view controller会实时的反映出你改变后的尺寸大小。

现在你就可以为iPhone的竖屏状态重新设计view的位置和添加新的布局约束了。下图中说明了我在竖屏状态下添加的约束:

图片

我的storyboard中看起来是这样的:

图片

在这个阶段,你可以在3.5、4、4.7、5.5这几个尺寸的模拟器中编译运行应用,在竖屏状态下看看它们的运行情况,尽管在横屏状态表现的还很糟糕。

这里要注意的是我们并没有定义图片的宽度,我们只是给图片添加了相对父容器的左右边间距,并将其值设为了0。所以当屏幕尺寸增大的时候图片的宽度在布局约束的作用下也会增加,这时又因为我们给图片添加了高宽比例约束,所以图片的高度也会随之增加。

图片

为iPhone的横屏添加布局约束

打开Size Classes选择器,选择w Any | h Compact,这个size class适用于任何一个尺寸的横屏状态。我们将在这个size class下设计我们希望在iPhone横屏时显示的用户界面。

此时图片的顶部、左侧、底部都添加了相对于父容器的边界约束。在显示评论的label上添加了右侧边界约束。

当设备横屏时,图片在布局约束的作用下移到了左侧,个人信息label和评论label在约束的作用下被挤到了右侧,并且评论label的高度增加,宽度减少。

图片

现在来看看我的storyboard中显示的内容。大家注意左侧的对象结构树中有很多个约束,但是有些是灰色的。那是因为这些灰色的约束在当前的size class下是禁用的,或者说不适用、不起作用。那些是竖屏时用到的约束。

图片

现在在storyboard中切换size class时布局也会随之变化,更新非常及时和平滑。

图片

我们在模拟器中编译运行应用,切换横竖屏,可以看到布局切换的效果,过度非常自然平滑。

图片

为了能让大家看清楚布局变化过度的细节,我放慢了这个git动画。大家注意,在布局变化时屏幕上的组件有一个层级关系,从该示例中我们可以看到图片view的层级高于个人信息label和评论label。所以我们在设计布局的时候就要考虑如何给view分层,包括在普通布局中不会被覆盖的view。这是一个细节问题。

屏幕预览助手编辑器

Xcode6中另一个值得关注的功能是屏幕预览助手。它可以避免你一遍遍的在不同屏幕尺寸的模拟器中编译运行应用来检查布局的正确与否。你可以在屏幕预览编辑器中添加一个或多个你想查看的屏幕尺寸,并可以让他们呈现横屏或竖屏的状态。

当然它也不是非常完美(因为在预览时导航栏的颜色会丢失),但是切换Double Length Pseudolanguage选项很方便,便于你检查问题。

图片

从上图中大家可以看到我在屏幕预览编辑器中显示了3.5寸屏幕的横屏和4寸屏幕的竖屏,整体布局没有问题,但是当选择Double Length Pseudolanguage使label中的文字都增加一倍时,问题就出现了,个人信息和评论的label长度没有自适应。

为iPad布局添加约束

现在我们将Sizae Classes调整为w Regular | h Regular,这个size class表示了iPad的横屏和竖屏大小。

和往常一样,我们先调整图片和各个label的位置及大小,调整满意后,再添加相关的约束。在iPad布局中我打算将图片的尺寸设置为固定尺寸(不像iPhone中那样随着横屏和竖屏改变图片的尺寸),并将个人信息和评论label紧跟在图片下面。

我的storyboard现在看起来是这样的:

图片

以往,我们都是在特定的size class中添加相关的约束,但在iPad布局中,我们不仅仅只添加一些约束。

因为在iPad布局中有很大的空余空间,所以我们不只是重新排列一下组件的位置和大小,我们还要添加一些其他的组件。在这个示例中,我们再添加两个图片view(分别表示当前显示图片的上一张图片和下一张图片)。

再来看看我的storyboard:

图片

我们不需要对这两个图片手动的设置高宽,而是给它们设置对于主显示图片的相对高宽即可。

这样做的好处是当主显示图片的尺寸更改时,我们不需要自己手动计算和更改这两个图片的尺寸,相对高宽会自动针对主显示图片的尺寸调整这两个图片的尺寸。

图片

我没有制作在iPad上运行的gif动画,但这里有iPad横屏竖屏的运行图片,大家可以看看:

图片

使用布局视图和间距视图

目前Xcode提供的布局约束可以满足大部分的布局需求,但是有些场景下需要变通的使用约束才能达到我们想要的效果。

布局视图示例

通过Xcode提供的约束,我们可以很容易设置组件与view controller view的边缘的间距,也可以很容易的让组件在view controller view中水平居中显示和垂直居中显示。但是却不太容易设置组件与view controller view的水平中线或垂直中线的间距。像这样:

图片

下面向大家介绍两种实现该需求的方法。首先我们先添加一个view,背景色设置为透明,让它在屏幕中垂直居中显示,它作为该需求中组件的父容器,因为父容器在屏幕中是垂直居中的,所以可以给组件添加相对于父容器的Center X约束,就可以达到我们的需求了:

图片

但是通过上述的方式未免会使我们的布局的层级关系太过复杂,我更希望布局比较扁平一些,不需要太多的层级关系。因为上述方法中的容器view主要是用于布局使用,所以这种view我称之为布局视图。

我们来改进一下方法,首先也是在view controller view中添加一个布局视图,我习惯将高度设置为20(这个值随个人喜好或实际情况而定),然后将它和屏幕的顶部、左侧、右侧的间距设为0,这样的话这个布局视图就和状态栏重合了,我们将这个布局视图设置一个深一点的颜色,然后将它的hidden属性设置为true,这样布局视图的颜色就会变浅,也不太会影响我们的布局,而且在应用运行时是不会显示该视图的。现在我们的组件就可以设置相对于布局视图的Center X约束来实现我们的需求了。

图片

间隔视图示例

之前我们学习了如何使用约束设置视图和视图边缘的间距,并且当屏幕尺寸增加时视图也会相应的改变大小,但它们的间距不会改变。

但是如果我们希望当屏幕增加尺寸时,视图的大小保持不变,只是增加间距呢?

图片

从理论上讲,实现该需求可能可以使用类似等宽这样的约束,但是现实往往的是骨干感的。

这时我们就需要间距视图出场了,和布局视图一样,间距视图可以设置一个深一点的颜色,hidden属性要设置为true

我们在每个视图与视图间距之间添加一个间距视图,设置每个视图与相邻间距视图的边缘间距约束,然后给所有的间距视图添加相对于view controller view的等宽约束,设置合适的比例即可。

图片

原文地址:ADAPTIVE LAYOUTS FOR iPHONE 6