重要:這是針對(duì)于正在開發(fā)中的API或技術(shù)的預(yù)備文檔(預(yù)發(fā)布版本)。蘋果提供這份文檔的目的是幫助你按照文中描述的方式對(duì)技術(shù)的選擇及界面的設(shè)計(jì)開發(fā)進(jìn)行規(guī)劃。這些信息有可能發(fā)生變化蔑匣,因此根據(jù)本文檔的軟件開發(fā)應(yīng)當(dāng)基于最終版本的操作系統(tǒng)和文檔進(jìn)行測(cè)試。該文檔的新版本或許會(huì)隨著API或相關(guān)技術(shù)未來(lái)的發(fā)展而進(jìn)行更新。
翻譯自蘋果官網(wǎng):
在本課中潮剪,為 app 實(shí)現(xiàn)評(píng)分功能。完成的樣子如下:
[圖片上傳失敗...(image-ed9a4d-1608214851935)]
學(xué)習(xí)目標(biāo):
課程的最后分唾,你講能夠:
- 創(chuàng)建自定義源代碼文件并與 storyboard 中控件關(guān)聯(lián)
- 定義自定義類
- 實(shí)現(xiàn)自定義類的構(gòu)造器
- 使用 UIView 作為容器
- 理解如何在程序中顯示視圖
創(chuàng)建自定義類
我們需要一個(gè)控件來(lái)讓用戶為美食評(píng)分抗碰。雖然有多種實(shí)現(xiàn)方式,但是本節(jié)重點(diǎn)是使用自定義類的方式绽乔。
這是你即將要實(shí)現(xiàn)的評(píng)分控件:
[圖片上傳失敗...(image-5a7bae-1608214851936)]
評(píng)分控件讓用戶為美食選擇 0弧蝇、1、2、3看疗、4或5分沙峻。當(dāng)用戶點(diǎn)擊星星,在所點(diǎn)星星左邊的所有星星都將被填充顏色两芳。計(jì)算填充的星星數(shù)目作為評(píng)分摔寨,而空的星星不算。
通過(guò)創(chuàng)建一個(gè) UIView 的子類來(lái)構(gòu)建控件的界面怖辆、交互和行為是复。
創(chuàng)建 UIView 的子類
選擇
File > New > File
(或按 Command-N)。選擇
Cocoa Touch Class
然后點(diǎn)擊 Next竖螃。在 Class 區(qū)域淑廊,輸入 RatingControl。
在
SubClass of
區(qū)域特咆,選擇 UIView季惩。-
確保語(yǔ)言設(shè)置成了 Swift。
[圖片上傳失敗...(image-781de4-1608214851936)]
點(diǎn)擊 Next腻格。
默認(rèn)會(huì)存到你的項(xiàng)目目錄中画拾。
Group 選項(xiàng)默認(rèn)是你的 app 名字,F(xiàn)oodTracker荒叶。
在 Targets 區(qū)域碾阁,app 選項(xiàng)是選中的而 tests 未選中。其他都按照默認(rèn)的來(lái)些楣,點(diǎn)擊 Create脂凶。
Xcode 創(chuàng)建了 RatingControl.swift 文件。RatingControl 是 UIView 的子類愁茁。-
刪除文件中模板自動(dòng)添加的注釋以便輕裝上陣蚕钦。
import UIKit class RatingControl: UIView { }
有兩種典型的方式創(chuàng)建視圖:第一種通過(guò) frame 初始化視圖這樣可以手動(dòng)添加視圖到你的界面中,或者通過(guò) storyboard 加載視圖鹅很。它們都有對(duì)應(yīng)的構(gòu)造方法:frame 的是 init(frame:) 而 storyboard 對(duì)應(yīng) int?(coder:)方法嘶居。
由于使用 storyboard 來(lái)加載 view,所以一開始請(qǐng)覆寫父類的 init?(coder:)
構(gòu)造方法促煮。
覆寫構(gòu)造方法
-
在 RatingControl.swift 的 class 行下面邮屁,添加如下注釋。
// MARK: Initialization
在注釋下面菠齿,輸入 init佑吝。
彈出代碼提示框。
[圖片上傳失敗...(image-d1c2fd-1608214851936)]-
選擇列表的第三個(gè) init?(coder:) 構(gòu)造方法绳匀,回車確認(rèn)芋忿。
init?(coder aDecoder: NSCoder) { }
Xcode 為你生成基本的構(gòu)造方法炸客。
-
點(diǎn)擊紅色圓圈添加 required 關(guān)鍵字來(lái)修復(fù)錯(cuò)誤。
required init?(coder aDecoder: NSCoder) { }
每個(gè) UIView 子類實(shí)現(xiàn)自定義構(gòu)造方法同時(shí)必須實(shí)現(xiàn) init?(coder:)戈钢。
Swift 編譯器知道這個(gè)規(guī)則痹仙,所以提供了 fix-it 功能為錯(cuò)誤提供潛在的解決方案。 -
在子類的構(gòu)造器中添加如下行殉了。
super.init(coder: aDecoder)
init?(coder:) 構(gòu)造方法應(yīng)該像這樣:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
顯示自定義視圖
為了顯示自定義視圖开仰,需要向界面添加視圖并確保視圖關(guān)聯(lián)了 RatingControl 類。
顯示視圖
打開 storyboard宣渗。
在 storyboard 中抖所,使用對(duì)象庫(kù)找到 View 對(duì)象并拖到堆棧視圖中 image view 的下面。
-
選中視圖痕囱,在實(shí)用工具區(qū)打開尺寸檢查器。
[圖片上傳失敗...(image-71bf43-1608214851936)]
在 Intrinsic Size 區(qū)域的彈出菜單中暴匠,選擇 Placeholder鞍恢。
-
在 Instrinsic Size 下面的 Height 區(qū)域輸入 44 ,Width 區(qū)域輸入 240 每窖“锏簦回車確認(rèn),界面最后應(yīng)該像這樣:
[圖片上傳失敗...(image-c76766-1608214851936)]
-
選中視圖窒典,打開識(shí)別檢查器(Identity inspector)蟆炊。
[圖片上傳失敗...(image-d3e5ed-1608214851936)]
-
在識(shí)別檢查器中,找到名為 Class 的區(qū)域選擇 RatingControl瀑志。
[圖片上傳失敗...(image-50b30f-1608214851936)]
添加按鈕到視圖
下一步是添加按鈕到視圖來(lái)允許用戶選擇評(píng)分涩搓。
步驟:
-
在 init?(coder:) 構(gòu)造方法中,添加如下代碼行來(lái)創(chuàng)建紅色按鈕:
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor()
使用 redColor() 更容易讓你看到視圖的樣子劈猪。如果你喜歡可以換成其他 UIColor 的值昧甘,如 blueColor() 和 greenColor()。
-
在方法最后添加如下代碼:
addSubview(button)
addSubView() 方法向 RatingControl 添加剛才創(chuàng)建的按鈕战得。
init?(coder:)
構(gòu)造方法應(yīng)該像這樣:
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)
}
為了告訴堆棧視圖如何布局控件充边,需要提供固有內(nèi)容尺寸。如下面這樣覆寫 intrinsicContentSize
方法常侦。
override func intrinsicContentSize() -> CGSize {
return CGSize(width: 240, height: 44)
}
檢驗(yàn):運(yùn)行 app浇冰。應(yīng)該能夠看到視圖中有個(gè)小紅色正方形。它是剛才在構(gòu)造方法中添加的按鈕聋亡。
[圖片上傳失敗...(image-4360ea-1608214851936)]
給按鈕添加動(dòng)作
-
在 RatingController.swift 最后 } 的前面添加這行注釋:
// MARK: Button Action
-
在注釋下面添加如下代碼:
func ratingButtonTapped(button: UIButton) { print("Button pressed ??") }
使用 print() 方法檢查 ratingButtonTapped(_:) 動(dòng)作連接了預(yù)期的按鈕肘习。這個(gè)函數(shù)打印消息到 Xcode 調(diào)試控制臺(tái)中,而控制臺(tái)是在編輯區(qū)底部非常有用的調(diào)試工具杀捻。
稍后會(huì)用真實(shí)實(shí)現(xiàn)來(lái)替代這行調(diào)試代碼井厌。 -
找到 init?(coder:) 構(gòu)造方法:
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) }
-
在 addSubView(button) 行下面蚓庭,添加如下代碼:
button.addTarget(self, action: "ratingButtonTapped:", forControlEvents: .TouchDown)
你應(yīng)該很熟悉 target-action 設(shè)計(jì)模式因?yàn)橹耙呀?jīng)使用它連接過(guò)控件和方法。現(xiàn)在讓我們?cè)俅蝿?chuàng)建連接仅仆。連接 ratingButtonTapped: 動(dòng)作和按鈕對(duì)象器赞,當(dāng)用戶按下了按鈕,會(huì)觸發(fā)這個(gè)方法墓拜。
注意因?yàn)槭褂昧私缑鏄?gòu)造器(Interface Builder)港柜,就可以像普通方法一樣定義,而不需要使用 IBAction 屬性定義方法咳榜。
最后的 init?(coder:) 構(gòu)造方法應(yīng)該像這樣:
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)
}
檢驗(yàn):運(yùn)行 app夏醉。當(dāng)點(diǎn)擊了紅色正方形,應(yīng)該看到控制臺(tái)打印了 Button Pressed涌韩。
[圖片上傳失敗...(image-cb94a3-1608214851936)]
使用整形變量來(lái)表示評(píng)分值畔柔,它的范圍是0到5,使用數(shù)組來(lái)存放所有的按鈕臣樱。
添加評(píng)分屬性
-
在 RatingControl.swift 中靶擦,找到類定義行:
class RatingControl: UIView {
-
在行的下面,添加如下代碼:
// MARK: Properties var rating = 0 var ratingButtons = [UIButton]()
現(xiàn)在只有一個(gè)按鈕雇毫,但我們總共需要5個(gè)玄捕。使用 for-in 循環(huán)遍歷 sequence 來(lái)創(chuàng)建五個(gè)按鈕。
創(chuàng)建五個(gè)按鈕
-
在 RatingControl.swift 中找到 init?(coder:) 構(gòu)造方法:
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) }
-
在最后四行添加 for-in 循環(huán)棚放,像這樣:
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 確保 for-in 中的行縮進(jìn)正確枚粘。
半閉區(qū)間運(yùn)算符(..<) 不包括最大的數(shù),所以范圍是從 0 到 4 五次循環(huán)來(lái)添加五個(gè)按鈕飘蚯。當(dāng)不需要知道當(dāng)前循環(huán)變量使用通配符 _馍迄。
-
在 addSubView(button) 行上面,添加如下代碼:
ratingButtons += [button]
添加每個(gè)按鈕到 ratingButtons 數(shù)組孝冒。
你的 init?(coder:) 構(gòu)造方法最后應(yīng)該像這樣:
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)
}
}
檢驗(yàn):運(yùn)行 app柬姚。還是只看到了一個(gè)按鈕。因?yàn)?for-in 循環(huán)僅僅把各個(gè)按鈕堆疊到一起庄涡。需要調(diào)整這些按鈕的布局量承。
[圖片上傳失敗...(image-7a228e-1608214851936)]
調(diào)整按鈕的布局
-
在 RatingControl.swift 的
// MARK: Initialization
區(qū)域添加如下方法: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 ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (44 + 5)) button.frame = buttonFrame }
使用 for-in 循環(huán)遍歷按鈕來(lái)設(shè)置它們的 frames撕捍。
enumerate() 方法返回 ratingButtons 中所有控件的集合。集合中每個(gè)元組包含一個(gè) index 和 button泣洞,分別代表遍歷的下標(biāo)和按鈕忧风。使用 index 計(jì)算新的 frame 并設(shè)置給對(duì)應(yīng)的按鈕。frame 的 x 值等于 44 點(diǎn)的標(biāo)準(zhǔn)按鈕大小加上 5 點(diǎn)的空隙然后乘以 index 球凰。
最后的 layoutSubviews() 方法應(yīng)該像這樣:
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 ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (44 + 5)) button.frame = buttonFrame } }
檢驗(yàn):運(yùn)行 app狮腿。此刻按鈕應(yīng)該并排在一起了腿宰。注意,此時(shí)點(diǎn)擊任一按鈕應(yīng)該繼續(xù)調(diào)用 ratingButtonTapped(_:) 并打印消息到控制臺(tái)缘厢。
[圖片上傳失敗...(image-382550-1608214851936)]
使用 Debug toggle
收縮控制臺(tái)吃度。
[圖片上傳失敗...(image-e34a5f-1608214851936)]
為按鈕大小添加常量
注意在代碼中使用了 44 的值。而在代碼的各處使用硬編碼值是很不好的做法贴硫。如果需要一個(gè)大一點(diǎn)的按鈕椿每,就得在各處修改這個(gè) 44 的值。相反英遭,定義常量來(lái)表示按鈕的大小间护,這樣更方便修改因?yàn)橹抵恍栊薷囊惶帯?/p>
現(xiàn)在,根據(jù)容器視圖的高度調(diào)整按鈕來(lái)適配不同尺寸的容器視圖.
定義一個(gè)常量作為按鈕的大小
-
在 layoutSubviews() 方法第一行前面添加如下代碼:
// Set the button's width and height to a square the size of the frame's height. let buttonSize = Int(frame.size.height)
這讓布局更加靈活挖诸。
-
修改方法剩下部分使用 buttonSize 常量取代 44:
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 }
和第一次添加按鈕一樣汁尺,你需要更新控件的固有內(nèi)容尺寸來(lái)讓堆棧視圖正確的布局。使用 intrinsicContentSize 方法來(lái)計(jì)算控件的大小并返回税灌,代碼如下:
-
修改 init?(coder:) 方法中 for-in 循環(huán)的第一行為如下代碼 :
let button = UIButton()
因?yàn)槟阍?layoutSubviews() 中設(shè)置了按鈕的 frame均函,你不再需要在創(chuàng)建按鈕時(shí)候設(shè)置了。
最后的
layoutSubviews()
方法應(yīng)該是這樣: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 ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } }
intrinsicContentSize 方法應(yīng)該像這樣:
override func intrinsicContentSize() -> CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize + spacing) * stars
return CGSize(width: width, height: buttonSize)
}
init?(coder:) 構(gòu)造方法應(yīng)該像這樣:
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)
}
}
檢驗(yàn):運(yùn)行 app菱涤。一切和之前一樣。按鈕應(yīng)該并排顯示洛勉。此刻點(diǎn)擊任何一個(gè)按鈕都會(huì)調(diào)用 ratingButtonTapped(_:) 并打印信息到控制臺(tái)粘秆。
[圖片上傳失敗...(image-24503b-1608214851936)]
向按鈕中添加星星的圖片
下一步,添加空的和填充的星星圖片到按鈕中收毫。
[圖片上傳失敗...(image-27ef0d-1608214851936)]
可以在課程最終項(xiàng)目的 Images/ 文件夾中找到這兩張圖片攻走,或者用你自己的。
添加圖片到項(xiàng)目中
選擇項(xiàng)目導(dǎo)航的 Assets.xcassets 查看 asset catalog此再。
回憶一下 asset catalog 是存放和管理 app 圖片資源的地方昔搂。-
點(diǎn)擊左下角的 + 按鈕,從彈出的菜單中選擇 New Folder输拇。
[圖片上傳失敗...(image-ca95d8-1608214851936)]
雙擊文件夾名字并重命名為 Rating Images摘符。
選中文件夾,點(diǎn)擊左下角的 + 按鈕策吠,在彈出菜單中選擇 New Image Set逛裤。
一個(gè)圖片集合雖然只能代表單張圖片,但是包含在不同屏幕分辨率上顯示的不同版本圖片猴抹。雙擊圖片集合的名字并重命名為 emptyStar带族。
選擇電腦中空的星星圖片。
-
拖動(dòng)圖片到圖片集合的 2x 槽中蟀给。
[圖片上傳失敗...(image-54cea1-1608214851936)]
2x 是 iPhone 6 模擬器顯示的分辨率蝙砌,圖片在這個(gè)分辨率顯示最好阳堕。
點(diǎn)擊左下角的 + 按鈕,在彈出的菜單中選擇 New Image Set择克。
雙擊圖片集合名字并重命名為 filledStar恬总。
在電腦上,選擇想要添加的填充狀態(tài)的星星圖片祠饺。
-
拖動(dòng)圖片到圖片集合的 2x 槽中越驻。
[圖片上傳失敗...(image-8ab7f7-1608214851936)]
asset catalog 應(yīng)該像這樣:
[圖片上傳失敗...(image-3c4c98-1608214851936)]
下一步,在適當(dāng)時(shí)候?qū)懘a為按鈕設(shè)置正確的圖片道偷。
為按鈕設(shè)置星星的圖片
打開 RatingControl.swift缀旁。
-
在 init?(coder:) 構(gòu)造方法的 for-in 循環(huán)前面添加這兩行:
let filledStarImage = UIImage(named: "filledStar") let emptyStarImage = UIImage(named: "emptyStar")
-
在 for-in 循環(huán)按鈕初始化那一行的后面,添加如下代碼:
button.setImage(emptyStarImage, forState: .Normal) button.setImage(filledStarImage, forState: .Selected) button.setImage(filledStarImage, forState: [.Highlighted, .Selected])
為按鈕的不同狀態(tài)設(shè)置兩張不同的圖片這樣當(dāng)按鈕選中的時(shí)候就能看到狀態(tài)變化了勺鸦。按鈕未選中(.Normal 狀態(tài))顯示空的星星圖片并巍。按鈕被選中(.Selected 狀態(tài))顯示填充的星星圖片。在用戶點(diǎn)擊按鈕不松手時(shí)候按鈕同時(shí)處于選中和高亮狀態(tài)换途。
-
刪除設(shè)置背景顏色為紅色的那行代碼:
button.backgroundColor = UIColor.redColor()
因?yàn)榘粹o已經(jīng)有圖片懊渡,所以把背景顏色去掉。
-
添加如下代碼:
button.adjustsImageWhenHighlighted = false
確保圖片在狀態(tài)切換時(shí)不顯示額外的高亮狀態(tài)军拟。
init?(coder:)
構(gòu)造方法應(yīng)該像這樣:
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)
}
}
檢驗(yàn):運(yùn)行 app剃执。應(yīng)該看到星星已經(jīng)取代紅色按鈕了。此刻點(diǎn)擊任意一個(gè)按鈕仍然會(huì)調(diào)用 ratingButtonTapped(_:) 并打印信息到控制臺(tái)上懈息,但是按鈕圖片并沒(méi)有改變肾档。下一步會(huì)修復(fù)這個(gè)問(wèn)題的。
[圖片上傳失敗...(image-2e0a7f-1608214851936)]
實(shí)現(xiàn)按鈕動(dòng)作
用戶需要點(diǎn)擊星星的時(shí)候選擇評(píng)分辫继,所以使用真實(shí)的 ratingButtonTapped(_:) 方法替代調(diào)試實(shí)現(xiàn)怒见。
實(shí)現(xiàn)評(píng)分動(dòng)作
找到 RaingControl.swift 文件的 ratingButtonTapped(_:) 方法:
-
使用下面這行替代 print 語(yǔ)句:
rating = ratingButtons.indexOf(button)! + 1
indexOf(_:) 方法嘗試找出數(shù)組中選中的按鈕并返回它的下標(biāo)。
方法返回可選的 Int 因?yàn)樗阉鞯膶?duì)象或許在集合中不存在姑宽。然而遣耍,因?yàn)橛|發(fā)動(dòng)作的按鈕是你自己添加和創(chuàng)建到數(shù)組中的,所以確定會(huì)返回正確的下標(biāo)炮车。這時(shí)舵变,使用 ! 來(lái)訪問(wèn)內(nèi)在的下標(biāo)值示血。因?yàn)閿?shù)組下標(biāo)是從0開始所以要加1才是合適的評(píng)分. -
在 RatingControl.swift 的最后一個(gè) } 前面棋傍,添加如下代碼:
func updateButtonSelectionStates() { }
使用幫助方法來(lái)更新按鈕的選中狀態(tài)。
-
在 updateButtonSelectionStates() 方法中难审,添加 for-in 循環(huán):
for (index, button) in ratingButtons.enumerate() { // If the index of a button is less than the rating, that button should be selected. button.selected = index < rating }
代碼遍歷按鈕數(shù)組根據(jù)按鈕的下標(biāo)是否小于評(píng)分來(lái)設(shè)置每個(gè)按鈕的狀態(tài)瘫拣。如果 index < rating 設(shè)置按鈕的狀態(tài)為選中并讓它顯示填充的星星圖片。否則告喊,按鈕不選中顯示空的星星圖片麸拄。
-
在 ratingButtonTapped(_:) 方法中派昧,添加 updateButtonSelectionStates() 的調(diào)用作為最后一行的實(shí)現(xiàn):
func ratingButtonTapped(button: UIButton) { rating = ratingButtons.indexOf(button)! + 1 updateButtonSelectionStates() }
-
在 layoutSubviews() 方法中,添加 updateButtonSelectionStates() 的調(diào)用作為最后一行的實(shí)現(xiàn):
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 ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } updateButtonSelectionStates() }
不僅僅當(dāng)評(píng)分改動(dòng)的時(shí)候拢切,視圖加載的時(shí)候也要更新按鈕的選中狀態(tài)蒂萎,這是非常重要的。
-
在
// MARK: Properties
區(qū)域淮椰,找到 rating 屬性:var rating = 0
-
更新 rating 屬性讓它包含屬性觀察器:
var rating = 0 { didSet { setNeedsLayout() } }
屬性觀察器觀察和響應(yīng)屬性值的修改五慈。在設(shè)置屬性值的前后它會(huì)被立即調(diào)用。具體來(lái)講主穗,當(dāng)設(shè)完值后 didSet 屬性觀察器立刻執(zhí)行泻拦。在這里調(diào)用 setNeedsLayout() 讓每次評(píng)分修改后觸發(fā)布局更新。確保 UI 顯示正確的 rating 屬性值忽媒。
updateButtonSelectionStates() 方法應(yīng)該像這樣:
func updateButtonSelectionStates() {
for (index, button) in ratingButtons.enumerate() {
// If the index of a button is less than the rating, that button shouldn't be selected.
button.selected = index < rating
}
}
檢驗(yàn):運(yùn)行 app争拐。應(yīng)該能看到五顆星,請(qǐng)嘗試點(diǎn)擊一個(gè)來(lái)修改評(píng)分晦雨。點(diǎn)擊第三顆星來(lái)修改評(píng)分為3架曹,例如。
[圖片上傳失敗...(image-273d8b-1608214851936)]
為間距和星星數(shù)量添加屬性
確保在代碼中沒(méi)有任何硬編碼的值闹瞧,為評(píng)分的數(shù)量和評(píng)分之間的間距添加屬性绑雄。這樣,你只需修改代碼的一處奥邮。
用屬性代替硬編碼的值
-
找到 RatingControl.swift 的
// MARK: Properties
區(qū)域绳慎。// MARK: Properties var rating = 0 { didSet { setNeedsLayout() } } var ratingButtons = [UIButton]()
點(diǎn)擊編輯區(qū)頂部文件名字使用 functions menu 快速跳過(guò)去。
-
在已存在的屬性下面漠烧,添加如下代碼:
var spacing = 5
使用這個(gè)屬性給你的按鈕增加一些額外間距。
-
在 layoutSubviews 中靡砌,使用 spacing 屬性替代那個(gè)作為間距的常量已脓。
buttonFrame.origin.x = CGFloat(index * (buttonSize + spacing))
-
在 spacing 屬性下面,添加另外一個(gè)屬性:
var stars = 5
可以使用這個(gè)屬性來(lái)控制控件的星星數(shù)量通殃。
-
在
init?(coder:)
中,使用 stars 屬性替代之前你為星星數(shù)量設(shè)置的常量度液。for _ in 0..<stars {
檢驗(yàn):運(yùn)行 app。一起都應(yīng)該和之前一樣画舌。
連接評(píng)分控件和視圖控制器
最后一件要做的事情就是給 ViewController 類添加一個(gè)評(píng)分控件的引用堕担。
連接評(píng)分控件 outlet 到 ViewController.swift 中
打開 storyboard。
-
點(diǎn)擊 Xcode 工具欄中的 Assistant 按鈕打開輔助編輯器曲聂。
[圖片上傳失敗...(image-bca606-1608214851936)]
-
如果想要更多的空間來(lái)工作霹购,點(diǎn)擊 Xcode 工具欄中的 Navigator 和 Utilties 按鈕來(lái)收縮項(xiàng)目導(dǎo)航和實(shí)用工具區(qū)。
[圖片上傳失敗...(image-4a797d-1608214851936)]
同樣可以收縮大綱視圖朋腋。
-
選擇評(píng)分控件齐疙。
ViewConroller.swift 會(huì)顯示在右邊的編輯器膜楷。(如果沒(méi)有,在右邊的編輯器選擇欄中選擇 Automatic > ViewController.swift)
-
按住 Control 從畫板中的評(píng)分控件拖動(dòng)到右邊的編輯器中贞奋,在 photoImageView 屬性的下面停止拖動(dòng)赌厅。
[圖片上傳失敗...(image-8d53cb-1608214851936)]
-
在出現(xiàn)的對(duì)話框中,輸入名字 ratingControl轿塔。
忽略剩下的選項(xiàng)特愿,對(duì)話框最后應(yīng)該像這樣:[圖片上傳失敗...(image-80bfa3-1608214851936)]
點(diǎn)擊 Connect。
此刻 ViewController 類應(yīng)該有評(píng)分控件的引用了勾缭。
清理項(xiàng)目
現(xiàn)在已經(jīng)很接近最后的食物場(chǎng)景界面了揍障,但是還是需要做一些清理工作。app 正在逐漸實(shí)現(xiàn)更多牛逼的功能且擁有與前面課程完全不同的界面漫拭。需要?jiǎng)h掉一些不需要的代碼亚兄,同時(shí)把控件集中到堆棧視圖中來(lái)平衡界面。
清理界面
-
點(diǎn)擊 Standard 按鈕返回標(biāo)準(zhǔn)編輯器采驻。
[圖片上傳失敗...(image-48437d-1608214851936)]
點(diǎn)擊 Xcode 工具欄的 Navigator 和 Utiliites 按鈕展開項(xiàng)目導(dǎo)航和實(shí)用工具區(qū)审胚。
打開 storyboard。
-
選擇 Set Default Label Text 按鈕礼旅,點(diǎn)擊 Delete 鍵刪除它膳叨。
堆棧視圖重新排放 UI 控件來(lái)填充按鈕留下的空間。[圖片上傳失敗...(image-80ff66-1608214851936)]
-
如果需要痘系,打開大綱視圖菲嘴。選擇 Stack View 對(duì)象。
[圖片上傳失敗...(image-7b62c4-1608214851936)]
打開屬性檢查器汰翠。
-
在屬性檢查器中龄坪,找到 Alignment 區(qū)域并選擇 Center。
堆棧視圖中所有控件應(yīng)該水平居中了:[圖片上傳失敗...(image-15509a-1608214851936)]
現(xiàn)在复唤,清理剛才刪除按鈕的對(duì)應(yīng)動(dòng)作方法健田。
清理代碼
打開 ViewController.swift。
-
刪除 ViewController.swift 中的 setDefaultLabelText(_:) 動(dòng)作方法佛纫。
@IBAction func setDefaultLabelText(sender: UIButton) { mealNameLabel.text = "Default Text" }
檢驗(yàn):運(yùn)行 app妓局。一切都和之前一樣,但是 Set Default Label Text 按鈕消失了呈宇,控件也都水平居中了好爬。按鈕并排貼在一起。此刻點(diǎn)擊任何一個(gè)按鈕仍然會(huì)調(diào)用 ratingButtonTapped(_:) 并相應(yīng)修改按鈕圖片甥啄。
重要提示
如果運(yùn)行出現(xiàn)問(wèn)題存炮,嘗試按 Command-Shift-K 來(lái) clear 項(xiàng)目。
[圖片上傳失敗...(image-91ad7a-1608214851936)]
注意:
為了查看本課的完整實(shí)例項(xiàng)目, 下載文件并在 Xcode 中查看它僵蛛。