本章將會實現對FoodTracker APP的評級控制,當你完成時,你的APP看起來像這樣:編程
學習目標swift
在課程結束時,你將可以:數組
建立並關聯自定義源代碼文件和在storyboard中的元素
定義一個自定義類
在實現自定義類的初始化
使用的UIView做爲容器
瞭解如何以編程方式顯示views app
建立一個自定義Viewless
爲了能評級一個菜譜,用戶須要一個控制,讓他們能選擇給想要菜譜多少星星數量。有許多方法實現這個,但咱們會專一於涉及建立一個自定義view,用過在代碼中定義,並使用storyboard。實現的效果以下:ide
評級控件將讓用戶爲一個菜譜選擇0-5個星星。當用戶點擊一個星星時,全部被填充的星星就是目前的星星數。填充的星星數量就是評級的數量,空心的星星就不是。爲了開始設置這個UI,交互和控制行爲,咱們要建立一個UIView的子類。函數
建立UIView的子類步驟以下:工具
1.選擇File>New>File(或Command+N)佈局
2.在對話框的左邊,選擇iOS下方的Source學習
3.選擇Cocoa Touch Class,而後點擊下一步
4.在Class標籤後,輸入RatingControl
5.Subclass of標籤後,選擇UIView
6.肯定語言選擇的是Swift
7.點擊Next。
保存的位置是默認項目目錄,Group選擇默認的是你APP的名字,FoodTracker
在Targets字段,你的APP是被選擇的,App的Test是未選擇的
8.保留這些默認值,而後點擊Create
Xcode會建立一個RatingControl類的文件:RatingControl.swift,
RatingControl是一個自定義的UIView的子類
9.在RatingControl.swift中,刪除註釋,來到類中,就像一個白板同樣
import UIKit class RatingControl: UIView { }
你一般建立一個view有兩種方式,一種是經過View的初始化frame來手動添加View到你的UI中,另外一種是容許view,在storyboard中加載。每個方法對應一個初始程序:對於第一種,是使用init(frame:),第二種,使用init(coder:)。回想一下initializer方法,用於準備一個類的實例,它涉及到爲每一個屬性設置初始值並執行一些其餘設置。
由於咱們這裏會在storyboard中使用init方法,因此咱們覆蓋子類init(coder:)的實現,
下面是覆蓋初始化程序的步驟
1.在RatingControl.swift中的class下面,添加註釋
// MARK: Initialization
2.在註釋的下方,輸入init。代碼完成功能會出現
3.選擇第二個方法,即init(coder:),而後按下Return
init(coder aDecoder: NSCoder!) {
}
4.你會發現有個錯誤,但能修復它,會自動導入required關鍵字
每一個實現了initializer的UIView的子類,必須包含一個init(coder:)的實現。Swift編譯器知道這個,並提供自動修復工具,來改變你的代碼,對於你代碼中的錯誤,它提供一個潛在的解決方法
5.在init方法中,添加父類的初始化程序
super.init(coder: aDecoder)
最終 init(coder:)的方法,以下
required init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
顯示自定義View
爲了顯示你的自定義View,你須要添加一個View到你的UI中並在view的代碼中創建一個鏈接。顯示View的步驟以下:
1.打開你的storyboard
2.在storyboard中,使用Object library找到View對象,並拖動到storyboard場景中,它在image view的下方
3.選中View,打開Size inspector
4.在Intrinsic Size標籤旁,選擇Placeholder
5.在Intrinsic Size內輸入44的Height 和240的Width,按下Return,而後界面以下:
6.在View選中的狀況下,選擇Identity inspector
7.在Identity inspector中,找到Class 標籤,選擇RatingControl
添加按鈕到View中
此刻,你獲取到了自定義的UIView子類,名爲RatingControl。接下來咱們須要爲這個View添加按鈕,來容許用戶選擇一個評級。先從簡單的開始,獲取一個紅色的按鈕顯示在你的view中。步驟以下:
1.在init(coder:)內,添加如下代碼來建立一個紅色的按鈕
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor()
你使用的是redColor(),因此按鈕爲紅色。若是你喜歡,你能夠改爲其餘顏色如blueColor()
或者greenColor()
2.接着添加下一行
addSubview(button)
addSubview()表示,把Button添加到你建立的RatingControl
View中
你的 init(coder:)完整代碼,應該以下所示:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() addSubview(button) }
檢查站:執行你的APP,你應該能看到一個View中有一個紅色的正方形。這個紅色的正方形就是你添加的按鈕
你須要這個按鈕,最終還有其餘按鈕,在View中執行點擊動做。這個動做你用來改變菜譜的評級。
下面讓咱們添加一個動做到按鈕中
1.在RatingControl.swift類中大括號}上方,添加以下代碼:
// MARK: Button Action
2.在註釋下方添加以下代碼:
func ratingButtonTapped(button: UIButton) { print("Button pressed ") }
如今,咱們使用 print()函數來檢查ratingButtonTapped動做是否如預期那樣連接到按鈕上。這個函數打印一個消息,會輸出在Xcode的控制檯。控制檯是一個有效的調試機制。
3.找到init(coder:)
initializer:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() addSubview(button) }
4.在 addSubview(button)上方,添加以下代碼:
button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
你已經熟悉目標 - 動做( target-action )模式了,由於你已經屢次用它在storyboard中連接代碼和元素的動做方法。上面,咱們在作一樣的事情,只不過你在代碼中建立鏈接。你要附加的動做ratingButtonTapped:到button
對象,每當.TouchDown事件發生時將被觸發。這個事件代表用戶在按鈕中已按下。設置目標self,在這種狀況下是RatingControl類,由於的動做定義在這裏。
須要注意的是,由於你沒有使用界面生成器,你不須要定義你的操做方法爲IBAction屬性;你就像定義其餘方法同樣定義動做。
最終代碼看起來以下:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) addSubview(button) }
檢查站:運行你的應用程序。當你單擊紅色正方形,你應該在控制檯中看到了「Button pressed」的消息。
是時候想一想爲了展現評級,RatingControl類須要一些什麼信息了。你須要保持並記錄評級的值0-5,也就是用戶點擊來設置的評級按鈕。你可使用Int來展現評級的值,而且這些按鈕做爲UIButton對象數組
添加評級屬性
1.RatingControl.swift中
, 找到class聲明的這行:
class RatingControl: UIView {
2.在這行代碼下面,添加以下代碼:
// MARK: Properties var rating = 0 var ratingButtons = [UIButton]()
此時,你在View中有一個按鈕,但你須要5個這樣的按鈕。爲了建立一整個按鈕集,咱們使用for-in循環。一個for-in循環遍歷一個序列,如數字範圍,屢次執行一組代碼。如今咱們就用它來建立一個按鈕,循環建立五個。
建立5個按鈕
1.在RatingControl.swift中,找到init(coder:):
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) addSubview(button) }
2.添加for-in循環代碼
for _ in 0..<5 { let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) addSubview(button) }
你能夠全選他們,而後按下Control+I來縮進。(..<)這個Swift語法中的操做符,不會包含<後的數字,也就是說循環(0..<5)就是0,1,2,3,4。你可使用下劃線(_)表示通配符,意思是你不須要知道當前執行的循環迭代。
3.在addSubview(button)上方添加以下代碼
:
ratingButtons += [button]
你建立的每一個按鈕,須要把它存放在ratingButtons數組中,由於咱們將要用它它們。
完整的init(coder:)函數,代碼以下:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) for _ in 0..<5 { let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
檢查站:運行你的應用程序。你會注意到它看起來像有隻有一個按鈕。這是由於for
-in
循環只是堆疊在彼此頂部的按鈕。你須要調整按鈕的位置佈局。
佈局方法名爲layoutSubviews的方法,這個方法在UIView類中已定義。layoutSubviews會由系統在合適的時候調用,給UIView的子類一個能夠自行實現準確佈局的地方。你須要在重寫這個方法來把按鈕放置在合適的地方
寫佈局按鈕的代碼
1.在RatingControl.swift中的init(coder:)函數下方,添加一個方法
override func layoutSubviews() { }
你可使用代碼完成功能迅速添加方法
2.在方法中,添加以下代碼
var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44) // Offset each button's origin by the length of the button plus spacing. for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (44 + 5)) button.frame = buttonFrame }
這個代碼建立了一個框,使用for-in循環遍歷全部的框(frame),enumerate()方法返回一個集合,包含ratingButtons數組中的元素和索引。這個集合包含一個元組,每一個元組包含一個索引和一個按鈕。正好咱們須要用到索引來計算按鈕的位置。你的layoutSubviews方法應該以下所示:
override func layoutSubviews() { var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44) // Offset each button's origin by the length of the button plus spacing. for (index, button) in enumerate(ratingButtons) { buttonFrame.origin.x = CGFloat(index * (44 + 5)) button.frame = buttonFrame } }
檢查站:運行你的應用程序。如今,按鈕應該是並排的了。點擊任何按鈕,能夠在控制檯收到信息。
聲明一個常量表示按鈕大小
注意咱們使用了44這個值在代碼中,這通常來講是很差的作好,咱們使用了硬編碼。若是你想要一個稍微大點的按鈕,你就必須在每一個44出現的地方去修改,這樣很麻煩,相反咱們使用一個常量,來表示按鈕的大小,這樣其餘地方引用這個常量,咱們要修改按鈕大小的時候,只須要修改這個常量便可。如今咱們能夠經過檢索容器View的高度,來調整我按鈕的大小。
聲明一個常量爲保存按鈕的尺寸
1.在layoutSubviews()方法中,添加以下代碼:
// Set the button's width and height to a square the size of the frame's height. let buttonSize = Int(frame.size.height)
這使的佈局更靈活
2.改變方法中,把44變成buttonSize:
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) // Offset each button's origin by the length of the button plus spacing. for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame }
3.在init(coder:)初始化函數中,改變循環中的第一行let button = UIButton()
let button = UIButton()
由於咱們layoutSubviews()方法中設置了按鈕的frames ,因此這咱們你再也不須要在建立按鈕時,進行設置尺寸。
你的layoutSubviews()方法如今看起來應該是這樣:
override func layoutSubviews() { // Set the button's width and height to a square the size of the frame's height. let buttonSize = Int(frame.size.height) var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) // Offset each button's origin by the length of the button plus some spacing. for (index, button) in enumerate(ratingButtons) { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } }
init(coder:)方法中,如今看起來應該是這樣:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) for _ in 0..<5 { let button = UIButton() button.backgroundColor = UIColor.redColor() button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
檢查點:運行你的應用程序。一切都應該和之前同樣工做。按鈕應該是並排的。點擊任何按鈕,能夠在控制檯收到信息。
添加Star圖像到Buttons中
接下來咱們將添加星星到按鈕中
上面圖片,你能夠直接右鍵下載
添加圖片到項目中
1.打開項目導航,選擇Images.xcassets
,這個目錄,這一系列的操做,在上一章咱們已經作過了,你是否還記得呢?
2.在底部的左下角,點擊(+)按鈕,而後選擇New Folder(上一章是直接選擇New Image Set,由於咱們這裏有兩個星星圖片,因此咱們使用文件夾,方便分類)
3.雙擊這個文件夾名字而後重命名爲Rating Images
4.選擇文件夾,點擊(+)選擇New Image Set。
5.雙擊image而後重命名爲emptyStar
6.在電腦中,選擇空的星星圖片。
7.拖動到2x槽中
8.重複4-7步驟,把emptyStar換成filledStar,拖動填充的星星圖片到2x槽中
最後你的asset目錄應該以下所示:
下一步,咱們會在適當的時間,經過寫代碼來設置Button的圖像
爲按鈕設置圖片
1.打開RatingControl.swift
2.在init(coder:)中,循環以前,添加以下代碼:
let filledStarImage = UIImage(named: "filledStar") let emptyStarImage = UIImage(named: "emptyStar")
3.在循環中的最後一行添加以下代碼:
button.setImage(emptyStarImage, forState: .Normal)
button.setImage(filledStarImage, forState: .Selected)
button.setImage(filledStarImage, forState: [.Highlighted, .Selected])
根據不一樣的狀態,你設置兩個不一樣的圖像,因此你能夠看到,當按鈕被選中。當按鈕處於未選中狀態(Normal
)空星星的圖像出現 。當按鈕處於選中狀態(Selected狀態),實星星的圖像出現。當用戶是在敲擊按鈕時,按鈕突出顯示(.Selected和.Highlighted狀態)。
4.刪除設置背景色爲紅色的,這行代碼
button.backgroundColor = UIColor.redColor()
5.添加如下這行代碼:
button.adjustsImageWhenHighlighted = false
這是爲了確保圖像不會在狀態變化過程當中顯示高亮。
如今的init(coder:)函數看起來應該以下:
required init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) let emptyStarImage = UIImage(named: "emptyStar") let filledStarImage = UIImage(named: "filledStar") for _ in 0..<5 { let button = UIButton() button.setImage(emptyStarImage, forState: .Normal) button.setImage(filledStarImage, forState: .Selected) button.setImage(filledStarImage, forState: [.Highlighted, .Selected]) button.adjustsImageWhenHighlighted = false button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
檢查站:運行你的應用程序。您應該看到星星,而不是紅色的按鈕。但你的按鈕不改變圖像呢。咱們下面會解決這個問題。
實現按鈕的動做
用戶須要可以經過點擊星星來選擇一個評級,因此咱們須要更換先前ratingButtonTapped()方法中的內容。
實現評級動做
1.RatingControl.swift中
,找到ratingButtonTapped(_:)方法
:
func ratingButtonTapped(button: UIButton) { print("Button pressed 👍") }
2.替換print語句爲下面的代碼:
rating = ratingButtons.indexOf(button)! + 1
indexOf(_:)方法嘗試找到按鈕數組中已選擇的按鈕,而後返回這個按鈕的索引。這個方法返回可選的Int,由於搜索的實例可能不存在於你的集合中。然而,由於僅僅只在會你建立並添加到數組中的一個按鈕纔會觸發動做,你能搜索按鈕並返回有效的索引。在這種狀況下你能使用強制解包操做符(!)來訪問潛在的索引值。你索引的值加1表示這個評級。由於數組下標是從0(0-4)開始的,咱們須要評級爲1-5。
3.在RatingControl.swift中,(})前面,添加以下代碼
func updateButtonSelectionStates() {
}
這個幫助方法,你將用於和更新按鈕的選擇狀態
4.在updateButtonSelectionStates()方法內,添加以下for-in循環:
for (index, button) in enumerate(ratingButtons) { // If the index of a button is less than the rating, that button should be selected. button.selected = index < rating }
這段代碼經過按鈕數組來遍歷設置每個按鈕的狀態,根據數組中的索引是否小於評級來判斷按鈕是否選中。若是index < ratin的值爲true,那麼這個按鈕的狀態就是已選擇的,同時使之顯示爲填充的星星圖片。不然,其餘的按鈕爲爲選中狀態,顯示爲空心的星星。若是你的Swift版本找不到indexOf方法,那麼給你一個提示使用一個全局函數(尋找)能夠解決這個問題
5.在ratingButtonTapped()方法中,添加一個調用方法爲updateButtonSelectionStates(),在方法實現的最後一行
func ratingButtonTapped(button: UIButton) { rating = ratingButtons.indexOf(button)! + 1 updateButtonSelectionStates() }
6.在layoutSubviews方法()中,也添加updateButtonSelectionStates()方法,到方法實現的最後一行
override func layoutSubviews() { // Set the button's width and height to a square the size of the frame's height. let buttonSize = Int(frame.size.height) var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) // Offset each button's origin by the length of the button plus some spacing. for (index, button) in enumerate(ratingButtons) { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } updateButtonSelectionStates() }
當載入view時,更新按鈕的選擇狀態很重要,不僅是當評級改變時。
7.在 // MARK: Properties
,找到rating
屬性
var rating = 0
8.更新rating屬性,包含這個觀察者:
var rating = 0 { didSet { setNeedsLayout() } }
一個屬性觀察者,用來觀察和響應屬性值的變化。一個屬性的值每一次被設置時,屬性觀察者會被調用,可用於值改變先後當即執行一些工做。didSet屬性觀察者是在屬性值被設置後哦當即調用。在這裏,咱們調用了setNeedsLayout()方法,表示每次評級改變時,出發一個佈局更新。確保UI會一直準確的顯示評級屬性。
如今咱們的updateButtonSelectionStates()看起來應該是這樣:
func updateButtonSelectionStates() { for (index, button) in enumerate(ratingButtons) { // If the index of a button is less than the rating, that button shouldn't be selected. button.selected = index < rating } }
檢查站:運行你的應用程序。您應該看到五顆星,並能點擊一個改變評級。點擊第三顆星的評級更改成3,如。
添加星星的間距和數量屬性
確保你不會有任何的硬編碼,建立評級星星的數量和間距屬性。這樣,若是你須要改變這些值,你只須要在一個地方修改
使評級控件的屬性,來界面構造器中可查
1.在 RatingControl.swift中,找到
// MARK: Properties
// MARK: Properties var rating = 0 var ratingButtons = [UIButton]()
2.在已存在的屬性下方,添加以下代碼:
var spacing = 5
這個屬性是用於你按鈕的間距
3.在layoutSubviews中,替換原來間距的常量,改成使用spacing屬性:
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
4.在spacing屬性的下方,添加另外一個屬性:
var stars = 5
這個屬性用來控制你顯示星星的數量
5.在 init(coder:)中替換先前的代碼:
for _ in 0..<stars {
檢查點:運行你的應用程序。一切都應該看起來跟之前徹底同樣。
鏈接Rating Control到View Controller
咱們最後須要作的事情就是爲rating control,在ViewController中
設置一個引用
鏈接到一個rating control outlet到ViewController.swift
1.打開你的storyboard
2.打開assistant editor
3.選擇rating control
4.按住Control從畫布中拖動rating control到代碼中,而後在photoImageView
下方,鬆開
5.在彈出的對話框中,Name字段旁輸入ratingControl。其餘不變,而後點擊Connect
清理項目
你即將完成菜譜的場景UI,但首先你須要作一些清理工做。相比先前的章節,如今,FoodTracker應用程序實現更先進的特性和不一樣的用戶界面,你要刪除你不須要的部分。你還可讓元素居中,以平衡的UI元素。
清理UI
1.返回到standard editor
2.打開你的storyboard
3.選擇 Set Default Label Text按鈕,而後按下Delete鍵刪除它。
4.選擇Select View(若是你不是在Xcode7下,沒有Stack View的,後面能夠略過不看)
5.打開Attributes inspector
6.在Attributes inspector,找到Alignment字段,而後選擇Center,stack view會水平居中
下面咱們須要移除按鈕點擊響應的動做方法
清理代碼
1.打開ViewController.swift
2.在ViewController.swift中,刪除setDefaultLabelText()方法
後面的章節咱們會修改mealNameLabel的outlet
檢查站:運行你的應用程序。一切都應該像之前同樣工做,但設置默認標籤文本按鈕消失了,而元素水平居中。點擊任何按鈕,依然會有消息顯示在控制檯。