實現(xiàn)一個定制化控件
本文的學(xué)習(xí)目標(biāo)
- 創(chuàng)建和關(guān)聯(lián)定制化源代碼到storyboard的界面元素
- 定義一個定制類
- 實現(xiàn)一個定制類的構(gòu)造器
- 使用UIView作為容器
- 理解如何程序化顯示視圖
創(chuàng)建一個定制化視圖
為了能夠給一個菜品打分,需要一個控件(control)來選擇給一個菜品賦予星級的數(shù)量匆背,本文關(guān)注于在storyboard上創(chuàng)建一個定制視圖和編寫定義代碼扩灯。
這個打分控件讓用戶給一個菜品選擇0对途、1、2英融、3、4或者5顆星,點擊其中一顆星時忌傻,包括被點擊的星和其前面的星將被填充,被填充的星的數(shù)量作為打分值搞监。
創(chuàng)建一個UIView類的子類
- 選擇File/New/File(或按鍵)
- 在提示對話框中選擇Source為iOS
- 選擇Cocoa Touch Class水孩,點擊Next
- 在Class字段,輸入RatingControl
- 在SubClass of字段琐驴,選擇UIKit
-
選擇語言選項為Swift俘种。
- 點擊“Next”
- 使用所有缺省,點擊“Create”绝淡,
生成了定義RatingControl類的RatingControl.swift文件宙刘。 - 代碼如下
import UIKit class RatingControl: UIView { }
典型創(chuàng)建一個視圖的方法有兩種:一種是初始化(initializing)視圖的框架然后人工添加視圖到界面上,第二種使用storyboard來加載一個視圖牢酵,分別對應(yīng)于:初始化框架方法init(frame:)和storyboard的init?(coder:)悬包。
本文使用storyboard加載視圖,需要重寫(override)父類的init?(coder:)方法的實現(xiàn)馍乙。
重寫構(gòu)造器
-
在RatingControl.swift中布近,添加//MARK注釋;
//MARK: Initialization
在評論下丝格,輸入init后出現(xiàn)命令自動完成窗口撑瞧;
選擇對應(yīng)于init?(coder:)的方法;
-
點擊錯誤提示來消除錯誤显蝌,添加required關(guān)鍵字预伺;
required init?(coder aDecoder: NSCoder) { }
每個UIView子類實現(xiàn)構(gòu)造器必須包含一個init?(coder:)構(gòu)造器的實現(xiàn),因此在該構(gòu)造器前面需要required。
-
增加父類構(gòu)造器的調(diào)用酬诀。
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }
顯示定制視圖
為了顯示定制視圖脏嚷,在界面上增加一個視圖并在代碼和視圖之間建立連接。
顯示視圖
- 打開storyboard文件
- 在對象庫(Object library)中找到視圖對象料滥,將其拖拽到storyboard場景堆棧視圖中圖像視圖的下面
- 選中定制視圖的同時然眼,打開尺寸編輯器(Size inspector),修改Height字段為44葵腹,修改Width字段為240高每,修改Intrinsic Size字段為Placeholder
- 選中定制視圖的同時,打開標(biāo)示編輯器(Identity inspector)践宴,在Class字段選擇RatingControl
增加按鈕到視圖中
在UIView類的子類定制視圖RatingControl中添加按鈕鲸匿,讓用戶可以選擇點擊打分。
在視圖中創(chuàng)建一個按鈕
-
在構(gòu)造器init?(coder:)中阻肩,添加創(chuàng)建一個紅色按鈕的代碼带欢,并添加到RatingControl視圖中
let button = UIButton(frame: CGRect(x:0, y:0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() addSubview(button)
-
設(shè)置按鈕所在視圖的固有尺寸(intrinsic size),用于堆棧視圖對按鈕進(jìn)行布局烤惊。
override func instrinsicContentSize() ->CGSize { return CGSize(width: 240, height: 44) }
增加按鈕的動作方法
-
在RatingControl.swift文件中乔煞,在代碼塊中添加注釋說明到最后一行。
// MARK: Button Action
-
在注釋下面添加代碼
func ratingButtonTapped(button: UIButton) { print(“Button pressed”) }
-
在構(gòu)造器init?(coder:)中代碼addSubview(button)之前添加代碼
button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)
前面教程有講述連接storyboard上的界面元素到代碼中的動作方法柒室,采用了目標(biāo)-動作(target-action)模式渡贾,同樣這里綁定ratingButtonTapped(_:)動作方法到button對象上,當(dāng)button對象出現(xiàn)TouchDown事件時會觸發(fā)其綁定的動作方法雄右。這里目標(biāo)是self指的是RatingControl類空骚。
完整的代碼如下:
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: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents:.TouchDown) addSubview(button) }
增加打分屬性
- 在RatingControl.swift文件中,在代碼塊中添加屬性注釋說明和以下代碼擂仍。
// MARK: Properties var rating = 0 var ratingButtons = [UIButton]()
創(chuàng)建5個按鈕
- 在RatingControl.swift文件中囤屹,在構(gòu)造器init?(coder:)中增加for-in循環(huán),增加ratingButtons數(shù)組相關(guān)代碼逢渔。
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:#selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
UIView類的布局方法是layoutSubviews肋坚,在子類中重寫該方法。
布局多個按鈕
- 在RatingControl.swift文件中構(gòu)造器后面添加layoutSubviews方法
override func layoutSubviews() { var buttonFrame = CGRect(x: 0, y: 0, width: 44, height: 44) for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (44 + 5)) button.frame = buttonFrame } }
使用for-in循環(huán)遍歷ratingButtons數(shù)組肃廓,調(diào)用其enumerate()方法得到數(shù)組中的對值(index, button)對象冲簿,index為數(shù)組的下標(biāo)。
增加屬性用于Star間隔和數(shù)量
增加屬性
-
在RatingControl.swift文件中// MARK: Properties段添加一行代碼
let spacing = 5
-
在layoutSubviews方法中亿昏,修改代碼
buttonFrame.origin.x = CGFloat(index * (44 + spacing))
-
在spacing屬性下面添加代碼
let starCount = 5
-
在構(gòu)造器init?(coder:)中修改代碼
for _ in 0..<starCount {
聲明一個按鈕尺寸的常量
讓按鈕根據(jù)所在容器視圖的高度來調(diào)整尺寸,使用常量保存容器視圖的高度档礁。
聲明一個常量用于按鈕的尺寸
-
在layoutSubviews()方法中角钩,增加以下代碼
let buttonSize = Int(frame.size.height)
-
修改layoutSubviews()方法的代碼
var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame }
-
在instrinsicContentSize()方法修改代碼
let buttonSize = Int(frame.size.height) let width = (buttonSize * starCount) return CGSize(width: width, height: buttonSize)
-
在init?(coder:)構(gòu)造器中for-in循環(huán)修改第一行代碼
let button = UIButton()
按鈕的框架尺寸在layoutSubviews()方法中完成。完整的代碼如下:
override func layoutSubviews() { let buttonSize = Int(frame.size.height) var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } }
intrinsicContentSize方法代碼如下:
`override func intrinsicContentSize() -> CGSize {
let buttonSize = Int(frame.size.height)
let width = (buttonSize * starCount) + (spacing * (starCount - 1))
return CGSize(width: width, height: buttonSize)
}`
init?(coder:)構(gòu)造器完整代碼如下:
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) for _ in0..<5 { let button = UIButton() button.backgroundColor = UIColor.redColor() button.addTarget(self, action:#selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
在模擬器上運(yùn)行APP
增加星型圖像到按鈕上
添加圖像到項目中
在項目導(dǎo)航欄中選中Assets.xcassets,右側(cè)顯示資源目錄(asset catalog)递礼;
-
在點擊左下角的“+”按鈕惨险,在彈出菜單中選擇“New Folder”;
雙擊目錄名并改名為”Rating Images”脊髓;
在目錄選中的狀態(tài)下辫愉,在點擊左下角的“+”按鈕,在彈出菜單中選擇“New Image Set”将硝,一個圖像集合(image set)代表一組包含用于顯示不同屏幕分辨率的不同版本圖像的單一圖像資源恭朗;
雙擊資源集合名并改名為”emptyStar“;
-
將電腦中empty star圖像拖拽到圖像集合中依疼,2x表示用于iPhone 6的顯示分辨率痰腮;
同上方法創(chuàng)建filledStar圖像集合。
設(shè)置按鈕為星型圖像
- 打開RatingControl.swift文件
- 在init?(coder:)構(gòu)造器中律罢,添加以下代碼
let filledStarImage = UIImage(named: “filledStar”) let emptyStarImage = UIImage(named: “emptyStar”)
- 在for-in循環(huán)中膀值,在按鈕被初始化代碼后面增加以下代碼
button.setImage(emptyStartImage, forState: .Normal) button.setImage(filledStarImage, forState: .Selected) button.setImage(filledStarImage, forState: [.Highlighted, .Selected])
- 修改部分代碼,完整的init?(coder:)構(gòu)造器代碼如下
required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let filledStarImage = UIImage(named: “filledStar”) let emptyStarImage = UIImage(named: “emptyStar”) for _ in 0..<5 { let button = UIButton() button.setImage(emptyStartImage, forState: .Normal) button.setImage(filledStarImage, forState: .Selected) button.setImage(filledStarImage, forState: [.Highlighted, .Selected]) button.adjustsImageWhenHighlighted = false button.addTarget(self, action:#selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown) ratingButtons += [button] addSubview(button) } }
在模擬器上運(yùn)行APP
實現(xiàn)按鈕動作方法
點擊一個星完成實際的打分误辑,實現(xiàn)ratingButtonTapped(_:)方法沧踏。
實現(xiàn)打分動作
- 在RatingControl.swift中,找到ratingButtonTapped(_:)方法巾钉,添加代碼翘狱;
`func ratingButtonTapped(button: UIButton) {
rating = ratingButtons.indexOf(button)! + 1
}`
indexOf(_:)方法在按鈕數(shù)組中找到被點擊按鈕所在的數(shù)組下標(biāo)(數(shù)組下標(biāo)從0開始,打分計數(shù)要加1)睛琳。
- 添加updateButtonSelectionStates()方法和代碼盒蟆;
func updateButtonSelectionStates() { for (index, button) in ratingButtons.enumerate() { button.selected = index < rating } }
更新按鈕的選中狀態(tài),當(dāng)小于點擊按鈕的數(shù)組下標(biāo)時設(shè)置為選中师骗,否則為未選中历等。
- 修改ratingButtonTapped(_:)方法如下;
func ratingButtonTapped(button: UIButton) { rating = ratingButtons.indexOf(button)! + 1 updateButtonSelectionStates() }
- 修改layoutSubviews()方法辟癌,增加updateButtonSelectionStates()的調(diào)用寒屯;
override func layoutSubviews() { let buttonSize = Int(frame.size.height) var buttonFrame = CGRect(x: 0, y: 0, width: buttonSize, height: buttonSize) for (index, button) in ratingButtons.enumerate() { buttonFrame.origin.x = CGFloat(index * (buttonSize + 5)) button.frame = buttonFrame } updateButtonSelectionStates() }
- 為rating屬性增加屬性偵聽(property observer)方法didSet。
var rating=0 { didSet { setNeedsLayout() } }
屬性偵聽當(dāng)屬性發(fā)生改變后會調(diào)用didSet黍少,這里會調(diào)用setNeedsLayout()觸發(fā)布局更新事件寡夹。
在模擬器上運(yùn)行APP:
連接打分控件到視圖控制器
在ViewController類中建立一個打分控件的引用。
使用outlet連接打分控件到ViewController.swift
- 打開storyboard文件厂置;
- 點擊右上角的助手編輯器菩掏;
- 在界面選中打分控件,拖拽到ViewController.swift中屬性photoImageView下面昵济;
-
在提示對話框中Name字段輸入ratingControl智绸;
點擊“Coonect”按鈕野揪。
清理項目代碼
清理界面
- 打開storyboard;
- 選中“Set Default Label Text”按鈕瞧栗,按Delete鍵將其刪除斯稳;
- 打開outline視圖,選中StackView對象迹恐;
- 打開屬性編輯器(Attributes inspector)挣惰;
- 找到Alignment字段,選擇Center殴边。
清理代碼
- 打開ViewController.swift文件
- 刪除setDefaultLabelText(_:)方法
在模擬器上運(yùn)行APP: