这是一篇swift入门教程,顶10篇文章?虾米?怎么没有swift语法,看下去,你之前的理解都是错的。
更新记录:该Storyboard教程由Caroline Begbie更新iOS 8和Swift相关内容。原文作者为教程编纂组的成员Matthijs Hollemans。
2014/12/10更新:更新至Xcode 6.1.1。
Storyboard是最先在iOS 5引入的一项振奋人心的特性,大幅缩减构建App用户界面所需的时间。
要介绍Storyboard是什么,我打算从这张图讲起。下面是您将会在本教程中构建的Storyboard:
或许你现在并不清楚这个App是用来做什么的,但其中有哪些页面,还有页面间的关联都一目了然。这就是使用Storyboard的力量。
如果App中包括很多不同的页面,使用Storyboard可以帮你减少实现页面间跳转的胶合代码。过去的开发者对应每个视图控制器分别创建界面设计文件(即“nib”或“xib”文件),现在,只要一个Storyboard就可以包揽所有视图控制器的界面设计和他们之间的关联。
Storyboard有很多优点:
-
使用Storyboard可以更好地了解App中所有的视图以及它们之间的关联的概况。掌控全局更加容易,因为所有的设计都包含在一个文件中,而不是分散在很多单独的nib文件中。
-
Storyboard可以描述不同视图之间的过渡,这种过渡叫做“segue”(译注:意为“转场”,而“Storyboard”原意为“分镜”,均源自电影术语),你可以直接在Storyboard中通过连接不同的视图控制器来创建转场。多亏有了转场,打理界面的代码比以前要少了。
-
Storyboard通过新的原型表项(prototype cell)和静态表项(static cell)特性,让处理表视图(table view)的工作更加轻松。几乎完全可以在Storyboard编辑器里搞定表视图的设计,同样也减少了代码量。
-
Storyboard使自动布局(Auto Layout)更易用。自动布局功能可以让你通过界面元素之间的数学关系定义来确定元素的位置和尺寸,极大简化了不同尺寸屏幕的适配工作。自动布局不在本教程范围之内,若想了解更多,请参阅自动布局入门。
如果你非常讨厌Interface Builder,或者推崇用代码搞定所有界面的话,Storyboard可能不适合你。个人主张是代码能少写就少写,特别是UI代码,所以Storyboard简直就是为我准备的一把利器。
如果你想继续使用nib,那就继续用吧,要知道Storyboard里是可以使用nib的,两者并非互斥关系。
本教程中,你会了解Storyboard可以做什么,我们将构建一个简单的App,功能大致是创建玩家列表和游戏列表,然后给玩家技能评分。过程中你会学到大多数可以用Storyboard完成的最常见的任务。
准备开始
打开Xcode,创建新项目。选用Single View Application模板:
如下填写模板选项:
-
Product Name:ratings
-
Organization Name: 随意填写
-
Company Identifier: 你的App使用的标识符,逆域名记法
-
Language:Swift
-
Devices:iPhone
-
Use Core Data: 不选
项目创建完成后,Xcode的主界面应该如下图所示:
这个新项目包含2个类:AppDelegate 和 ViewController, 此外还有本教程的主角: Main.storyboard 文件。
这是一个只支持竖屏显示的App,所以在继续之前,在项目综合设置上面看到的 Deployment Info - Device Orientation下面把 Landscape Left和Landscape Right 选项勾掉。
接下来我们看一下Storyboard,点击项目浏览器中的 Main.storyboard 就可以在Interface Builder中打开。
一个视图控制器在Storyboard中的官方术语是“场景(scene)”,但这两种叫法是相通的。一个视图控制器在Storyboard中可以叫做场景。
这里可以看到一个包含空视图的视图控制器。在这个视图控制器左边指向它的箭头表明它是这个Storyboard中要显示的第一个视图控制器。
在Storyboard编辑器中设计布局的方法是从右下角的Object Library(对象库)中把控件拖入视图控制器,非常容易。
注:你会注意到默认场景是一个正方形。Xcode 6默认为Storyboard和nib文件开启自动布局(Auto Layout)和尺寸归类(Size Classes)。自动布局和尺寸归类这两项新技术可以构建易于调整大小的用户界面,这对支持不同尺寸的iPhone和iPad非常有用。
自动布局由iOS 6引入,尺寸归类由iOS 8引入。两者都需要一定的学习曲线,所以本教程中暂不使用,但为了支持不同的设备尺寸,以后还是要接触到的。要了解更多,请参阅我们的书籍iOS 8教程在继续探索之前,先在当前Storyboard的File inspector(文件检查器)中禁用Auto Layout和Size Classes,如图:
Xcode询问操作时,选择保留iPhone的尺寸归类数据,然后点击disable Size Classes:
现在,场景变成了4英寸iPhone尺寸的样子。
从右下方的对象库里把一些控件拖到空的视图控制器上,感受一下Storyboard编辑器的工作方式:
控件拖进来之后应该会在左边的文档大纲(Document Outline)中显示:
如果没看到文档大纲,请点击Storyboard面板左下角的这个按钮:
Storyboard显示所有视图控制器的内容,当前的Storyboard中仅有一个视图控制器(场景),在本教程后面我们会添加其他场景。
在场景上面还有一个缩小的文档大纲,称作Dock:
Dock显示场景中最上层的对象,每个视图都至少有一个 视图控制器(View Controller) 对象,一个 第一响应者(First Responder) 对象,一个 出口(Exit) 项。除此之外也可以有其他的最上层对象。Dock方便连接outlet和action,当你想把某个对象连接到视图控制器时,只需把它拖到Dock的图标上。
注:你可能不常用到First Responder。这是指任意对象在任意时间具有第一响应状态的代理对象。举个例子,把一个按钮的Touch Up Inside事件拖到First Responder的cut:
选择器上。如果在某时有一个文本字段具有输入焦点,此时按下该按钮,就可以让该文本字段,也就是现在的第一响应者,把其中的文本剪切到剪贴板。
运行App,它看起来应该和你在编辑器中设计的样子相同(截图可能与你的不同,仅供演示参考,教程后面不会用到):
你定义的这个视图控制器被设定为初始视图控制器,但App是如何加载的呢?答案就在应用代理(application delegate)当中,打开AppDelegate.swift,你会看到如下代码:
1
2
3
4
5
6
7
8
9
10
|
importUIKit
@UIApplicationMain
classAppDelegate:UIResponder,UIApplicationDelegate{
var
window:UIWindow?
funcapplication(application:UIApplication!,didFinishLaunchingWithOptionslaunchOptions:NSDictionary!)->Bool{
//Overridepointforcustomizationafterapplicationlaunch.
return
true
}
|
上面的@UIApplicationMain
标记指定这个AppDelegate类为该模块的入口。使用Storyboard时,应用代理必须继承UIResponder
,必须含有UIWindow
属性,几乎所有的方法都是空的,甚至application(_:didFinishLaunchingWithOptions:)
也只是返回true而已。
秘密藏在 Info.plist 文件里,在Supporting Files Group里找到并点击Info.plist,你会看到这一条:
Storyboard用UIMainStoryboardFile
(即Main storyboard file base name键) 来指明App启动时必须加载的Storyboard的名称。当设置生效,UIApplication
会加载对应名称的Storyboard文件,自动将该Storyboard中的初始视图控制器实例化,并将其纳入一个新的UIWindow
对象中。
在General分页的Project Settings和Deployment Info中也可以看到:
接下来真正开始创建包含多个视图控制器的评分App吧。
添加分页标签
你要构建的这个评分App中含有由分页标签控制的两个视图,使用Storyboard创建分页标签非常容易。
现在这个Storyboard需要从头做起,切回 Main.storyboard 然后把刚才做的视图控制器删掉。在文档大纲中点击View Controller
并按下delete键即可。
把一个 Tab Bar Controller(分页栏控制器) 从对象库拖到面板中。你可能需要让Xcode最大化,因为分页栏控制器附带两个视图控制器,需要腾出更多空间,你可以双击面板进行缩放,或者按住control点击面板,在弹出的菜单中选择缩放比例。
一个新增的分页栏控制器默认附带两个额外的视图控制器,每个分页标签一个控制器。由于UITabBarController包含一个或多个其他的视图控制器,它被称作 容器视图控制器。此外还有两种常见的容器视图控制器,Navigation Controller(导航控制器)和Split View Controller(分割视图控制器)。
容器关系由分页栏控制器和他所包含的视图控制器之间的箭头表示,如下图这个箭头上的图标表示嵌入关系。
注:如果你想一起移动分页栏控制器和附带的视图控制器的话,先缩小画面,然后按住command点击,或直接拖选多个场景,这样可以同时移动多个场景。(选中的场景轮廓为淡蓝色。)
在第一个视图控制器(当前名称为“Item 1”)中拖入一个Label(文本标签)并将其文本设为 "First Tab",同理,在第二个视图控制器中加入文本为"Second Tab"的Label,这样你就可以看到分页标签切换后的变化。
注:编辑器缩小时无法向场景内拖入控件,此时需要先在面板上双击,回到正常缩放比例。
构建,运行,你会在Console中看到类似信息:
ratings[18955:1293100] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
幸运的是这条报错信息讲得很清楚:未设置入口,也就是刚才删除最先使用的那个场景之后没设置初始视图控制器。为解决问题,选中这个分页栏控制器,然后在Attributes Inspector(属性检查器)中选定Is Initial View Controller。
注:在Xcode 6.2中,上述选项已被控件取代。先选中当前分页栏控制器,然后从对象库里把一个Storyboard Entry Point(Storyboard入口)拖上去,可以拖到控制器上面,也可以拖入文档大纲。
现在,一开始的那个箭头已经指向当前的分页栏控制器了:
注:Xcode 6.2 beta在这里可能会崩溃,如果出现问题,请选中该分页栏控制器的某个视图控制器,把入口拖上去,然后再把入口箭头拖到分页栏控制器上。
这意味着启动App时, UIApplication 会把此分页栏控制器作为主画面。运行App试一试,现在App下面有分页标签栏了,可以用分页标签在两个视图控制器之间切换。
提示:你也可以通过拖拽视图控制器之间的箭头来改变初始视图控制器。
其实在前面你也可以选用Xcode自带的标签分页式App模板(即Tabbed Application模板)创建App,但最好还是了解一下工作原理,以后有必要的时候也能手动创建分页栏控制器。
注:如果在分页栏控制器上连接超过5个场景,App在运行时会自动将其归入一个More分页标签,干净利落。
添加表视图控制器
现在附属于分页栏控制器的两个场景都是标准UIViewController
实例,接下来你会把其中第一个分页标签对应的场景替换为UITableViewController
在文档大纲中点选第一个视图控制器并将其删除,然后从对象库中把一个新的Table View Controller(表视图控制器)拖到原场景所在的地方。
接下来把表视图控制器放到导航控制器(Navigation Controller)中,选中表视图控制器,在Xcode菜单中选择EditorEmbed InNavigation Controller,现在面板中又加入了另一个控制器:
你也可以从对象库中拖入导航控制器后再嵌入表视图,但这个操作一般来讲使用菜单命令会更省时。
与分页栏控制器类似,导航控制器也是容器视图控制器,所以有一个关系箭头指向表视图控制器,你也可以在文档大纲中看到这个关系:
注意,嵌入表视图控制器后,Interface Builder自动给它添加了一个导航栏,因为当前视图是在导航控制器的框架中显示的。它并不是实际存在的UINavigationBar
对象,只是模拟显示情况。
打开表视图控制器的属性检查器,上面可以看到 Simulated Metrics(模拟度量)选项:
Storyboard中的默认值为“Inferred(推断)”,意思是该场景在处于导航控制器中时会显示导航栏,处于分页栏控制器中时会显示分页栏等等。你可以修改这些设置,但是请记住,这只是方便你设计界面时参考的模拟显示,并不会在运行时使用,仅仅是视觉设计的辅助工具,用来表示视图最后应该是什么样子。
接下来把这两个新场景连接到分页栏控制器,按住control从分页栏控制器拖到导航控制器,松手时会弹出一个小选单,选择Relationship Segue – view controllers选项:
这会在两个场景间新建一个关系箭头,与分页栏控制器包含控制器一样,都是嵌入关系。
分页栏控制器有两个嵌入关系,分别对应两个分页标签。导航控制器上有一个表视图控制器的嵌入关系。
创建这个连接后,分页栏控制器中会添加一个新分页标签,默认名称为“Item”。在这个App中,你希望第一个分页标签对应这个新场景,直接拖动分页标签,更改顺序:
运行App试试看,现在第一个分页标签中包含一个嵌入在导航控制器中的表视图。
在添加实际功能之前,你还需要再修整一下Storyboard,将第一个分页标签命名为"Players",第二个命名为"Gestures"。不是在分页栏控制器上修改,而是在这些分页标签对应的视图控制器上修改。
将一个视图控制器连接到分页栏控制器后,在场景下面和文档大纲中会看到它被赋予的 分页栏项(Tab Bar Item) 对象,可以用来设置分页标签的标题和在分页栏控制器中看到的图标。
选中导航控制器中的分页栏项,在属性检查器中将标题设为Players:
以同样的方法把第二个分页标签对应场景栏目改名为Gestures。
一个设计精良的App应该为分页标签附上图标。教程资源中有个Image文件夹,把这个文件夹拖入项目,选择“copy items if needed”并点击Finish:
在Players分页栏项的属性检查器中选择图片Players.png。
你可能已经想到了,给Gestures选择Gestures.png。
嵌入导航控制器的一个视图控制器包含用于设置导航栏的Navigation Item(导航项)。在文档大纲中选择表视图控制器的导航项,在属性检查器中把Title改成Players。
或者你也可以双击导航栏直接修改title,注意你需要双击的是表视图控制器中的模拟导航栏,而不是导航控制器中的那个导航栏对象。
运行App,欣赏一下这漂亮的分页标签栏吧!一行代码也不用写哦!
原型表项(Prototype Cell)
原型表项允许你直接在Storyboard编辑器中为表视图设计自定义布局。
表视图控制器默认会带一个空的原型表项。点击它,在属性检查器中设置Style为 Subtitle(副标题)。这会立即改变表项的外观,使其包含两个Label。
Storyboard上可以堆叠很多内容,有时可能很难点击到你想选中的东西。如果遇到困难,有几种选择:第一是在面板左侧的文档大纲中选择,第二是快捷键(按住control+option+shift,点击想选择的区域后会弹出指针所指区域的所有元素),第三种选择是Xcode 6的新功能,反复点击可以在各层之间循环。
如果你之前用过表视图,还手动创建过自己的表项,你可能会将其认作UITableViewCellStyle.Subtitle
样式。有了原型表项,你可以像刚才那样选择系统内建的样式,也可以自定义设计(我们稍后就要创建了)。
设置Accessory(附件,即表项右侧的附属元素)属性为 disclosure Indicator(展开方向标,即右键头),并在 Identifier(标识符) 字段中输入 PlayerCell。所有的原型表项仍然是标准UITableViewCell
对象,所以它们需要一个以供重用的标识符。
运行应用……什么都没变。这没什么值得奇怪的,接下来你还需要为这个表指定一个data source(数据源),这样它才会知道要显示什么。
在项目中添加一个新文件,选择iOS/Source下的Cocoa Touch Class模板,命名为 PlayersViewController ,并确保它是UITableViewController的子类。不要选中Also create XIB file选项,因为你已经在Storyboard中设计好了,今天不用nib!选择Swift语言,点击Next,然后点击Create。
回到Storyboard,选择表视图控制器(确保你选择的是视图控制器而不是其中包含的某个视图)。在身份检查器(Identity inspector)中设置它的 Class 为 PlayersViewController。这对于在Storyboard场景中使用自定义视图控制器的子类很重要,因为如果你不这么做,你的类就都不会被使用!
此后运行App时Storyboard中加载的那个表视图控制器就是PlayersViewController
类的实例。
这个表视图要显示玩家列表,所以你需要为App创建主要的数据模型:一个包含Player对象的数组。由iOS/Source下的Swift File模板添加新文件,命名为Player。
在Player.swift中追加以下代码:
classplayer:NSObject{
name:String
game:String
rating:Int
init(name:String,game:String,rating:Int){
self.name=name
self.game=game
self.rating=rating
super
.init()
}
没什么特别的东西,
|