使用Swift編程語言開發iOS應用(五)

實現一個定制化控件

本文的學習目標

  • 創建和關聯定制化源代碼到storyboard的界面元素
  • 定義一個定制類
  • 實現一個定制類的構造器
  • 使用UIView作為容器
  • 理解如何程序化顯示視圖

創建一個定制化視圖

為了能夠給一個菜品打分,需要一個控件(control)來選擇給一個菜品賦予星級的數量,本文關注于在storyboard上創建一個定制視圖和編寫定義代碼。

5_2_ratingcontrol_2x.png

這個打分控件讓用戶給一個菜品選擇0、1、2、3、4或者5顆星,點擊其中一顆星時,包括被點擊的星和其前面的星將被填充,被填充的星的數量作為打分值。

創建一個UIView類的子類
  1. 選擇File/New/File(或按鍵)
  2. 在提示對話框中選擇Source為iOS
  3. 選擇Cocoa Touch Class,點擊Next
  4. 在Class字段,輸入RatingControl
  5. 在SubClass of字段,選擇UIKit
  6. 選擇語言選項為Swift。


    5_3_newviewclass_2x.png
  7. 點擊“Next”
  8. 使用所有缺省,點擊“Create”,
    生成了定義RatingControl類的RatingControl.swift文件。
  9. 代碼如下

import UIKit class RatingControl: UIView { }

典型創建一個視圖的方法有兩種:一種是初始化(initializing)視圖的框架然后人工添加視圖到界面上,第二種使用storyboard來加載一個視圖,分別對應于:初始化框架方法init(frame:)和storyboard的init?(coder:)。
本文使用storyboard加載視圖,需要重寫(override)父類的init?(coder:)方法的實現。

重寫構造器
  1. 在RatingControl.swift中,添加//MARK注釋;

    //MARK: Initialization

  2. 在評論下,輸入init后出現命令自動完成窗口;

5_4_initcoder_codecompletion_2x.png
  1. 選擇對應于init?(coder:)的方法;

  2. 點擊錯誤提示來消除錯誤,添加required關鍵字;

    required init?(coder aDecoder: NSCoder) { }

    每個UIView子類實現構造器必須包含一個init?(coder:)構造器的實現,因此在該構造器前面需要required。

  3. 增加父類構造器的調用。

    required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) }

5_5_initcoder_fixit_2x.png

顯示定制視圖

為了顯示定制視圖,在界面上增加一個視圖并在代碼和視圖之間建立連接。

顯示視圖
  1. 打開storyboard文件
  2. 在對象庫(Object library)中找到視圖對象,將其拖拽到storyboard場景堆棧視圖中圖像視圖的下面
  3. 選中定制視圖的同時,打開尺寸編輯器(Size inspector),修改Height字段為44,修改Width字段為240,修改Intrinsic Size字段為Placeholder
5_6_inspector_size_2x.png
5_7_viewwithplaceholdersize_2x.png
  1. 選中定制視圖的同時,打開標示編輯器(Identity inspector),在Class字段選擇RatingControl
5_8_inspector_identity_2x.png
5_9_identity_ratingcontrol_2x.png

增加按鈕到視圖中

在UIView類的子類定制視圖RatingControl中添加按鈕,讓用戶可以選擇點擊打分。

在視圖中創建一個按鈕
  1. 在構造器init?(coder:)中,添加創建一個紅色按鈕的代碼,并添加到RatingControl視圖中

    let button = UIButton(frame: CGRect(x:0, y:0, width: 44, height: 44)) button.backgroundColor = UIColor.redColor() addSubview(button)

  2. 設置按鈕所在視圖的固有尺寸(intrinsic size),用于堆棧視圖對按鈕進行布局。

    override func instrinsicContentSize() ->CGSize { return CGSize(width: 240, height: 44) }

增加按鈕的動作方法
  1. 在RatingControl.swift文件中,在代碼塊中添加注釋說明到最后一行。

    // MARK: Button Action

  2. 在注釋下面添加代碼

    func ratingButtonTapped(button: UIButton) { print(“Button pressed”) }

  3. 在構造器init?(coder:)中代碼addSubview(button)之前添加代碼

    button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(_:)), forControlEvents: .TouchDown)

    前面教程有講述連接storyboard上的界面元素到代碼中的動作方法,采用了目標-動作(target-action)模式,同樣這里綁定ratingButtonTapped(_:)動作方法到button對象上,當button對象出現TouchDown事件時會觸發其綁定的動作方法。這里目標是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) }

5_11_console_buttonpressed_2x.png
增加打分屬性
  1. 在RatingControl.swift文件中,在代碼塊中添加屬性注釋說明和以下代碼。

    // MARK: Properties var rating = 0 var ratingButtons = [UIButton]()

創建5個按鈕
  1. 在RatingControl.swift文件中,在構造器init?(coder:)中增加for-in循環,增加ratingButtons數組相關代碼。

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,在子類中重寫該方法。

布局多個按鈕
  1. 在RatingControl.swift文件中構造器后面添加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循環遍歷ratingButtons數組,調用其enumerate()方法得到數組中的對值(index, button)對象,index為數組的下標。

增加屬性用于Star間隔和數量

增加屬性
  1. 在RatingControl.swift文件中// MARK: Properties段添加一行代碼

    let spacing = 5

  2. 在layoutSubviews方法中,修改代碼

    buttonFrame.origin.x = CGFloat(index * (44 + spacing))

  3. 在spacing屬性下面添加代碼

    let starCount = 5

  4. 在構造器init?(coder:)中修改代碼

    for _ in 0..<starCount {

聲明一個按鈕尺寸的常量

讓按鈕根據所在容器視圖的高度來調整尺寸,使用常量保存容器視圖的高度。

聲明一個常量用于按鈕的尺寸
  1. 在layoutSubviews()方法中,增加以下代碼

    let buttonSize = Int(frame.size.height)

  2. 修改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 }

  3. 在instrinsicContentSize()方法修改代碼

    let buttonSize = Int(frame.size.height) let width = (buttonSize * starCount) return CGSize(width: width, height: buttonSize)

  4. 在init?(coder:)構造器中for-in循環修改第一行代碼

    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:)構造器完整代碼如下:

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) } }

在模擬器上運行APP

5_13_sim_5redbuttons_2x.png

增加星型圖像到按鈕上

添加圖像到項目中
  1. 在項目導航欄中選中Assets.xcassets,右側顯示資源目錄(asset catalog);

  2. 在點擊左下角的“+”按鈕,在彈出菜單中選擇“New Folder”;

    5_14_assetcatalog_addfolder_2x.png
  3. 雙擊目錄名并改名為”Rating Images”;

  4. 在目錄選中的狀態下,在點擊左下角的“+”按鈕,在彈出菜單中選擇“New Image Set”,一個圖像集合(image set)代表一組包含用于顯示不同屏幕分辨率的不同版本圖像的單一圖像資源;

  5. 雙擊資源集合名并改名為”emptyStar“;

  6. 將電腦中empty star圖像拖拽到圖像集合中,2x表示用于iPhone 6的顯示分辨率;

    5_17_emptystar_drag_2x.png
  7. 同上方法創建filledStar圖像集合。

5_18_filledstar_drag_2x.png
5_19_assetcatalog_final_2x.png
設置按鈕為星型圖像
  1. 打開RatingControl.swift文件
  2. 在init?(coder:)構造器中,添加以下代碼

let filledStarImage = UIImage(named: “filledStar”) let emptyStarImage = UIImage(named: “emptyStar”)

  1. 在for-in循環中,在按鈕被初始化代碼后面增加以下代碼

button.setImage(emptyStartImage, forState: .Normal) button.setImage(filledStarImage, forState: .Selected) button.setImage(filledStarImage, forState: [.Highlighted, .Selected])

  1. 修改部分代碼,完整的init?(coder:)構造器代碼如下

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) } }

在模擬器上運行APP

實現按鈕動作方法

點擊一個星完成實際的打分,實現ratingButtonTapped(_:)方法。

實現打分動作
  1. 在RatingControl.swift中,找到ratingButtonTapped(_:)方法,添加代碼;

`func ratingButtonTapped(button: UIButton) {

   rating = ratingButtons.indexOf(button)! + 1

}`

indexOf(_:)方法在按鈕數組中找到被點擊按鈕所在的數組下標(數組下標從0開始,打分計數要加1)。

  1. 添加updateButtonSelectionStates()方法和代碼;

func updateButtonSelectionStates() { for (index, button) in ratingButtons.enumerate() { button.selected = index < rating } }

更新按鈕的選中狀態,當小于點擊按鈕的數組下標時設置為選中,否則為未選中。
  1. 修改ratingButtonTapped(_:)方法如下;

func ratingButtonTapped(button: UIButton) { rating = ratingButtons.indexOf(button)! + 1 updateButtonSelectionStates() }

  1. 修改layoutSubviews()方法,增加updateButtonSelectionStates()的調用;

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() }

  1. 為rating屬性增加屬性偵聽(property observer)方法didSet。

var rating=0 { didSet { setNeedsLayout() } }

屬性偵聽當屬性發生改變后會調用didSet,這里會調用setNeedsLayout()觸發布局更新事件。
在模擬器上運行APP:
5_21_sim_filledstars_2x.png

連接打分控件到視圖控制器

在ViewController類中建立一個打分控件的引用。

使用outlet連接打分控件到ViewController.swift
  1. 打開storyboard文件;
  2. 點擊右上角的助手編輯器;
  3. 在界面選中打分控件,拖拽到ViewController.swift中屬性photoImageView下面;
5_22_ratingcontrol_dragoutlet_2x.png
  1. 在提示對話框中Name字段輸入ratingControl;

    5_23_ratingcontrol_addoutlet_2x.png
  2. 點擊“Coonect”按鈕。

清理項目代碼

清理界面
  1. 打開storyboard;
  2. 選中“Set Default Label Text”按鈕,按Delete鍵將其刪除;
5_24_deletebutton_2x.png
  1. 打開outline視圖,選中StackView對象;
2_AL_outlineview_2x.png
  1. 打開屬性編輯器(Attributes inspector);
  2. 找到Alignment字段,選擇Center。
5_25_centerstack_2x.png
清理代碼
  1. 打開ViewController.swift文件
  2. 刪除setDefaultLabelText(_:)方法
    在模擬器上運行APP:
5_26_sim_finalUI_2x.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,002評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,400評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,136評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,714評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,452評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,818評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,812評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,997評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,552評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,292評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,510評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,721評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,121評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,429評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,235評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,480評論 2 379

推薦閱讀更多精彩內容