iOS 9 Day by Day :: Day 4 :: 第四天 UIStackView简介

本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 4 :: UIStack View》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day4-uistack-view)。感谢Chris Grant的辛苦工作!

苹果从iOS 9开始引入UIStackView为应用程序提供简单的水平或垂直布局的能力。UIStackView使用自动布局管理每个子视图的位置和大小,使得UI更容易适配。

在没有UIStackView时,我们要实现同样的布局,需要使用大量的约束。比如手动设置间距,根据方向确定x/y坐标等。

UIStackView替我们把这些事情都做完了,甚至还为增加、隐藏和移除子视图以及修改布局属性等添加了平滑的过渡动画。

使用UIStackView

现在我们来看一下如何使用UIStackView。最终的代码在GitHub上。下面就一步一步来看一下如何使用UIStackView。这个例子使用UISegmentedControl来控制UIStackView的对齐和分布属性。




我们的目标就是实现上图所示的程序。如图所示,界面上显示了4个好友信息和2个分段控件。这个界面使用自动布局来适配不同屏幕尺寸。令人惊讶的是,我们只需要添加4个布局约束就可以了!

所有东西都被放在UIStackView中。我们一共会使用4个UIStackView。其中只要给第一个设置约束,并且将它放在根视图里。




将一个垂直的栈视图添加到页面后,打开约束面板,然后设置上图一样的四个约束。这样栈视图就会一直处于屏幕中央。

然后将三个或更多的水平栈视图加入刚才的垂直栈视图中。最上面的栈视图包含四个图片,每个图片对应一个好友。由于图片的尺寸各自不同,为了避免出现变形,我们将UIImageView的contentMode设置为Aspect Fit。这样不管图片视图的尺寸是什么样的,图片都会保持原始的长宽比!

大家可能注意到在最终的实现里,每个图片之间有一点小的间隙。这通过设置最上层栈视图的spacing属性即可。在同样的地方还可以设置对齐和分布属性,将这些属性都设置为“Fill”。我们在点击分段控件的时候再做其它修改。

另外还有两个水平的栈视图,每个都放置一个标签(Label)和分段控件(UISegmentedControl)。按下面的样子设置标签和分段控件的对齐和分布属性。
  • Distribution[list][*]Fill
  • Fill Equally
  • Fill Proportionally
  • Equal Spacing
  • Equal Centering




[/*]
[*]Alignment
  • Fill
  • Top
  • Center
  • Bottom




[/*]
[/list]

我们很快就可以看到每个属性的演示了。需要注意的是,这些属性很大程度上依赖UIStackView的contentSize属性。

现在我们已经将UI创建好,接下来就是实现选择不同分段以后的动作。首先,将包含图片视图的UIStackView连接IBOutlet到视图控制器,并命名为peopleStackView。然后为每一个分段控件拖一个IBAction事件到代码中,用来处理分段控件的Value Changed事件。在这些方法中,我们将为peopleStackView设置对齐和分布属性。
@IBAction func alignmentSegmentSelected(sender: UISegmentedControl) {
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.2,
options: .CurveEaseInOut,
animations: { () -> Voie in
if sender.selectedSegmentIndex == 0 {
self.peopleStackView.alignment = .Fill
}
else if sender.selectedSegmentIndex == 1 {
self.peopleStackView.alignment = .Top
}
else if sender.selectedSegmentIndex == 2 {
self.peopleStackView.alignment = .Center
}
else if sender.selectedSegmentIndex == 3 {
self.peopleStackView.alignment = .Bottom
}
},
completion: nil)
}

@IBAction func distributionSegmentSelected(sender: UISegmentedControl) {
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.2,
options: .CurveEaseInOut,
animations: { () -> Void in
if sender.selectedSegmentIndex == 0 {
self.peopleStackView.distribution = .Fill
}
else if sender.selectedSegmentIndex == 1 {
self.peopleStackView.distribution = .FillEqually
}
else if sender.selectedSegmentIndex == 2 {
self.peopleStackView.distribution = .FillProportionally
}
else if sender.selectedSegmentIndex == 3 {
self.peopleStackView.distribution = .EqualSpacing
}
else if sender.selectedSegmentIndex == 4 {
self.peopleStackView.distribution = .EqualCentering
}
})
}

我将每每个改变动作都放在一个动画方法中,但这并不是必须的。只是这样能够看起来更舒服一点。如果将动画删掉,视图变化起来更加直接。下面就可以编译运行了。看看结果吧!

结果视频:





https://www.shinobicontrols.com/wp-content/uploads/2015/08/StackView.mp4?_=1

尝试不同的组合,看看结果是什么样的。我们可以发现,UIStackView自动适配了不同的设备。

将已有视图添加到UIStackView

如果需要将一个现有的布局改用UIStackView,只需要移除这些视图的约束,选中它们并在Interface Builder下方选择UIStackView即可。




Interface Builder自动将这些视图放入一个UIStackView中进行布局。

更多信息

更多关于Xcode 7自动布局的信息,可以观看WWDC session 218(“Mysteries of Auto Layout, Part 1”)。Jason Yao用了15分钟的时间来介绍UIStackView的用法,并且用一个例子来演示如何用更少的约束来快速布局。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

官网:戴维营教育http://www.diveinedu.com
在线视频:戴维营学院http://v.diveinedu.com
问答网:潜心俱乐部http://divein.club
继续阅读 »
本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 4 :: UIStack View》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day4-uistack-view)。感谢Chris Grant的辛苦工作!

苹果从iOS 9开始引入UIStackView为应用程序提供简单的水平或垂直布局的能力。UIStackView使用自动布局管理每个子视图的位置和大小,使得UI更容易适配。

在没有UIStackView时,我们要实现同样的布局,需要使用大量的约束。比如手动设置间距,根据方向确定x/y坐标等。

UIStackView替我们把这些事情都做完了,甚至还为增加、隐藏和移除子视图以及修改布局属性等添加了平滑的过渡动画。

使用UIStackView

现在我们来看一下如何使用UIStackView。最终的代码在GitHub上。下面就一步一步来看一下如何使用UIStackView。这个例子使用UISegmentedControl来控制UIStackView的对齐和分布属性。




我们的目标就是实现上图所示的程序。如图所示,界面上显示了4个好友信息和2个分段控件。这个界面使用自动布局来适配不同屏幕尺寸。令人惊讶的是,我们只需要添加4个布局约束就可以了!

所有东西都被放在UIStackView中。我们一共会使用4个UIStackView。其中只要给第一个设置约束,并且将它放在根视图里。




将一个垂直的栈视图添加到页面后,打开约束面板,然后设置上图一样的四个约束。这样栈视图就会一直处于屏幕中央。

然后将三个或更多的水平栈视图加入刚才的垂直栈视图中。最上面的栈视图包含四个图片,每个图片对应一个好友。由于图片的尺寸各自不同,为了避免出现变形,我们将UIImageView的contentMode设置为Aspect Fit。这样不管图片视图的尺寸是什么样的,图片都会保持原始的长宽比!

大家可能注意到在最终的实现里,每个图片之间有一点小的间隙。这通过设置最上层栈视图的spacing属性即可。在同样的地方还可以设置对齐和分布属性,将这些属性都设置为“Fill”。我们在点击分段控件的时候再做其它修改。

另外还有两个水平的栈视图,每个都放置一个标签(Label)和分段控件(UISegmentedControl)。按下面的样子设置标签和分段控件的对齐和分布属性。
  • Distribution[list][*]Fill
  • Fill Equally
  • Fill Proportionally
  • Equal Spacing
  • Equal Centering




[/*]
[*]Alignment
  • Fill
  • Top
  • Center
  • Bottom




[/*]
[/list]

我们很快就可以看到每个属性的演示了。需要注意的是,这些属性很大程度上依赖UIStackView的contentSize属性。

现在我们已经将UI创建好,接下来就是实现选择不同分段以后的动作。首先,将包含图片视图的UIStackView连接IBOutlet到视图控制器,并命名为peopleStackView。然后为每一个分段控件拖一个IBAction事件到代码中,用来处理分段控件的Value Changed事件。在这些方法中,我们将为peopleStackView设置对齐和分布属性。
@IBAction func alignmentSegmentSelected(sender: UISegmentedControl) {
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.2,
options: .CurveEaseInOut,
animations: { () -> Voie in
if sender.selectedSegmentIndex == 0 {
self.peopleStackView.alignment = .Fill
}
else if sender.selectedSegmentIndex == 1 {
self.peopleStackView.alignment = .Top
}
else if sender.selectedSegmentIndex == 2 {
self.peopleStackView.alignment = .Center
}
else if sender.selectedSegmentIndex == 3 {
self.peopleStackView.alignment = .Bottom
}
},
completion: nil)
}

@IBAction func distributionSegmentSelected(sender: UISegmentedControl) {
UIView.animateWithDuration(1.0,
delay: 0,
usingSpringWithDamping: 0.5,
initialSpringVelocity: 0.2,
options: .CurveEaseInOut,
animations: { () -> Void in
if sender.selectedSegmentIndex == 0 {
self.peopleStackView.distribution = .Fill
}
else if sender.selectedSegmentIndex == 1 {
self.peopleStackView.distribution = .FillEqually
}
else if sender.selectedSegmentIndex == 2 {
self.peopleStackView.distribution = .FillProportionally
}
else if sender.selectedSegmentIndex == 3 {
self.peopleStackView.distribution = .EqualSpacing
}
else if sender.selectedSegmentIndex == 4 {
self.peopleStackView.distribution = .EqualCentering
}
})
}

我将每每个改变动作都放在一个动画方法中,但这并不是必须的。只是这样能够看起来更舒服一点。如果将动画删掉,视图变化起来更加直接。下面就可以编译运行了。看看结果吧!

结果视频:





https://www.shinobicontrols.com/wp-content/uploads/2015/08/StackView.mp4?_=1

尝试不同的组合,看看结果是什么样的。我们可以发现,UIStackView自动适配了不同的设备。

将已有视图添加到UIStackView

如果需要将一个现有的布局改用UIStackView,只需要移除这些视图的约束,选中它们并在Interface Builder下方选择UIStackView即可。




Interface Builder自动将这些视图放入一个UIStackView中进行布局。

更多信息

更多关于Xcode 7自动布局的信息,可以观看WWDC session 218(“Mysteries of Auto Layout, Part 1”)。Jason Yao用了15分钟的时间来介绍UIStackView的用法,并且用一个例子来演示如何用更少的约束来快速布局。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

官网:戴维营教育http://www.diveinedu.com
在线视频:戴维营学院http://v.diveinedu.com
问答网:潜心俱乐部http://divein.club 收起阅读 »

iOS 9 Day by Day :: Day 3 :: Storyboard引用

本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 3 :: Storyboard References》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day3-storyboard-references)。感谢Chris Grant的辛苦工作!

用Storyboard实现过复杂应用就会知道,当页面很多的情况下,它会变得非常大。这会使程序变得很慢并且无法管理。我们可以将界面分散在多个Storyboard中,但是这样需要编写不少代码将它们粘合在一起。

为了解决这个问题,苹果在iOS 9中引入了Storyboard References的概念。它允许不同Storyboard文件之间的页面通过Segue进行连接。这样就可以将程序进行模块化,让单个Storyboard更小更容易维护。这样更加便于理解,同时在团队开发的情况下,很并起来更加轻松。

简化Storyboard

我们通过简化一个现有应用的结构来演示Storyboard References如何工作。原始项目可以在GitHub上下载到。其中我们从OldMain.Storyboard开始。但这个文件实际上并没有被程序所使用,仅供参考。如果想要跟随下面的内容,我们可以将其它Storyboard文件删除,并将OldMain.Storyboard改为Main.Storyboard。

原始Storyboard截图如下:




该项目使用UITabBarController作为初始页面。它包含3个导航控制器,并且每个导航控制器都有自己的根视图控制器。第一个是一个表视图控制器(UITableViewController)实现的联系人列表;第二个为表视图控制器实现的常用联系人列表。这两个都会跳转到同一个显示联系人详情的页面。第三个导航控制器包含更多关于应用程序的信息,比如帐号详情、反馈以及关于。

这个应用程序虽然还不是很复杂,但Storyboard以及很大了。我见过比这大十倍的Storyboard。下面我们来进行分解。从哪里着手呢?很显然,整个程序被标签控制器分为三个部分。

我们从最简单的开始。在Main.Storyboard的右边,我们可以看到它为大家提供了更多关于程序的信息。这些视图控制器都是自包含的,并且没有与应用程序中的其它页面链接到相同的地方。




拖动选中这些视图控制器,使之高亮,然后选择“Editor”菜单->“Refactor to Storyboard”。




为新的Storyboard命名为“More.storyboard”,并且保存。Xcode会自动将“More.storyboard”添加到项目,并打开。




这时再返回“Main.storyboard”,就可以看到标签控制器的第三个页面以及变成了一个Storyboard Reference。




就这样,我们不只是将整个界面的一部分独立到一个Storyboard中,并且允许以后重用。虽然在这个例子里看不出什么特别的用处,但在实际应用中非常方便。

接下来我们将其它区域也分离到单独的Storyboard中。这一步可能会比刚才要复杂一些,因为其中有两个页面链接到同一个位置。我们有两种选择:

将公共视图控制器保留在Main.storyboard。
将公共视图控制器重构到一个单独的Storyboard文件。

这两中选择都可以正常工作。但是我个人习惯将它们分离开来。同样,选择联系人详情页面,然后使用Editor菜单对它进行重构(“Refactor to Storyboard”)。

回到Main.storyboard,并且选择联系人列表的导航控制器和表视图控制器,同样重构到一个单独的Storyboard。继续对常用联系人列表做同样的事情。结果如下:




现在我们成功将Main.storyboard分为5个独立的实体。

Main.storyboard只包含一个标签控制器。
Contacts.storyboard由一个导航控制器和表视图控制器组成,并且进一步链接到ContactDetail.storyboard。
Favorites.storyboard同样包含一个导航控制器和表视图控制器,并且链接到ContactDetail.storyboard。
ContactDetail.storyboard显示单个视图控制器,并且能够被联系人列表和常用联系人列表所访问。
More.storyboard包含一个视图控制器对象来显示程序的信息。

这样重构以后,整个程序就被划分为多个模块,能够简化我们以后的开发。

从Storyboard Reference打开指定的视图控制器

上面已经演示了如何从一个已有的Storyboard对Segue展示的内容重构为单独的Storyboard。但是还没有将如何手动创建Storyboard Reference。

假设我们想在联系人列表的右上角增加一个UIBarButtonItem来显示更多关于帐号的信息,而不需要返回设置页面。

打开Contacts.Storyboard并且将一个UIBarButtonItem拖到导航条上,并且将标题改为“Account”。然后从控件栏中拖一个“Storyboard Reference”对象到Storyboard上,并打开属性检查查看。

在属性栏选择“More” Storyboard,然后在“ReferencedID”中输入“accountViewController”。这样就成功引用了帐号详情页面。




按住“Control + 单击“,从联系人列表上拖动连线到新创建的Storyboard Reference上。




整个项目的结果在GitHub上下载。

更多信息

更多关于Xcode 7中Storyboard的用法,建议观看WWDC session 215:”What's New in Storyboards”。刚开始的20分钟将的就是Storyboard Reference。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

官网:戴维营教育http://www.diveinedu.com
在线视频:戴维营学院http://v.diveinedu.com
问答网:潜心俱乐部http://divein.club
继续阅读 »
本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 3 :: Storyboard References》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day3-storyboard-references)。感谢Chris Grant的辛苦工作!

用Storyboard实现过复杂应用就会知道,当页面很多的情况下,它会变得非常大。这会使程序变得很慢并且无法管理。我们可以将界面分散在多个Storyboard中,但是这样需要编写不少代码将它们粘合在一起。

为了解决这个问题,苹果在iOS 9中引入了Storyboard References的概念。它允许不同Storyboard文件之间的页面通过Segue进行连接。这样就可以将程序进行模块化,让单个Storyboard更小更容易维护。这样更加便于理解,同时在团队开发的情况下,很并起来更加轻松。

简化Storyboard

我们通过简化一个现有应用的结构来演示Storyboard References如何工作。原始项目可以在GitHub上下载到。其中我们从OldMain.Storyboard开始。但这个文件实际上并没有被程序所使用,仅供参考。如果想要跟随下面的内容,我们可以将其它Storyboard文件删除,并将OldMain.Storyboard改为Main.Storyboard。

原始Storyboard截图如下:




该项目使用UITabBarController作为初始页面。它包含3个导航控制器,并且每个导航控制器都有自己的根视图控制器。第一个是一个表视图控制器(UITableViewController)实现的联系人列表;第二个为表视图控制器实现的常用联系人列表。这两个都会跳转到同一个显示联系人详情的页面。第三个导航控制器包含更多关于应用程序的信息,比如帐号详情、反馈以及关于。

这个应用程序虽然还不是很复杂,但Storyboard以及很大了。我见过比这大十倍的Storyboard。下面我们来进行分解。从哪里着手呢?很显然,整个程序被标签控制器分为三个部分。

我们从最简单的开始。在Main.Storyboard的右边,我们可以看到它为大家提供了更多关于程序的信息。这些视图控制器都是自包含的,并且没有与应用程序中的其它页面链接到相同的地方。




拖动选中这些视图控制器,使之高亮,然后选择“Editor”菜单->“Refactor to Storyboard”。




为新的Storyboard命名为“More.storyboard”,并且保存。Xcode会自动将“More.storyboard”添加到项目,并打开。




这时再返回“Main.storyboard”,就可以看到标签控制器的第三个页面以及变成了一个Storyboard Reference。




就这样,我们不只是将整个界面的一部分独立到一个Storyboard中,并且允许以后重用。虽然在这个例子里看不出什么特别的用处,但在实际应用中非常方便。

接下来我们将其它区域也分离到单独的Storyboard中。这一步可能会比刚才要复杂一些,因为其中有两个页面链接到同一个位置。我们有两种选择:

将公共视图控制器保留在Main.storyboard。
将公共视图控制器重构到一个单独的Storyboard文件。

这两中选择都可以正常工作。但是我个人习惯将它们分离开来。同样,选择联系人详情页面,然后使用Editor菜单对它进行重构(“Refactor to Storyboard”)。

回到Main.storyboard,并且选择联系人列表的导航控制器和表视图控制器,同样重构到一个单独的Storyboard。继续对常用联系人列表做同样的事情。结果如下:




现在我们成功将Main.storyboard分为5个独立的实体。

Main.storyboard只包含一个标签控制器。
Contacts.storyboard由一个导航控制器和表视图控制器组成,并且进一步链接到ContactDetail.storyboard。
Favorites.storyboard同样包含一个导航控制器和表视图控制器,并且链接到ContactDetail.storyboard。
ContactDetail.storyboard显示单个视图控制器,并且能够被联系人列表和常用联系人列表所访问。
More.storyboard包含一个视图控制器对象来显示程序的信息。

这样重构以后,整个程序就被划分为多个模块,能够简化我们以后的开发。

从Storyboard Reference打开指定的视图控制器

上面已经演示了如何从一个已有的Storyboard对Segue展示的内容重构为单独的Storyboard。但是还没有将如何手动创建Storyboard Reference。

假设我们想在联系人列表的右上角增加一个UIBarButtonItem来显示更多关于帐号的信息,而不需要返回设置页面。

打开Contacts.Storyboard并且将一个UIBarButtonItem拖到导航条上,并且将标题改为“Account”。然后从控件栏中拖一个“Storyboard Reference”对象到Storyboard上,并打开属性检查查看。

在属性栏选择“More” Storyboard,然后在“ReferencedID”中输入“accountViewController”。这样就成功引用了帐号详情页面。




按住“Control + 单击“,从联系人列表上拖动连线到新创建的Storyboard Reference上。




整个项目的结果在GitHub上下载。

更多信息

更多关于Xcode 7中Storyboard的用法,建议观看WWDC session 215:”What's New in Storyboards”。刚开始的20分钟将的就是Storyboard Reference。

戴维营教育

戴维营教育(Dive In Education),潜心做IT职业教育!紧跟时代潮流,不弄虚作假!不忘初心!

官网:戴维营教育http://www.diveinedu.com
在线视频:戴维营学院http://v.diveinedu.com
问答网:潜心俱乐部http://divein.club 收起阅读 »

iOS 9 Day by Day :: Day 2 :: UI测试

UI测试

本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 2 :: UI Testing》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day1-search-apis)。感谢Chris Grant的辛苦工作!

自动化用户界面测试在开发应用程序的时候非常有用。它可以快速检测程序中的问题。iOS系统使用JavaScript编写的UIAutomation样例进行测试。整个过程涉及启动程序、Instruments以及创建和运行脚本。这个工作流程需要花一点时间才能适应。

UI测试

在Xcode 7中,苹果引入了一种新的方法来管理应用程序的UI测试。它允许我们查找和操作UI元素,并且检查它们的属性和状态。UI测试已经被完全整合到Xcode 7的测试报告中,并且与单元测试一起运行。Xcode 5整合的XCTest框架,在Xcode 7的时候被更新成支持UI测试。它支持在检查某个UI状态后执行断言。

辅助功能

为了进行UI测试,这个框架必须能够访问应用程序UI中的各个元素。这样它才能操作这些控件。我们可以定义手指头点击、滑动的位置。但这在不同尺寸的设备上没法正常工作。

这时辅助功能(无障碍环境)就有用了。辅助功能是苹果专门为残疾人与应用进行交互而维护的一个框架。它给UI界面提供了丰富的语义数据,以便残疾人通过这些辅助功能操作应用程序。iOS提供了越来越多的这种功能。虽然它们可能只能在某个应用里使用,但我们还是应该去改进这些API所使用的数据。在很多场景下这都是必要的,比如自定义控件的辅助功能没有自动识别出API的功能。

UI测试有能力通过辅助功能操作我们的应用,并且能够自动适应不同设备尺寸。当我们对界面进行重新布局时,不需要重写测试样例。辅助功能还能让残疾人更方便的使用我们的程序。

UI录制

一旦设置好可以访问的UI后,我们就可创建测试样例了。如果界面比较复杂的话,编写UI测试样例是一个费事、无聊的事情。不过在Xcode 7里,苹果引入了UI录制,它允许我们创建新测试、扩展已有测试。启动UI录制后,只要在设备或模拟器里与程序进行交互,就会自动生成测试代码。总体了解了UI测试后,下面实际动手试试。

创建UI测试样例

下面的例子里,我们使用最新的测试工具来创建测试样例。最终的示例代码可以在GitHub上找到。

设置

在Xcode 7中创建项目时,我们可以选择包含UI测试(include UI Tests)。它会自动帮我们创建一个UI测试目标,并设置好所有需要的内容。
 


这个示例非常简单,但包含了演示Xcode 7的UI测试功能所需要的内容。

这是一个菜单页面,它包含一个开关和按钮,其中按钮链接到详情页面。当开关处于关闭状态时,按钮被禁用。详情页面包含一个简单的按钮,点击后增加标签上显示的值。

使用UI录制

一旦设置好UI以及对应的功能。我们就可以开始编写UI测试样例来确保界面上的任何修改都不会影响程序的功能。

XCTest UI测试接口

在开始录制动作之前,需要确定判断条件。XCTest框架判断UI上元素的功能被分为下面三组API:

XCUIApplication:所测试应用的代理,允许启动应用程序作为测试目标。它总是启动一个新进程。这样做消耗的时间稍微多一点,但是能够保证测试环境不被干扰,处理的变量更少。
XCUIElement:程序中UI控件的代理。所有元素都拥有自己的类型和标识,用来查找程序中的任何一个元素。程序中的所有元素都被嵌套成一个树形结构。
XCUIElementQuery:用于查询元素。每个XCUIElement都有一个查询支撑。这类查询会遍历整个元素树,然后精确的匹配一个元素。如果没有匹配上,则测试失败。当我们检查一个元素是否在这棵树上时,有一个例外就是exists属性。这个属性用作判断的时候非常有用。我们可以使用更加通用的XCUIElementQuery进行查询。它会返回一个结果集。

了解了API后就可以开始写测试样例了。

测试1-确保开关关闭时不会发生导航

首先我们订一个一个函数用来包含测试代码。
func testTapViewDetailWhenSwitchIsOffDoesNothing() {

}

 
然后将光标移动到花括号中间,并且点击录制Xcode下方的按钮。
 


应用程序开始启动,点击开关将它关闭,然后点击“View Detail”按钮。在刚才的testTapViewDtailWhenSwitchIsOffDoesNothing会出现一下代码:
let app = XCUIApplication() 
app.switches["View Detail Enabled Switch"].tap()
app.buttons["View Detail".tap()

 
再次点击录制按钮,停止录制。我们可以看到,应用程序并没有显示详情页面,但是测试样例并不知道这件事。我们必须加入判断语句,是否有内容发生改变。这可以通过检测导航条上的标题做到。这样做虽然不能满足所有条件,但都与目前的情况来说已经足够了。
XCTAssertEqual(app.navigationBars.element.identifier, "Menu")

 
重新运行测试,它还是会通过。我们将“Menu”字符串改为“Detail”,这时测试就会失败了。给测试样例添加一些注释:
func testTapViewDetailWhenSwitchIsOffDoesNothing() { 
let app = XCUIApplication()

//关闭开关
app.switches["View Detail Enabled Switch"].tap()

//点击查看详情按钮
app.buttons["View Detail"].tap()

//验证是否还停留在菜单页
XCTAssertEqual(app.navigaionBars.element.identifier, "Menu")
}

 
测试2-确保开关打开时导航条发生改变

第二个测试与第一个非常类似,因此我们不再详细介绍。唯一不同的地方是,这里的开关是打开的,因此应用程序应该会加载详情页面,并且XCTAssertEqual会确认这一点。
func testTapViewDetailWhenSwitchIsOnNavigatesToDetailViewController() { 
let app = XCUIApplication()

//点击详情按钮
app.buttons["View Detail"].tap()

//验证导航条标识
XCTAssertEqual(app.navigationBars.element.identifier, "Detail")
}

 
测试3-确保递增按钮正常工作

在这个测试中,我们确保用户点击递增按钮后,标签上的值会加1。测试代码的前两行非常类似,因此直接将它从上面复制下来就可以了。
let app = XCUIApplication() 

//点击详情按钮,打开页面
app.buttons["View Detail"].tap()

 
下一步,获取按钮。需要点击这个按钮多次,因此先将它存储在一个变量中。我们还是可以进行录制。代码如下:
app.buttons["Increment Value"].tap()

 
停止录制,并且将代码改为:
let incrementButton = app.buttons["Increment Value"]

 
这样我们就不再需要手动写代码查找按钮。下面是查找标签的代码:
let valueLabel = app.staticTexts["Number Value Label"]

 
到目前为止,我们已经获取了所有感兴趣的控件。在这个测试里,我们将验证10次点击后,标签上的值是否正确。还是可以使用录制功能,点击十次按钮。不过之前已经将元素全部保存,因此只需要简单的输入一个循环就可以。
for index in 0...10 { 
//点击按钮
incrementButton.tap()

//确保每次增1
XCTAssertEqual(valueLabel.value as! String, "\(index+1)")
}

 
这是整个测试包里的三个而已,但是给我们提供了非常好的切入点,并且很容易在这个上面进行扩充。为什么不尝试自己增加一些测试样例来验证当开关打开时按钮可以点击,关闭时不能点击?

录制出错

录制的时候,可能会发现当我们点击按钮后,并没有产生代码。这是因为交互的元素并不支持辅助功能。我们需要使用Xcode的“Accessibility Inspector”进行检查。

一旦辅助功能检查器被打开,按“Command+F7”,然后用鼠标覆盖一个元素。这时我们会看到光标下元素的详细信息。这会提示我们辅助功能到底能否找到该元素。

测试失败

如果测试失败,并且我们不知道为什么。这里有一些方法可以帮助我们。首先,访问Xcode的测试报告。

当我们打开这个视图,并且鼠标悬停在某个步骤时就会看到测试动作的右边有一个小眼睛一样的图标。如果点击这个眼睛,就可以看到该测试的详情信息。这样就可以可视化的检查UI状态,并且找出具体是哪里有问题。

与单元测试类似,我们可以给UI测试设置断点。这样就可以对行为进行调试,并且找出问题。当测试失败时,可以查看视图结构以及检查辅助功能的属性。

为什么需要UI测试

自动化UI测试能够极大的保障程序质量。在Xcode中进行测试非常简单,并且给程序增加辅助功能不只是帮助我们进行测试,还能提高对残疾人的友好度。

Xcode中的UI测试能够直接在持续集成服务器上运行。当测试失败时,我们可以通过Xcode机器人或者命令行方式立即获取信息。

扩展阅读

关于Xcode UI测试的更多信息,建议观看WWDC session 406:“UI Testing in Xcode”。或者查看文档:“Testing in Xcode Document”和“Accessibility for Developers Documentation”。

翻译配套信息

视频:戴维营学院(http://v.diveinedu.com) 
问答:潜心俱乐部(http://divein.club
继续阅读 »
UI测试

本文翻译自Chris Grant的《iOS9 Day-by-Day :: Day 2 :: UI Testing》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day1-search-apis)。感谢Chris Grant的辛苦工作!

自动化用户界面测试在开发应用程序的时候非常有用。它可以快速检测程序中的问题。iOS系统使用JavaScript编写的UIAutomation样例进行测试。整个过程涉及启动程序、Instruments以及创建和运行脚本。这个工作流程需要花一点时间才能适应。

UI测试

在Xcode 7中,苹果引入了一种新的方法来管理应用程序的UI测试。它允许我们查找和操作UI元素,并且检查它们的属性和状态。UI测试已经被完全整合到Xcode 7的测试报告中,并且与单元测试一起运行。Xcode 5整合的XCTest框架,在Xcode 7的时候被更新成支持UI测试。它支持在检查某个UI状态后执行断言。

辅助功能

为了进行UI测试,这个框架必须能够访问应用程序UI中的各个元素。这样它才能操作这些控件。我们可以定义手指头点击、滑动的位置。但这在不同尺寸的设备上没法正常工作。

这时辅助功能(无障碍环境)就有用了。辅助功能是苹果专门为残疾人与应用进行交互而维护的一个框架。它给UI界面提供了丰富的语义数据,以便残疾人通过这些辅助功能操作应用程序。iOS提供了越来越多的这种功能。虽然它们可能只能在某个应用里使用,但我们还是应该去改进这些API所使用的数据。在很多场景下这都是必要的,比如自定义控件的辅助功能没有自动识别出API的功能。

UI测试有能力通过辅助功能操作我们的应用,并且能够自动适应不同设备尺寸。当我们对界面进行重新布局时,不需要重写测试样例。辅助功能还能让残疾人更方便的使用我们的程序。

UI录制

一旦设置好可以访问的UI后,我们就可创建测试样例了。如果界面比较复杂的话,编写UI测试样例是一个费事、无聊的事情。不过在Xcode 7里,苹果引入了UI录制,它允许我们创建新测试、扩展已有测试。启动UI录制后,只要在设备或模拟器里与程序进行交互,就会自动生成测试代码。总体了解了UI测试后,下面实际动手试试。

创建UI测试样例

下面的例子里,我们使用最新的测试工具来创建测试样例。最终的示例代码可以在GitHub上找到。

设置

在Xcode 7中创建项目时,我们可以选择包含UI测试(include UI Tests)。它会自动帮我们创建一个UI测试目标,并设置好所有需要的内容。
 


这个示例非常简单,但包含了演示Xcode 7的UI测试功能所需要的内容。

这是一个菜单页面,它包含一个开关和按钮,其中按钮链接到详情页面。当开关处于关闭状态时,按钮被禁用。详情页面包含一个简单的按钮,点击后增加标签上显示的值。

使用UI录制

一旦设置好UI以及对应的功能。我们就可以开始编写UI测试样例来确保界面上的任何修改都不会影响程序的功能。

XCTest UI测试接口

在开始录制动作之前,需要确定判断条件。XCTest框架判断UI上元素的功能被分为下面三组API:

XCUIApplication:所测试应用的代理,允许启动应用程序作为测试目标。它总是启动一个新进程。这样做消耗的时间稍微多一点,但是能够保证测试环境不被干扰,处理的变量更少。
XCUIElement:程序中UI控件的代理。所有元素都拥有自己的类型和标识,用来查找程序中的任何一个元素。程序中的所有元素都被嵌套成一个树形结构。
XCUIElementQuery:用于查询元素。每个XCUIElement都有一个查询支撑。这类查询会遍历整个元素树,然后精确的匹配一个元素。如果没有匹配上,则测试失败。当我们检查一个元素是否在这棵树上时,有一个例外就是exists属性。这个属性用作判断的时候非常有用。我们可以使用更加通用的XCUIElementQuery进行查询。它会返回一个结果集。

了解了API后就可以开始写测试样例了。

测试1-确保开关关闭时不会发生导航

首先我们订一个一个函数用来包含测试代码。
func testTapViewDetailWhenSwitchIsOffDoesNothing() {

}

 
然后将光标移动到花括号中间,并且点击录制Xcode下方的按钮。
 


应用程序开始启动,点击开关将它关闭,然后点击“View Detail”按钮。在刚才的testTapViewDtailWhenSwitchIsOffDoesNothing会出现一下代码:
let app = XCUIApplication() 
app.switches["View Detail Enabled Switch"].tap()
app.buttons["View Detail".tap()

 
再次点击录制按钮,停止录制。我们可以看到,应用程序并没有显示详情页面,但是测试样例并不知道这件事。我们必须加入判断语句,是否有内容发生改变。这可以通过检测导航条上的标题做到。这样做虽然不能满足所有条件,但都与目前的情况来说已经足够了。
XCTAssertEqual(app.navigationBars.element.identifier, "Menu")

 
重新运行测试,它还是会通过。我们将“Menu”字符串改为“Detail”,这时测试就会失败了。给测试样例添加一些注释:
func testTapViewDetailWhenSwitchIsOffDoesNothing() { 
let app = XCUIApplication()

//关闭开关
app.switches["View Detail Enabled Switch"].tap()

//点击查看详情按钮
app.buttons["View Detail"].tap()

//验证是否还停留在菜单页
XCTAssertEqual(app.navigaionBars.element.identifier, "Menu")
}

 
测试2-确保开关打开时导航条发生改变

第二个测试与第一个非常类似,因此我们不再详细介绍。唯一不同的地方是,这里的开关是打开的,因此应用程序应该会加载详情页面,并且XCTAssertEqual会确认这一点。
func testTapViewDetailWhenSwitchIsOnNavigatesToDetailViewController() { 
let app = XCUIApplication()

//点击详情按钮
app.buttons["View Detail"].tap()

//验证导航条标识
XCTAssertEqual(app.navigationBars.element.identifier, "Detail")
}

 
测试3-确保递增按钮正常工作

在这个测试中,我们确保用户点击递增按钮后,标签上的值会加1。测试代码的前两行非常类似,因此直接将它从上面复制下来就可以了。
let app = XCUIApplication() 

//点击详情按钮,打开页面
app.buttons["View Detail"].tap()

 
下一步,获取按钮。需要点击这个按钮多次,因此先将它存储在一个变量中。我们还是可以进行录制。代码如下:
app.buttons["Increment Value"].tap()

 
停止录制,并且将代码改为:
let incrementButton = app.buttons["Increment Value"]

 
这样我们就不再需要手动写代码查找按钮。下面是查找标签的代码:
let valueLabel = app.staticTexts["Number Value Label"]

 
到目前为止,我们已经获取了所有感兴趣的控件。在这个测试里,我们将验证10次点击后,标签上的值是否正确。还是可以使用录制功能,点击十次按钮。不过之前已经将元素全部保存,因此只需要简单的输入一个循环就可以。
for index in 0...10 { 
//点击按钮
incrementButton.tap()

//确保每次增1
XCTAssertEqual(valueLabel.value as! String, "\(index+1)")
}

 
这是整个测试包里的三个而已,但是给我们提供了非常好的切入点,并且很容易在这个上面进行扩充。为什么不尝试自己增加一些测试样例来验证当开关打开时按钮可以点击,关闭时不能点击?

录制出错

录制的时候,可能会发现当我们点击按钮后,并没有产生代码。这是因为交互的元素并不支持辅助功能。我们需要使用Xcode的“Accessibility Inspector”进行检查。

一旦辅助功能检查器被打开,按“Command+F7”,然后用鼠标覆盖一个元素。这时我们会看到光标下元素的详细信息。这会提示我们辅助功能到底能否找到该元素。

测试失败

如果测试失败,并且我们不知道为什么。这里有一些方法可以帮助我们。首先,访问Xcode的测试报告。

当我们打开这个视图,并且鼠标悬停在某个步骤时就会看到测试动作的右边有一个小眼睛一样的图标。如果点击这个眼睛,就可以看到该测试的详情信息。这样就可以可视化的检查UI状态,并且找出具体是哪里有问题。

与单元测试类似,我们可以给UI测试设置断点。这样就可以对行为进行调试,并且找出问题。当测试失败时,可以查看视图结构以及检查辅助功能的属性。

为什么需要UI测试

自动化UI测试能够极大的保障程序质量。在Xcode中进行测试非常简单,并且给程序增加辅助功能不只是帮助我们进行测试,还能提高对残疾人的友好度。

Xcode中的UI测试能够直接在持续集成服务器上运行。当测试失败时,我们可以通过Xcode机器人或者命令行方式立即获取信息。

扩展阅读

关于Xcode UI测试的更多信息,建议观看WWDC session 406:“UI Testing in Xcode”。或者查看文档:“Testing in Xcode Document”和“Accessibility for Developers Documentation”。

翻译配套信息

视频:戴维营学院(http://v.diveinedu.com) 
问答:潜心俱乐部(http://divein.club收起阅读 »

iOS 9 Day by Day :: Day 1 :: 搜索API

本文翻译自Chris Grant的《iOS 9 Day by Day :: Day 1 :: Search APIs》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day1-search-apis)。感谢Chris Grant的辛苦工作!

iOS 9之前,我们只能在Spotlight中根据名字搜索应用程序。苹果在iOS 9上引入了搜索API,允许开发者在应用内指定可以被Spotlight索引和呈现的内容。

三个API
NSUSerActivity

iOS 8引入NSUserActivity API是为了Handoff,而iOS 9允许我们对活动进行搜索。给这些活动设置元数据(Metadata)后,Spotlight就可以对它们进行索引。所有的活动会跟浏览网页一样形成一个历史记录栈。用户可以在Sptlight里快速打开已有的活动。

Web Markup

Web Markup允许应用程序将它们的内容映射到一个网站,从而在Spotlight里进行索引。用户不需要在设备上安装Spotlight里出现的应用。苹果的索引器能够抓取我们的网站来获取所需的信息,抓取结果通过Safari和Spotlight提供给用户。

在没有安装应用的设备上进行展示是一个非常重要的特征,这能增加我们的应用在潜在用户前的曝光度。应用程序暴露给搜索API的深度链接被存储在苹果的云索引服务器上。更多关于Web Markup的信息请查看《《Use Web Markup to Make App Content Searchable》》。

CoreSpotlight

CoreSpotlight是一个新的iOS 9框架,它允许我们在应用程序里对任何内容进行索引。NSUserActivity也可以用于保存用户历史。本质上它给我们提供了访问CoreSpotlight索引的能力。

使用Core Spotlight API

NSUserActivity与Web Markup API用起来要比CoreSpotlight要简单。下面我们通过一个简单的应用来说明Core Spotlight API如何工作。这个程序会显示一个好友列表,当点击名字可以看到一个照片。可以在GitHub上找到示例代码。



应用程序使用Storyboard创建界面,其中包含一个FriendTableViewController显示好友列表,而FriendViewController显示好友详情。
 


好友信息保存在名为Datasource的类中。我们在这个类里创建保存好友信息的数据模型,并且包含操作Core Spotlight索引的代码。

首先,我们重写Datasource类中的init()方法,在里面创建一个Person对象的数组。这些数据可以存储在数据库或者后台服务器,这里只是为了演示,所以只是使用一些非常简单的测试数据。
 
override init() {
let becky = Person()
becky.name = "Becky"
becky.id = "1"
becky.image = UIImage(named: "becky")!

...

people = [becky, ben, jane, pete, ray, tom]
}

 
一旦数据被存放在people数组里,Datasource就以及可以使用了。

FriendTableViewController在显示好友列表前要先创建一个Datasource对象。
 
let datasource = Datasource()

 
函数中显示好友名称:
 
let person = datasource.people[indexPath.row]
cell?.textLabel?.text = person.name

 
保存people入口到Core Spotlight

下面我们使用iOS 9中新的API来将数据保存到Core Spotlight中。回到Datasource类,定义一个名为savePeopleToIndex的函数。FriendTableViewController在视图被加后可以调用该方法。

在savePeopleToIndex方法中遍历people数组中的每个人,并且创建对应的CSSearchableItem,然后将它们存储在一个临时数组searchableItems中。
 
let attributeSet = CSSearchableItemAttributeSet(itemContentType: "image" as String)
attributeSet.title = person.name
attributeSet.contentDescription = "This is an entry all about the interesting person called (person.name)"
attributeSet.tumbnailData = UIImagePNGRepresentation(person.image)
let item = CSSearchableitem(uniqueIdentifier: person.id, domainIdentifier: "com.ios9daybyday.SearchAPIs.people", attributeSet: attributeSet)
searchableItems.append(item)

 
最后一步是在默认的CSSearchableIndex上调用indexSearchableItems。这一步实际上是将数据保存到CoreSpotlight,这样用户就可以在Spotlight的搜索结果中看到它们了。



响应用户选择

当用户看到Spotlight中的搜索结果后,有可能会点击它们。但是这样做的结果只是打开了应用的主界面。如果希望在点击了一个朋友后,显示它的详细情况,我们还需要做更多的事情。首先需要定制AppDelegate中通过continueUserActivity打开应用后执行的UIApplicationDelegae的方法。

下面是整个方法的实现:
 
func appliction(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
//查找用户ID
let friendID = userActivity.userInfo?["kCSSearchableItemActivityIdentifier"] as! String

//查找根视图控制器并且显示用户信息
let navigationController = (window?.rootViewController as! UINavigationController)
navigationController.popToRootViewControllerAnimated(false)
let friendTableViewController = navigationController.viewControllers.first as! FriendTableViewController
friendTableViewController.showFriend(friendID)

return true
}

 
我们看到之前通过indexSearchableItems函数存放在CoreSpotlight索引里的数据,可以从userActivity.userInfo字典中获取到。我们感兴趣的数据是每个好友的ID,它被作为kCSSearchableItemActivityIdentifier存储在索引中。

一旦我们将userInfo中的数据取出来,就可以去获取应用程序的导航控制器,并且跳转到根视图,然后通过friendTableViewController里的showFriend方法显示好友详情。结果如下:
 


在屏幕的左上角有一个“返回搜索”的选项,点击可以返回搜索界面。

示例总结

在上面的示例代码里,我们可以看到如何将应用程序的数据整合到CoreSpotlight索引。然后Spotlight如何将我们引导到程序中对应的页面。

我们还没有涉及到如何将数据从索引中移除,但总是保持索引中的数据为最新的很重要。如果需要从CoreSpotlight中移除老的数据,请查看deleteSearchableItemsWithIdentifiers、deleteSearchableItemsWithDomainIdentifiers和deleteAllSearchableItemsWithCompletionHandler方法。

好公民的重要性

将尽可能多的内容索引到Spotlight和Safari看起来很不错,但这么做之前一定要记得三思。成为一个对iOS生态系统有益的好公民不止能够让你的客户体验更好,而且会引起苹果的注意。它们做了许多事情去跟踪、分析哪些垃圾制造者,并将它们放到最后面。

更多信息

关于搜索API的更多信息,建议观看WWDC的Session 709:《Introducing Search APIs》。也可以阅读《NSUserActivity Class Reference》和《CoreSpotlight文档》。本文的示例代码可以在Github获取到。
继续阅读 »
本文翻译自Chris Grant的《iOS 9 Day by Day :: Day 1 :: Search APIs》(https://www.shinobicontrols.com/blog/ios9-day-by-day-day1-search-apis)。感谢Chris Grant的辛苦工作!

iOS 9之前,我们只能在Spotlight中根据名字搜索应用程序。苹果在iOS 9上引入了搜索API,允许开发者在应用内指定可以被Spotlight索引和呈现的内容。

三个API
NSUSerActivity

iOS 8引入NSUserActivity API是为了Handoff,而iOS 9允许我们对活动进行搜索。给这些活动设置元数据(Metadata)后,Spotlight就可以对它们进行索引。所有的活动会跟浏览网页一样形成一个历史记录栈。用户可以在Sptlight里快速打开已有的活动。

Web Markup

Web Markup允许应用程序将它们的内容映射到一个网站,从而在Spotlight里进行索引。用户不需要在设备上安装Spotlight里出现的应用。苹果的索引器能够抓取我们的网站来获取所需的信息,抓取结果通过Safari和Spotlight提供给用户。

在没有安装应用的设备上进行展示是一个非常重要的特征,这能增加我们的应用在潜在用户前的曝光度。应用程序暴露给搜索API的深度链接被存储在苹果的云索引服务器上。更多关于Web Markup的信息请查看《《Use Web Markup to Make App Content Searchable》》。

CoreSpotlight

CoreSpotlight是一个新的iOS 9框架,它允许我们在应用程序里对任何内容进行索引。NSUserActivity也可以用于保存用户历史。本质上它给我们提供了访问CoreSpotlight索引的能力。

使用Core Spotlight API

NSUserActivity与Web Markup API用起来要比CoreSpotlight要简单。下面我们通过一个简单的应用来说明Core Spotlight API如何工作。这个程序会显示一个好友列表,当点击名字可以看到一个照片。可以在GitHub上找到示例代码。



应用程序使用Storyboard创建界面,其中包含一个FriendTableViewController显示好友列表,而FriendViewController显示好友详情。
 


好友信息保存在名为Datasource的类中。我们在这个类里创建保存好友信息的数据模型,并且包含操作Core Spotlight索引的代码。

首先,我们重写Datasource类中的init()方法,在里面创建一个Person对象的数组。这些数据可以存储在数据库或者后台服务器,这里只是为了演示,所以只是使用一些非常简单的测试数据。
 
override init() {
let becky = Person()
becky.name = "Becky"
becky.id = "1"
becky.image = UIImage(named: "becky")!

...

people = [becky, ben, jane, pete, ray, tom]
}

 
一旦数据被存放在people数组里,Datasource就以及可以使用了。

FriendTableViewController在显示好友列表前要先创建一个Datasource对象。
 
let datasource = Datasource()

 
函数中显示好友名称:
 
let person = datasource.people[indexPath.row]
cell?.textLabel?.text = person.name

 
保存people入口到Core Spotlight

下面我们使用iOS 9中新的API来将数据保存到Core Spotlight中。回到Datasource类,定义一个名为savePeopleToIndex的函数。FriendTableViewController在视图被加后可以调用该方法。

在savePeopleToIndex方法中遍历people数组中的每个人,并且创建对应的CSSearchableItem,然后将它们存储在一个临时数组searchableItems中。
 
let attributeSet = CSSearchableItemAttributeSet(itemContentType: "image" as String)
attributeSet.title = person.name
attributeSet.contentDescription = "This is an entry all about the interesting person called (person.name)"
attributeSet.tumbnailData = UIImagePNGRepresentation(person.image)
let item = CSSearchableitem(uniqueIdentifier: person.id, domainIdentifier: "com.ios9daybyday.SearchAPIs.people", attributeSet: attributeSet)
searchableItems.append(item)

 
最后一步是在默认的CSSearchableIndex上调用indexSearchableItems。这一步实际上是将数据保存到CoreSpotlight,这样用户就可以在Spotlight的搜索结果中看到它们了。



响应用户选择

当用户看到Spotlight中的搜索结果后,有可能会点击它们。但是这样做的结果只是打开了应用的主界面。如果希望在点击了一个朋友后,显示它的详细情况,我们还需要做更多的事情。首先需要定制AppDelegate中通过continueUserActivity打开应用后执行的UIApplicationDelegae的方法。

下面是整个方法的实现:
 
func appliction(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
//查找用户ID
let friendID = userActivity.userInfo?["kCSSearchableItemActivityIdentifier"] as! String

//查找根视图控制器并且显示用户信息
let navigationController = (window?.rootViewController as! UINavigationController)
navigationController.popToRootViewControllerAnimated(false)
let friendTableViewController = navigationController.viewControllers.first as! FriendTableViewController
friendTableViewController.showFriend(friendID)

return true
}

 
我们看到之前通过indexSearchableItems函数存放在CoreSpotlight索引里的数据,可以从userActivity.userInfo字典中获取到。我们感兴趣的数据是每个好友的ID,它被作为kCSSearchableItemActivityIdentifier存储在索引中。

一旦我们将userInfo中的数据取出来,就可以去获取应用程序的导航控制器,并且跳转到根视图,然后通过friendTableViewController里的showFriend方法显示好友详情。结果如下:
 


在屏幕的左上角有一个“返回搜索”的选项,点击可以返回搜索界面。

示例总结

在上面的示例代码里,我们可以看到如何将应用程序的数据整合到CoreSpotlight索引。然后Spotlight如何将我们引导到程序中对应的页面。

我们还没有涉及到如何将数据从索引中移除,但总是保持索引中的数据为最新的很重要。如果需要从CoreSpotlight中移除老的数据,请查看deleteSearchableItemsWithIdentifiers、deleteSearchableItemsWithDomainIdentifiers和deleteAllSearchableItemsWithCompletionHandler方法。

好公民的重要性

将尽可能多的内容索引到Spotlight和Safari看起来很不错,但这么做之前一定要记得三思。成为一个对iOS生态系统有益的好公民不止能够让你的客户体验更好,而且会引起苹果的注意。它们做了许多事情去跟踪、分析哪些垃圾制造者,并将它们放到最后面。

更多信息

关于搜索API的更多信息,建议观看WWDC的Session 709:《Introducing Search APIs》。也可以阅读《NSUserActivity Class Reference》和《CoreSpotlight文档》。本文的示例代码可以在Github获取到。 收起阅读 »

营内推荐教材之必学系列

iOS程序员进阶必学:

1. 《iOS程序员必学NodeJS》https://selfstore.io/products/592
本书就是为处于这个状况下且有想法去涉足步入后台开发领域的iOS开发者而写,采用目前流行的NodeJS进行后台开发,用Javascript语言统一Web前后台开发,希望本书能够带领读者可以跨入NodeJS开发之门,能够结合已掌握的iOS开发技能,进行一定程度的完全自我开发终端应用和对应的后台服务。

 
2. 《从Node.js到iOS学注册登录》https://selfstore.io/products/572
许多刚开始做iOS开发的童鞋总觉得与后台打交道是一件很“神奇”的事情,却忘了服务器端也只不过是同事写用其它语言写的程序而已。更加令人伤心的事情是学习了HTTP的各种请求方式后,还是觉得登录注册太“神秘”,要是加上“图片验证码”、“短信验证码”、“邮件验证码”等等,这就成了“神迹”了。醒醒吧,骚年们,本合集旨在用小白教小白如何破除所有的迷雾。 — 戴维营教育


3. 《零基础学Swift2.2语言》https://selfstore.io/products/573
本教程针对完全零编程经验的观众准备,可以不依赖苹果平台,
在Linux平台快速学习Swift语言的基础基础基础.
本教程内容还不能达到用Swift写App的目的.后续的中级部分可以用Swift写App.
强烈建议本课程的标准学习时间为3天.3天内必须掌握.超时请自省。


4. 《Perfect-Swift全栈开发实践》https://selfstore.io/products/596
“有童鞋会讲,服务器开发不是有PHP/Python/Java/Ruby...甚至Javascript嘛,为毛还要用Swift?最少的成本、最快的速度实现想要的功能就是最好的。

本书目的

作为一个iOS开发者,我们掌握了C/C++、精通Objective-C,现在更是多了一门炙手可热的Swift,但是只用在写iOS应用和游戏,而服务器端一直被PHP/Java/C#的兄弟把控,甚至连Ruby/Javascript也来挑衅?早上醒来,脑海里充溢着一个牛逼的点子,可是一想到需要找人开发服务器接口,顿时就蔫了?不,这已经是过去了。本书的目的就是借助Swift开源的契机,使用 PerfectlySoft 给我们带来的 Perfect 开源框架进行全栈开发。”


 
未完待续...
继续阅读 »
iOS程序员进阶必学:

1. 《iOS程序员必学NodeJS》https://selfstore.io/products/592
本书就是为处于这个状况下且有想法去涉足步入后台开发领域的iOS开发者而写,采用目前流行的NodeJS进行后台开发,用Javascript语言统一Web前后台开发,希望本书能够带领读者可以跨入NodeJS开发之门,能够结合已掌握的iOS开发技能,进行一定程度的完全自我开发终端应用和对应的后台服务。

 
2. 《从Node.js到iOS学注册登录》https://selfstore.io/products/572
许多刚开始做iOS开发的童鞋总觉得与后台打交道是一件很“神奇”的事情,却忘了服务器端也只不过是同事写用其它语言写的程序而已。更加令人伤心的事情是学习了HTTP的各种请求方式后,还是觉得登录注册太“神秘”,要是加上“图片验证码”、“短信验证码”、“邮件验证码”等等,这就成了“神迹”了。醒醒吧,骚年们,本合集旨在用小白教小白如何破除所有的迷雾。 — 戴维营教育


3. 《零基础学Swift2.2语言》https://selfstore.io/products/573
本教程针对完全零编程经验的观众准备,可以不依赖苹果平台,
在Linux平台快速学习Swift语言的基础基础基础.
本教程内容还不能达到用Swift写App的目的.后续的中级部分可以用Swift写App.
强烈建议本课程的标准学习时间为3天.3天内必须掌握.超时请自省。


4. 《Perfect-Swift全栈开发实践》https://selfstore.io/products/596
“有童鞋会讲,服务器开发不是有PHP/Python/Java/Ruby...甚至Javascript嘛,为毛还要用Swift?最少的成本、最快的速度实现想要的功能就是最好的。

本书目的

作为一个iOS开发者,我们掌握了C/C++、精通Objective-C,现在更是多了一门炙手可热的Swift,但是只用在写iOS应用和游戏,而服务器端一直被PHP/Java/C#的兄弟把控,甚至连Ruby/Javascript也来挑衅?早上醒来,脑海里充溢着一个牛逼的点子,可是一想到需要找人开发服务器接口,顿时就蔫了?不,这已经是过去了。本书的目的就是借助Swift开源的契机,使用 PerfectlySoft 给我们带来的 Perfect 开源框架进行全栈开发。”


 
未完待续... 收起阅读 »

京东面试经历 - 技术第一面

2016年1月26日, 京东技术面试
 
首先是笔试题, 分为选择题和问答题, 选择题有部分选择题是多选题, 主要考的就是iOS的基础知识点, 还有一小部分算法的基础.
 
问答题也是iOS的基础知识点, 比如NSObject的常用方法有哪些, 自己封装一个第三方控件, 保证一定是在最上层等, 还有一部分数据结构, 比如链表, 二叉树等.
 
NSObject常用方法:
//用于判断对象是不是参数提供的类型(参数可以是父类的class)
//参数示例: [NSObject class];

[list]
[*](BOOL)isKindOfClass:(Class)aClass;[/*]
[/list]


//用于判断对象是不是参数提供的类型(参数不可以是父类的class)
//参数示例: [NSObject class];

[list]
[*](BOOL)isMemberOfClass:(Class)aClass;[/*]
[/list]


//用于判断对象是否遵守了参数提供的协议
//参数示例: @protocol(UIApplicationDelegate)

[list]
[*](BOOL)conformsToProtocol:(Protocol *)aProtocol;[/*]
[/list]


//用于判断对象是否拥有参数提供的方法
//参数示例: @selector(test) or @selector(testById:)

[list]
[*](BOOL)respondsToSelector:(SEL)aSelector;[/*]
[/list]


//延迟调用参数提供的方法,参数所需参数用withObject传入(类似于ActionScript3.0中的setTimeout函数)
//delay单位:秒
[list]
[*](void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;[/*]
[/list]

 
闲聊也有关于iOS基础知识点, 比如Swift的枚举和Objective-C的区别, CoreData和SQLite, CoreData的优劣, 10次网络请求之后的处理等等问题.
 
 
总结: 京东主要的面试是针对iOS基础是否牢固, 是否有了解过算法与数据结构.
继续阅读 »
2016年1月26日, 京东技术面试
 
首先是笔试题, 分为选择题和问答题, 选择题有部分选择题是多选题, 主要考的就是iOS的基础知识点, 还有一小部分算法的基础.
 
问答题也是iOS的基础知识点, 比如NSObject的常用方法有哪些, 自己封装一个第三方控件, 保证一定是在最上层等, 还有一部分数据结构, 比如链表, 二叉树等.
 
NSObject常用方法:
//用于判断对象是不是参数提供的类型(参数可以是父类的class)
//参数示例: [NSObject class];

[list]
[*](BOOL)isKindOfClass:(Class)aClass;[/*]
[/list]


//用于判断对象是不是参数提供的类型(参数不可以是父类的class)
//参数示例: [NSObject class];

[list]
[*](BOOL)isMemberOfClass:(Class)aClass;[/*]
[/list]


//用于判断对象是否遵守了参数提供的协议
//参数示例: @protocol(UIApplicationDelegate)

[list]
[*](BOOL)conformsToProtocol:(Protocol *)aProtocol;[/*]
[/list]


//用于判断对象是否拥有参数提供的方法
//参数示例: @selector(test) or @selector(testById:)

[list]
[*](BOOL)respondsToSelector:(SEL)aSelector;[/*]
[/list]


//延迟调用参数提供的方法,参数所需参数用withObject传入(类似于ActionScript3.0中的setTimeout函数)
//delay单位:秒
[list]
[*](void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;[/*]
[/list]

 
闲聊也有关于iOS基础知识点, 比如Swift的枚举和Objective-C的区别, CoreData和SQLite, CoreData的优劣, 10次网络请求之后的处理等等问题.
 
 
总结: 京东主要的面试是针对iOS基础是否牢固, 是否有了解过算法与数据结构. 收起阅读 »

云服务器购买指南

国内:
  1. 阿里云服务器(严重推荐),国内速度很快,并且想对其它药稳定
购买链接:http://t.cn/zjxZrUk    打折优惠码:2e8tnt
 
国外:
  1. Linode(日本),稳定性不错,适合做VPN或面向国外市场
继续阅读 »
国内:
  1. 阿里云服务器(严重推荐),国内速度很快,并且想对其它药稳定
购买链接:http://t.cn/zjxZrUk    打折优惠码:2e8tnt
 
国外:
  1. Linode(日本),稳定性不错,适合做VPN或面向国外市场
收起阅读 »