故事板是iOS 5中首次引入的令人兴奋的功能,可以节省为应用程序构建用户界面的时间。
您可能不知道这个app的功能,但您可以看到它的场景以及它们之间的关系。
故事板有许多优点:
- 您可以在“场景”中以可视方式布置所有视图控制器,并描述它们之间的连接。通过故事板,您可以更好地概念化应用中的所有场景。
- 描述各种场景之间的跳转。这些跳转称为“segues”,您可以通过连接故事板中的视图控制器来创建它们。
- 使用原型和静态单元格功能,可以更轻松地使用表格视图。您可以在故事板编辑器中设计完整的表视图,从而减少代码量。
- 使用Auto Layout,这一功能可以轻松适配各种屏幕尺寸的设备。之后的教程将会单独开一章来讲。
在本教程中,您将构建一个示例app,创建一个玩家列表,并向您显示他们玩的游戏和他们的技能等级。
入门
打开Xcode并创建一个新项目。使用*Single View Application *模板。
创建后,Xcode窗口应如下:
新项目包含三个文件,AppDelegate.swift,ViewController.swift和本教程的焦点:Main.storyboard。
在 General > Deployment Info > Device Orientation下,将设备设置为iPhone。由于这是一个仅有纵向屏幕的app,因此取消选中Landscape Landscape和Landscape Right选项。
打开Main.storyboard:
视图控制器的官方术语是“场景”。
现在看到的时包含空视图的一个视图控制器。左侧的箭头表示这个是app启动之后的初始视图控制器。
在故事板中是通过将对象库中的控件(参见下图右下角)拖动到视图控制器中来完成的。
默认场景是4.7英寸的屏幕。Xcode默认为故事板启用自动布局和大小类。自动布局和大小类允许您制作可轻松调整大小的灵活用户界面,这对于适配各种尺寸的iPhone和iPad非常有用。要将场景大小更改为其他设备,请单击故事板左下角的按钮。然后,您将能够从纵向和横向两种方式中选择支持的全系列设备,从iPad Pro(12.9英寸)到iPhone 4S(3.5英寸)。
对于本教程,我们将保持默认场景大小 - iPhone 7 。
要了解故事板编辑器的工作方式,请将对象库中的一些控件拖到空白视图控制器中:
拖动控件后,它们会显示在左侧的Document Outline中:
故事板显示了所有场景的内容。目前,故事板中只有一个场景,但在本教程中,您将添加其他几个场景。
这个Document Outline的微缩版本在场景上方,称为Dock:
该Dock展示了场景中的顶级对象。每个场景至少具有一个View Controller对象,一个First Responder对象和一个Exit对象。它也可能包含其他顶级对象。该Dock是方便制作到出口和动作连接。如果您需要将某些内容连接到场景,只需将其拖动到Dock中的图标即可。
构建并运行应用程序,它应该与您在编辑器中设计的内容完全一样(您的内容可能与下面的屏幕截图不同):
现在用几个视图控制器创建真正的评级应用程序。
只需添加到tab
您即将构建的评级应用程序具有两个场景的tab式界面。使用故事板,可以轻松创建tab。
打开Main.storyboard并删除之前使用过的场景。这可以通过单击Document Outline中的视图控制器并按删除键。
将Tab Bar Controller从对象库拖到画布中。
新的Tab Bar Controller预先配置了两个额外的视图控制器 - 每个选项卡一个。UITabBarController
是一个所谓的容器视图控制器,因为它包含一个或多个其他视图控制器。另外两个常见的容器是导航控制器(Navigation Controller)和拆分视图控制器(Split View Controller)(稍后您将使用导航控制器)。
容器内的关系由Tab Bar Controller和它包含的视图控制器之间的箭头表示。
将一个label控件拖动到第一个视图控制器(现在的标题为“item1”),双击它,并为其指定文本“First Tab”。接下来,将另一个label控件拖到第二个视图控制器(“item2”)中,并为其指定文本“second Tab”。这样您可以在选项卡切换时看到他们发生的事情。
构建并运行应用程序。您将在控制台中看到:
Ratings[18955:1293100] Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?
幸运的是,错误在这里非常清楚 - 您从未设置入口点,这意味着在删除上一个场景后没有设置初始视图控制器。要解决此问题,请点击Tab Bar Controller,在右侧栏的Attributes Inspector并选中Is Initial View Controller。
在画布中,箭头现在指向Tab Bar Controller:
构建并运行应用程序。现在您可以看到一个标签栏,可以在两个视图控制器之间切换:
注意:要更改初始视图控制器,还可以在拖动视图控制器之间的箭头。
Xcode附带了一个用于构建tab类型的app的模板(称为Tabbed Application template)。您也可以使用它,但本节为了学习使用storyboard,我们手动创建一个Tab Bar Controller。
注意:如果超过五个选项卡,则在运行应用程序时它会自动出现一个更多按钮的选项卡。很简单!
添加Table View Controller
当前附加到选项卡栏控制器的两个场景都是UIViewController
实例。我们将替换第一个场景为UITableViewController
。
单击第一个视图控制器将其选中,然后将其删除。将Table View Controller拖到画布中:
接下来,您要将表视图控制器放在导航控制器中。首先,选择Table View Controller。接下来,从Xcode的菜单栏中选择Editor \ Embed In \ Navigation Controller。这为画布添加了另一个控制器:
由于导航控制器也是一个容器视图控制器(就像标签栏控制器一样),它有一个指向表视图控制器的关系箭头。您还可以在文档大纲中看到这些关系:
注意:把表视图控制器嵌入(Embed)进一个导航控制器,会自动给它一个导航栏。
要将这两个新场景连接到标签栏控制器,请单击标签栏控制器并按住Ctrl键拖动到导航控制器。松开后,会出现一个弹出菜单。选择Relationship Segue – view controllers选项:
这会在两个场景之间创建一个新的关系箭头。当您看到Tab Bar Controller包含的其他控制器时,这也是一个嵌入式关系。
选项卡栏控制器有两个嵌入关系,每个tab各嵌入一个。导航控制器本身与表视图控制器具有嵌入关系。
进行此新连接后,标签栏控制器中添加了一个新选项卡,名为“item”。您希望此场景成为第一个选项卡,因此请拖动选项卡以更改其顺序:
构建并运行应用程序以进行尝试。第一个选项卡现在包含导航控制器内的表视图。
在将一些实际功能放入此应用程序之前,您需要稍微清理一下故事板。您将命名第一个选项卡“Players”和第二个“Gestures”。您不要在标签栏控制器上更改,而是具体的视图控制器中更改。
只要将视图控制器连接到选项卡栏控制器,就会给它一个Tab Bar Item对象,您可以在文档大纲或场景底部看到它。在右侧栏可以配置选项卡的标题和图像。
选择导航控制器内的Tab Bar Item,然后在右侧Attributes inspector中将其标题设置为“ Players”:
接下来,将第二个选项卡的Tab Bar Item重命名为Gestures,方法与上面相同。
精心设计的应用程序还应在这些选项卡上添加图标。点击下载一个名为Images的文件夹。将该文件夹拖到项目的Assets.xcassets中。
打开Main.storyboard,在“players 的 tab bar item”的属性检查器中,选择图像。
接下来,给Gestures标签栏项目图像Gestures。
嵌入在导航控制器内的视图控制器具有用于配置导航栏的导航项。在“ 文档大纲”中选择“表视图控制器”的“导航项”,并在“属性”检查器中将其标题更改为“ Players”。。
“ 文档大纲”中的“场景”标题现在也自动更改为“ Players”了
注意:您可以双击导航栏并在那里更改标题。
构建并运行应用程序。现在看看你做的漂亮的tab栏,到现在没有编写任何代码!
原型单元格
原型单元格允许您直接在storyboard中为表格视图单元格轻松设计自定义布局。
Table View Controller附带一个空白原型单元。单击单元格以选择它,然后在“ 属性”检查器中将“ Style”选项设置为“ Subtitle”。这会立即更改单元格的外观。
注意:由于故事板上有如此多的可堆叠内容,有时很难点击您想要的内容。如果遇到麻烦,有几种选择。一个是您可以选择画布左侧的文档大纲中的项目。第二个是一个方便的热键:按住control + shift并单击你感兴趣的区域。将出现一个弹出窗口,允许您直接选择光标下的任何元素。
将Accessory属性设置为Disclosure Indicator,将Identifier设置为PlayerCell。所有原型单元都必须具有重用标识符,因此您可以在代码中引用它们。此外,将单元格的Selection设置为nonet。
注意:单元格的Selection属性设置为None,以去掉PlayerCell的点击效果。
构建并运行应用程序,没有任何改变。这并不奇怪:您仍然需要为表创建数据源,以便知道要显示哪些行。
在项目中创建新file。在iOS / Source下选择Cocoa Touch Class模板。将类命名为PlayersViewController,并使其成为UITableViewController的子类。取消选中也创建XIB文件。选择Swift语言,然后单击Next,然后单击Create。
打开Main.storyboard并选择Table View Controller(确保选择实际的视图控制器而不是其中一个视图)。在Identity检查器中,将其Class设置为PlayersViewController。这是使用自定义视图控制器子类从故事板连接场景的关键步骤。不要忘记这个!
现在,当您运行应用程序时,故事板中的表视图控制器是PlayersViewController
该类的实例。
表视图应显示Player列表,因此现在您将为应用程序创建数据模型 - 包含Player
对象的数组。使用iOS / Source下的Swift File模板将新文件添加到项目中,并将文件命名为Player。
用以下内容替换Player.swift中的代码:
import Foundation
struct Player {
// MARK: - Properties
var name: String?
var game: String?
var rating: Int
}
这里没什么特别的。Player
它只是这三个属性的容器对象:玩家的名字,他们正在玩的游戏,以及1到5星的评级。请注意,因为您没有定义自定义构造器,struct将自动初始化。
接下来,使用名为SampleData的Swift File模板创建一个新文件。用以下内容替换SampleData.swift的内容:
import Foundation
final class SampleData {
static func generatePlayersData() -> [Player] {
return [
Player(name: "Bill Evans", game: "Tic-Tac-Toe", rating: 4),
Player(name: "Oscar Peterson", game: "Spin the Bottle", rating: 5),
Player(name: "Dave Brubeck", game: "Texas Hold 'em Poker", rating: 2)
]
}
}
在这里,您已经定义了一个静态方法SampleData
来生成Player
对象数组。
接下来,打开PlayersViewController.swift并使用以下内容替换文件的内容:
import UIKit
class PlayersViewController: UITableViewController {
// MARK: - Properties
var players = SampleData.generatePlayersData()
}
您可以在PlayersViewController
定义player变量时设置样本数据。但是这些数据在实际开发中可能是从plist或其他外部源(服务器)来的,因此现在也模拟这么做。
现在你有一个Player
对象的数组,继续连接数据源到PlayersViewController
。在PlayersViewController.swift中,将以下extension添加到文件末尾:
// MARK: - UITableViewDataSource
extension PlayersViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return players.count
}
override func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell", for: indexPath)
let player = players[indexPath.row]
cell.textLabel?.text = player.name
cell.detailTextLabel?.text = player.game
return cell
}
}
该方法dequeueReusableCell(withIdentifier:for:)
将重用原型单元格。您需要做的就是提供您在storyboard编辑器中的原型单元格上设置的重用标识符(identifier) - 在本例中为PlayerCell。不要忘记设置标识符,否则这个app将无效!
构建并运行应用程序,表格视图中有玩家!
使用这些原型单元格只需几行代码!
注意:在这个应用程序中,您只使用一个原型单元格。如果您的表需要显示不同类型的单元格,则可以向故事板添加其他原型单元格。确保为每个单元格提供自己的重用标识符!
设计自己的原型cell
对于大多数应用程序,使用标准单元格样式都很好,但对于此应用程序,您希望在单元格的右侧添加一个显示玩家评级的图像。标准单元格样式不支持在该位置具有图像视图,因此您必须进行自定义设计。
打开Main.storyboard,在表视图中选择原型单元格,然后在“ 属性”检查器中,将其“ 样式”属性设置为“ 自定义”。默认标签现在消失。
首先让cell高一点。在“ 大小”检查器中更改“ 行高”值(在选中“自定义”后)或拖动单元格底部。设高为60point。
将两个Label对象从“对象库” 拖动到单元格中,并将它们大致放置在以前标准标签的位置。只需使用属性检查器中的字体和颜色,然后选择您喜欢的内容。将顶部标签的文本设置为Name,将底部标签设置为Game。
使用Command +单击选择Document Outline中的Name和Game标签,然后选择Editor \ Embed In \ Stack View。
将image view控件拖动到单元格中,并将其放在cell的右侧。在尺寸检查器中,使其宽81点,高35点。将其“ content mode”设置为“ center”(在“属性”检查器中的“view”下),这样您放入此视图的任何图像都不会被拉伸。
按住Command并单击“ 文档大纲 ”中的“Stack View”和“image view” 以选择它们。选择编辑器\嵌入\ Stack View。Xcode将创建一个包含这两个控件的水平堆栈视图。
选择此新的水平堆栈视图,然后在“ 属性”检查器中,将“alignment”更改为“ center”,将“Distribution”更改为“ Equal Spacing”。
现在为这个控件的一些简单的自动布局。在故事板的右下角,单击图钉图标:
将顶部约束更改为Top:0,Right:20,Bottom:0和Left:20。确保如图所示的四个红色指针。单击弹出窗口底部的“ 添加4个约束 ”。
如果堆栈视图具有橙色约束,则会放错位置。要解决此问题,请选择水平堆栈视图,然后选择编辑器\解析自动布局问题\更新框架(在菜单的选定视图部分中)。堆栈视图应正确定位,橙色约束错误消失。
要在堆栈视图中定位图像视图,请在“ 文档大纲”中选择图像视图,然后选择“ 编辑器\解决自动布局问题\添加缺失约束”(在菜单的“选定视图”部分中)。
您可能会在文档大纲中看到一个红色突出显示的小箭头,表示堆栈视图未解决的布局问题。单击此箭头并单击红色圆圈以查看Xcode建议的自动修复。为name或game标签的优先级选择更改优先级应该使此警告消失。
原型单元的最终设计看起来像这样:
因为这是一个custom的cell,你不能再使用UITableViewCell
的textLabel
和detailTextLabel
属性来放置文本标签。它们仅对标准细胞类型有效。相反,您将子类化UITableViewCell
以提供功能。
使用子类单元格
使用Cocoa Touch Class模板向项目添加新文件。将其命名为PlayerCell并使其成为UITableViewCell的子类。不要选中创建XIB的选项,因为故事板中已有单元格。
接下来,将以下内容添加到PlayerCell
类中,就在类定义的下方:
// MARK: - IBOutlets
@IBOutlet weak var gameLabel: UILabel!
@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var ratingImageView: UIImageView!
这些IBOutlets
可以使用故事板连接到您的场景。
接下来,在IBOutlets
下面添加以下属性:
// MARK: - Properties
var player: Player? {
didSet {
guard let player = player else { return }
gameLabel.text = player.game
nameLabel.text = player.name
ratingImageView.image = image(forRating: player.rating)
}
}
每当设置player
属性时,它将验证是否有值,如果是,则IBOutlets
使用正确的信息更新。
接下来,添加以下方法:
func image(forRating rating: Int) -> UIImage? {
let imageName = "\(rating)Stars"
return UIImage(named: imageName)
}
这将根据提供的评级返回不同的星形图像。
接下来,打开Main.storyboard,选择原型单元格PlayerCell
并在Identity检查器中将其类更改为PlayerCell
。现在,每当您向表格视图询问新单元格时dequeueReusableCell(withIdentifier:for:)
,它将返回PlayerCell
实例而不是常规实例UITableViewCell
。
注意:您为此类提供了与重用标识符相同的名称 - 它们都被调用PlayerCell
- 但这只是因为我喜欢保持一致。类名和重用标识符彼此无关,因此如果您愿意,可以将它们命名为不同的名称。
最后,将标签和图像视图连接到这些插座。到故事板中的Connections Inspector,然后从画布或文档大纲中选择Player Cell。从Connections检查器中的nameLabelOutlet 拖动到Document Outline或画布中的Name标签对象。重复gameLabel和ratingImageView。
现在您已经连接了属性,您可以稍微简化数据源代码。打开PlayersViewController.swift,并切换tableView(_:cellForRowAt:)
到以下内容:
override func tableView(_ tableView: UITableView,cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PlayerCell",for: indexPath) as! PlayerCell
let player = players[indexPath.row]
cell.player = player
return cell
}
差不多了。您现在将收到的对象转换dequeueReusableCell
为一个 PlayerCell
,并将正确的值传递给player
单元格。使用原型单元格使表格视图变得不那么混乱。
构建并运行应用程序。
嗯,这看起来不太正确 - cell似乎被压扁了。您确实更改了原型单元格的高度,但表格视图没有考虑到这一点。有两种方法可以解决它:您可以更改表视图的行高属性,或实现该tableView(_:heightForRowAt:)
方法。前者在这种情况下很好,因为我们只有一种类型的单元格,我们事先知道高度。
注意:tableView(_:heightForRowAt:)
如果您事先不知道细胞的高度,或者不同的行可以具有不同的高度,则可以使用。
打开Main.storyboard,在Table View 的Size检查器中,将Row Height设置为60:
构建并运行应用程序。
ok!
注意:如果通过拖动cell而不是键入值来更改单元格高度,则表视图的行高度属性也会自动更改。所以可能拖动更适合你。