Swift語言Storyboard教程:第一部分

本文由CocoaChina翻譯小組@TurtleFromMars翻譯自:Storyboards Tutorial in Swift: Part 1ios

更新記錄: 該Storyboard教程由Caroline Begbie更新iOS 8和Swift相關內容。原文做者爲教程編纂組的成員Matthijs Hollemans

swift

2014/12/10更新: 更新至Xcode 6.1.1。

數組

Storyboard是最早在iOS 5引入的一項振奮人心的特性,大幅縮減構建App用戶界面所需的時間。

瀏覽器

要介紹Storyboard是什麼,我打算從這張圖講起。下面是您將會在本教程中構建的Storyboard:app

The full storyboard for the app

或許你如今並不清楚這個App是用來作什麼的,但其中有哪些頁面,還有頁面間的關聯都一目瞭然。這就是使用Storyboard的力量。框架

若是App中包括不少不一樣的頁面,使用Storyboard能夠幫你減小實現頁面間跳轉的膠合代碼。過去的開發者對應每一個視圖控制器分別建立界面設計文件(即「nib」或「xib」文件),如今,只要一個Storyboard就能夠包攬全部視圖控制器的界面設計和他們之間的關聯。編輯器

Storyboard有不少優勢:ide

  • 使用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 模板:

01_sb_newproject

以下填寫模板選項:

  • Product Name: Ratings

  • Organization Name: 隨意填寫

  • Company Identifier: 你的App使用的標識符,逆域名記法

  • Language: Swift

  • Devices: iPhone

  • Use Core Data: 不選

項目建立完成後,Xcode的主界面應該以下圖所示:

initial project config

這個新項目包含2個類:AppDelegate 和 ViewController, 此外還有本教程的主角: Main.storyboard 文件。

這是一個只支持豎屏顯示的App,因此在繼續以前,在項目綜合設置上面看到的 Deployment Info - Device Orientation下面把 Landscape Left和Landscape Right 選項勾掉。

接下來咱們看一下Storyboard,點擊項目瀏覽器中的 Main.storyboard 就能夠在Interface Builder中打開。

The initial storyboard

一個視圖控制器在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,如圖:

Disabling auto layout

Xcode詢問操做時,選擇保留 iPhone 的尺寸歸類數據,而後點擊 Disable Size Classes :

Disabling size classes

如今,場景變成了4英寸iPhone尺寸的樣子。

從右下方的對象庫裏把一些控件拖到空的視圖控制器上,感覺一下Storyboard編輯器的工做方式:

Dragging controls into storyboard

控件拖進來以後應該會在左邊的文檔大綱(Document Outline)中顯示:

Document outline

若是沒看到文檔大綱,請點擊Storyboard面板左下角的這個按鈕:

The document outline button

Storyboard顯示全部視圖控制器的內容,當前的Storyboard中僅有一個視圖控制器(場景),在本教程後面咱們會添加其餘場景。

在場景上面還有一個縮小的文檔大綱,稱做Dock:

Dock

Dock顯示場景中最上層的對象,每一個視圖都至少有一個 視圖控制器(View Controller) 對象,一個 第一響應者(First Responder) 對象,一個 出口(Exit) 項。除此以外也能夠有其餘的最上層對象。Dock方便鏈接outlet和action,當你想把某個對象鏈接到視圖控制器時,只需把它拖到Dock的圖標上。

注:你可能不經常使用到First Responder。這是指任意對象在任意時間具備第一響應狀態的代理對象。舉個例子,把一個按鈕的Touch Up Inside事件拖到First Responder的 cut: 選擇器上。若是在某時有一個文本字段具備輸入焦點,此時按下該按鈕,就可讓該文本字段,也就是如今的第一響應者,把其中的文本剪切到剪貼板。

運行App,它看起來應該和你在編輯器中設計的樣子相同(截圖可能與你的不一樣,僅供演示參考,教程後面不會用到):

First app in the simulator

你定義的這個視圖控制器被設定爲初始視圖控制器,但App是如何加載的呢?答案就在應用代理(application delegate)當中,打開 AppDelegate.swift ,你會看到以下代碼:

1
2
3
4
5
6
7
8
9
10
import UIKit
  
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
   var  window: UIWindow?
  
   func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {
     // Override point for customization after application launch.
     return  true
   }

上面的 @UIApplicationMain 標記指定這個AppDelegate類爲該模塊的入口。使用Storyboard時,應用代理必須繼承 UIResponder ,必須含有 UIWindow 屬性,幾乎全部的方法都是空的,甚至 application(_:didFinishLaunchingWithOptions:) 也只是返回true而已。

祕密藏在 Info.plist 文件裏,在Supporting Files Group裏找到並點擊 Info.plist ,你會看到這一條:

Info.plist

Storyboard用 UIMainStoryboardFile(即Main storyboard file base name鍵) 來指明App啓動時必須加載的Storyboard的名稱。當設置生效,UIApplication會加載對應名稱的Storyboard文件,自動將該Storyboard中的初始視圖控制器實例化,並將其歸入一個新的 UIWindow 對象中。

在General分頁的Project Settings和Deployment Info中也能夠看到:

Project Settings

接下來真正開始建立包含多個視圖控制器的評分App吧。

添加分頁標籤

你要構建的這個評分App中含有由分頁標籤控制的兩個視圖,使用Storyboard建立分頁標籤很是容易。

如今這個Storyboard須要從頭作起,切回 Main.storyboard 而後把剛纔作的視圖控制器刪掉。在文檔大綱中點擊 View Controller 並按下delete鍵便可。

把一個 Tab Bar Controller(分頁欄控制器) 從對象庫拖到面板中。你可能須要讓Xcode最大化,由於分頁欄控制器附帶兩個視圖控制器,須要騰出更多空間,你能夠雙擊面板進行縮放,或者按住control點擊面板,在彈出的菜單中選擇縮放比例。

Tab bar controller

一個新增的分頁欄控制器默認附帶兩個額外的視圖控制器,每一個分頁標籤一個控制器。因爲UITabBarController包含一個或多個其餘的視圖控制器,它被稱做 容器視圖控制器。此外還有兩種常見的容器視圖控制器,Navigation Controller(導航控制器)和Split View Controller(分割視圖控制器)。

容器關係由分頁欄控制器和他所包含的視圖控制器之間的箭頭表示,以下圖這個箭頭上的圖標表示嵌入關係。

Container Relationship

注:若是你想一塊兒移動分頁欄控制器和附帶的視圖控制器的話,先縮小畫面,而後按住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 。

Set Initial View Controller

注:在Xcode 6.2中,上述選項已被控件取代。先選中當前分頁欄控制器,而後從對象庫裏把一個Storyboard Entry Point(Storyboard入口)拖上去,能夠拖到控制器上面,也能夠拖入文檔大綱。

Set Initial View Controller

如今,一開始的那個箭頭已經指向當前的分頁欄控制器了:

Is Initial View Controller

注:Xcode 6.2 beta在這裏可能會崩潰,若是出現問題,請選中該分頁欄控制器的某個視圖控制器,把入口拖上去,而後再把入口箭頭拖到分頁欄控制器上。

這意味着啓動App時, UIApplication 會把此分頁欄控制器做爲主畫面。運行App試一試,如今App下面有分頁標籤欄了,能夠用分頁標籤在兩個視圖控制器之間切換。

App-with-tabs

提示:你也能夠經過拖拽視圖控制器之間的箭頭來改變初始視圖控制器。

其實在前面你也能夠選用Xcode自帶的標籤分頁式App模板(即Tabbed Application模板)建立App,但最好仍是瞭解一下工做原理,之後有必要的時候也能手動建立分頁欄控制器。

注:若是在分頁欄控制器上鍊接超過5個場景,App在運行時會自動將其納入一個More分頁標籤,乾淨利落。

添加表視圖控制器

如今附屬於分頁欄控制器的兩個場景都是標準UIViewController實例,接下來你會把其中第一個分頁標籤對應的場景替換爲UITableViewController

在文檔大綱中點選第一個視圖控制器並將其刪除,而後從對象庫中把一個新的 Table View Controller(表視圖控制器)拖到原場景所在的地方。

Table view controller

接下來把表視圖控制器放到導航控制器(Navigation Controller)中,選中表視圖控制器,在Xcode菜單中選擇 EditorEmbed InNavigation Controller ,如今面板中又加入了另外一個控制器:

Navigation controller

你也能夠從對象庫中拖入導航控制器後再嵌入表視圖,但這個操做通常來說使用菜單命令會更省時。

與分頁欄控制器相似,導航控制器也是容器視圖控制器,因此有一個關係箭頭指向表視圖控制器,你也能夠在文檔大綱中看到這個關係:

Relationship arrow

注意,嵌入表視圖控制器後,Interface Builder自動給它添加了一個導航欄,由於當前視圖是在導航控制器的框架中顯示的。它並非實際存在的UINavigationBar對象,只是模擬顯示狀況。

打開表視圖控制器的屬性檢查器,上面能夠看到 Simulated Metrics(模擬度量)選項:

Simulated metrics

Storyboard中的默認值爲「Inferred(推斷)」,意思是該場景在處於導航控制器中時會顯示導航欄,處於分頁欄控制器中時會顯示分頁欄等等。你能夠修改這些設置,可是請記住,這只是方便你設計界面時參考的模擬顯示,並不會在運行時使用,僅僅是視覺設計的輔助工具,用來表示視圖最後應該是什麼樣子。

接下來把這兩個新場景鏈接到分頁欄控制器,按住control從分頁欄控制器拖到導航控制器,鬆手時會彈出一個小選單,選擇 Relationship Segue – view controllers 選項:

Relationship Segue

這會在兩個場景間新建一個關係箭頭,與分頁欄控制器包含控制器同樣,都是嵌入關係。

Relationship arrow

分頁欄控制器有兩個嵌入關係,分別對應兩個分頁標籤。導航控制器上有一個表視圖控制器的嵌入關係。

建立這個鏈接後,分頁欄控制器中會添加一個新分頁標籤,默認名稱爲「Item」。在這個App中,你但願第一個分頁標籤對應這個新場景,直接拖動分頁標籤,更改順序:

Drag tab items

運行App試試看,如今第一個分頁標籤中包含一個嵌入在導航控制器中的表視圖。

First tab with table view

在添加實際功能以前,你還須要再修整一下Storyboard,將第一個分頁標籤命名爲"Players",第二個命名爲"Gestures"。不是在分頁欄控制器上修改,而是在這些分頁標籤對應的視圖控制器上修改。

將一個視圖控制器鏈接到分頁欄控制器後,在場景下面和文檔大綱中會看到它被賦予的 分頁欄項(Tab Bar Item) 對象,能夠用來設置分頁標籤的標題和在分頁欄控制器中看到的圖標。

選中導航控制器中的分頁欄項,在屬性檢查器中將標題設爲Players:

Rename

以一樣的方法把第二個分頁標籤對應場景欄目更名爲Gestures。

一個設計精良的App應該爲分頁標籤附上圖標。教程資源中有個Image文件夾,把這個文件夾拖入項目,選擇「Copy items if needed」並點擊Finish:

Copy Resources

在Players分頁欄項的屬性檢查器中選擇圖片 Players.png 。

Players Image

你可能已經想到了,給Gestures選擇 Gestures.png 。

嵌入導航控制器的一個視圖控制器包含用於設置導航欄的 Navigation Item(導航項) 。在文檔大綱中選擇表視圖控制器的導航項,在屬性檢查器中把Title改爲Players。

或者你也能夠雙擊導航欄直接修改title,注意你須要雙擊的是表視圖控制器中的模擬導航欄,而不是導航控制器中的那個導航欄對象。

運行App,欣賞一下這漂亮的分頁標籤欄吧!一行代碼也不用寫哦!

App with tab bar images

原型表項(Prototype Cell)

原型表項容許你直接在Storyboard編輯器中爲表視圖設計自定義佈局。

表視圖控制器默認會帶一個空的原型表項。點擊它,在屬性檢查器中設置Style爲 Subtitle(副標題)。這會當即改變表項的外觀,使其包含兩個Label。

Storyboard上能夠堆疊不少內容,有時可能很難點擊到你想選中的東西。若是遇到困難,有幾種選擇:第一是在面板左側的文檔大綱中選擇,第二是快捷鍵(按住control+option+shift,點擊想選擇的區域後會彈出指針所指區域的全部元素),第三種選擇是Xcode 6的新功能,反覆點擊能夠在各層之間循環。

若是你以前用過表視圖,還手動建立過本身的表項,你可能會將其認做UITableViewCellStyle.Subtitle樣式。有了原型表項,你能夠像剛纔那樣選擇系統內建的樣式,也能夠自定義設計(咱們稍後就要建立了)。

設置Accessory(附件,即表項右側的附屬元素)屬性爲 Disclosure Indicator(展開方向標,即右鍵頭),並在 Identifier(標識符) 字段中輸入 PlayerCell。全部的原型表項仍然是標準UITableViewCell對象,因此它們須要一個以供重用的標識符。

Cell setup

運行應用……什麼都沒變。這沒什麼值得奇怪的,接下來你還須要爲這個表指定一個data source(數據源),這樣它纔會知道要顯示什麼。

在項目中添加一個新文件,選擇iOS/Source下的Cocoa Touch Class模板,命名爲 PlayersViewController ,並確保它是UITableViewController的子類。不要選中Also create XIB file選項,由於你已經在Storyboard中設計好了,今天不用nib!選擇Swift語言,點擊Next,而後點擊Create。

Players view controller

回到Storyboard,選擇表視圖控制器(確保你選擇的是視圖控制器而不是其中包含的某個視圖)。在身份檢查器(Identity inspector)中設置它的 Class 爲 PlayersViewController。這對於在Storyboard場景中使用自定義視圖控制器的子類很重要,由於若是你不這麼作,你的類就都不會被使用!

Custom class

此後運行App時Storyboard中加載的那個表視圖控制器就是PlayersViewController類的實例。

這個表視圖要顯示玩家列表,因此你須要爲App建立主要的數據模型:一個包含Player對象的數組。由iOS/Source下的Swift File模板添加新文件,命名爲Player。

在Player.swift中追加如下代碼:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import UIKit
  
class Player: NSObject {
   var  name: String
   var  game: String
   var  rating: Int
  
   init(name: String, game: String, rating: Int) {
     self.name = name
     self.game = game
     self.rating = rating
     super .init()
   }
}

沒什麼特別的東西,Player只是容器對象,其中包含三個屬性:玩家名稱,進行的遊戲,還有1到5星之間的評分。


接下來要建立一組Player測試對象,並在PlayersViewController中賦值到一個數組。請使用Swift File模板建立名爲SampleData的新文件,並在SampleData.swift中追加如下代碼:

1
2
3
4
5
//Set up sample data
  
let playersData = [ 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) ]

這裏定義了一個叫作playersData的常量,並把寫定的Player對象數組賦值給它。

如今在PlayersViewController.swift的class PlayersTableViewController: UITableViewController下面添加一個玩家數組屬性,用來保存玩家列表:

1
var  players: [Player] = playersData

這裏,你可能會在PlayersViewController中定義players變量時順帶就把示例數據準備好了,但之後數據可能源自plist或SQL文件,因此,在視圖控制器以外處理數據加載問題是明智之選。


如今你有一個包含多個Player對象的數組,能夠在PlayersViewController中綁定數據源了。仍是在PlayersViewController.swift中,用如下代碼替換表視圖數據源方法:

1
2
3
4
5
6
7
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
   return  1
}
  
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
   return  players.count
}

實際工做在cellForRowAtIndexPath中。用如下代碼替換方法(原來的註釋掉):

1
2
3
4
5
6
7
8
9
10
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
  -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier( "PlayerCell" , forIndexPath: indexPath)
     as UITableViewCell
  
   let player = players[indexPath.row] as Player
   cell.textLabel?.text = player.name
   cell.detailTextLabel?.text = player.game
   return  cell
}

dequeueReusableCellWithIdentifier(_:forIndexPath:)方法用來檢查是否存在可重用的表項。若是沒有,就返回一個自動分配的原型表項。你只須要提供以前在Storyboard編輯器中給原型表項設定的重用標識符,本例中對應PlayerCell。必定要設置標識符,不然沒法正常工做!

運行App,如今表視圖中有玩家項了!

App with players

只要寫幾行代碼就可使用原型表項,贊!

注:該App中只使用了一個原型表項,但若是你的列表須要顯示不一樣種類的表項,你能夠向Storyboard中另外添加原型表項。能夠複製現有的表項再進行修改,也能夠增大表視圖的Prototype Cells屬性值。記得每一個表項都要設置本身的重用標識符。

設計本身的原型表項

對不少App來講使用內建的標準表項樣式已經足夠了,但這個App須要在表項的右側添加一個顯示評分(1星到5星)的圖片。標準表項樣式不支持在這裏包含圖片視圖,因此你只能本身建立自定義設計。

切回Main.storyboard,選擇表視圖中的原型表項,在屬性檢查器中設置Style屬性爲Custom(自定義),隨後默認的Label不見了。

首先讓表項增高一些,拖動底邊上的小方塊或在尺寸檢查器(Size inspector)中修改Row Height(行高)值,設置表項高度爲55點(points)。

從Objects Library拖兩個Label到表項上,把它們放到和以前的標準樣式差很少的地方,你能夠在屬性檢查器中隨意設置字體和顏色。設置上面的Label文本爲「Name」,下面的爲「Game」。

把一個Image View(圖片視圖)拖到表項中,放在右面緊挨展開方向標的地方,設寬度爲81點,高度不是很重要。將其Mode設爲Center(在屬性檢查器的View下面),保證載入視圖的圖片不會被拉伸。

在尺寸檢查器中設Label寬度爲190點。Label不該蓋住Image View。原型表項的最終設計大概是這個樣子:

Custom cell design

由於這是一個自定義表項,因此不再能用 UITableViewCell中的textLabeldetailTextLabel屬性來設置文本了。這些屬性只在標準表項類型中有效,它們指向的label在該表項中已經不存在了。爲此,你須要用tag(標記)找到相應的label。

你也能夠選擇建立一個繼承UITableViewCell的自定義類幷包含對應表項視圖中的label的屬性。而tag能夠用來簡化工做,在簡單狀況下是很不錯的解決方案。不過本教程後面會嘗試使用自定義類的方法。

在屬性檢查器中設置「Name」Label的tag值爲100,「Game」Label爲101,Image View爲102.

打開PlayersViewController.swift,在後面以下添加新方法imageForRating

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
func imageForRating(rating:Int) -> UIImage? {
   switch  rating {
   case  1:
     return  UIImage(named:  "1StarSmall" )
   case  2:
     return  UIImage(named:  "2StarsSmall" )
   case  3:
     return  UIImage(named:  "3StarsSmall" )
   case  4:
     return  UIImage(named:  "4StarsSmall" )
   case  5:
     return  UIImage(named:  "5StarsSmall" )
   default :
     return  nil
   }
}

很簡單,該方法根據評分返回不一樣的星級圖片。依然在PlayersViewController中,以下修改tableView(_:cellForRowAtIndexPath:)方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier( "PlayerCell" , forIndexPath: indexPath) as UITableViewCell  //1
  
   let player = players[indexPath.row] as Player  //2
  
   if  let nameLabel = cell.viewWithTag(100) as? UILabel {  //3
     nameLabel.text = player.name
   }
   if  let gameLabel = cell.viewWithTag(101) as? UILabel {
     gameLabel.text = player.game
   }
   if  let ratingImageView = cell.viewWithTag(102) as? UIImageView {
     ratingImageView.image = self.imageForRating(player.rating)
   }
   return  cell
}

講解一下剛纔作的工做:

  1. dequeueReusableCellWithIdentifier在回收表項可重用的狀況下會抽出重用標識符爲PlayerCell的表項,不然建立一個新表項。

  2. 按行號查看Player對象並將其賦值給player

  3. 按表項上的tag找到label和圖片,並參照player對象填充數據。

應該能夠了。如今再次運行App,大概會像這樣:

Wrong table cell height

嗯,看起來不大對勁,表項都重疊在一塊兒了。你只修改了原型表項的高度,可是並無把表視圖考慮進去。這裏有兩個解決方案,一是改變表視圖的Row Height屬性,二是實現tableView(tableView:heightForRowAtIndexPath:)方法。本例中前者更合適,由於只有一種表項,並且咱們已經事先了解表項的高度。

注:若是沒法事先斷定表項的高度,或者各行的高度可能不一致,可使用tableView(tableView:heightForRowAtIndexPath:)方法。

回到Main.storyboard,在表視圖的尺寸檢查器中設Row Height爲55點:

Right table cell height

如今運行,看起來好多了!

App with proper row height

哦,還有一點,若是以前修改表項高度時沒有手動輸入數據,而是拖動表項邊上的小方塊的話,表視圖的行高屬性也會自動隨之改變。因此在構建過程當中你可能並沒碰到上述問題。

使用表項的子類

這個表視圖用起來已經至關不錯了,但我不大喜歡用tag來獲取原型表項的子視圖。若是可能的話,把這些label於outlet鏈接並使用相應屬性要優雅得多。事實是可行的。

在項目中以Cocoa Touch Class模板添加一個新文件,命名爲PlayerCell並令其繼承UITableViewCell。不要選中建立XIB的選項,由於Storyboard裏已經有表項了。

在PlayerCell類的類定義下面添加如下屬性

1
2
3
@IBOutlet weak  var  gameLabel: UILabel!
@IBOutlet weak  var  nameLabel: UILabel!
@IBOutlet weak  var  ratingImageView: UIImageView!

這些變量都是IBOutlet,它們能夠在Storyboard中與場景創建鏈接。


回到Main.storyboard,選中原型表項PlayerCell,並在身份檢查器中把它的class改爲PlayerCell。如今每當經過dequeueReusableCellWithIdentifier(_:forIndexPath:)向表視圖請求一個新表項時,它會返回PlayerCell實例而不是UITableViewCell。

注意:這裏咱們把類名跟重用標識符設置成同樣了,都是PlayerCell。這只是由於我的喜歡保持一致,類名跟重用標識符絕不相干,若是你願意,也能夠起不一樣的名字。

下面令label以及image view與outlet鏈接。在Storyboard中切到鏈接檢查器(Connections inspector),而後在面板或文檔大綱中選擇Player Cell,把鏈接檢查器中的nameLabel outlet拖到Name label對象上。對gameLabel和ratingImageView執行一樣操做。

Connect outlet

重點:控件要鏈接的是表項,而不是視圖控制器!當你的數據源向表視圖經過dequeueReusableCellWithIdentifier索求一個新表項的時候,表視圖並非把原型表項交給你,而是複製一份給你(或是以前被歸入回收空間的一個已有表項)。

這就意味着在同一時間不止有一個PlayerCell的實例,若是把表項中的label鏈接到了視圖控制器的outlet上,不一樣的label拷貝會試圖使用同一個outlet,這是自找麻煩。(另外一方面,把原型表項鍊接到視圖控制器的action上是可行的,當你的表項中含有自定義按鈕或者是其餘UIControl時可能會用到。)

除使用鏈接檢查器以外,你也能夠按住control從PlayerCell拖到控件上,而後在彈出的選單中選擇outlet名稱。

IBOutlet connection

如今已經綁定屬性,能夠稍微簡化數據源的代碼。在PlayersViewController中以下修改tableView(_:cellForRowAtIndexPath:)方法:

1
2
3
4
5
6
7
8
9
10
11
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath)
  -> UITableViewCell {
   let cell = tableView.dequeueReusableCellWithIdentifier( "PlayerCell" , forIndexPath: indexPath)
     as PlayerCell
  
   let player = players[indexPath.row] as Player
   cell.nameLabel.text = player.name
   cell.gameLabel.text = player.game
   cell.ratingImageView.image = imageForRating(player.rating)
   return  cell
}

這就更像樣了。把從dequeueReusableCellWithIdentifier接收的對象轉爲一個PlayerCell,而後就可使用鏈接到label和圖片視圖的屬性。這樣使用原型表項,表視圖不像之前那麼亂了。

運行App試試看。看起來應該和以前同樣,但在幕後,如今使用的已是你本身的表項子類了!

相關文章
相關標籤/搜索