Start Developing iOS Apps (Swift)->實(shí)現(xiàn)自定義控件(一)

在本課中幅疼,你將為FoodTracker應(yīng)用實(shí)現(xiàn)一個(gè)評(píng)分控件。當(dāng)你完成時(shí)昼接,應(yīng)用看上去是這樣的:

image: ../Art/ICC_sim_finalUI_2x.png

學(xué)習(xí)目標(biāo)

在結(jié)束本課時(shí),你將能夠:

  • 創(chuàng)建自定義源碼文件悴晰,并將其與storyboard中的元素關(guān)聯(lián)
  • 定義一個(gè)自定義類(lèi)
  • 實(shí)現(xiàn)一個(gè)自定義類(lèi)的初始化器(initializer)
  • 把UIStackView作為容器使用
  • 理解如何創(chuàng)建可編程的視圖
  • 為自定義控件添加輔助功能信息
  • 使用 @IBInspectable 和 @IBDesignable在Interface Builder中顯示和控制自定義視圖

創(chuàng)建自定義視圖

為了能給菜品評(píng)分慢睡,用戶需要一個(gè)控件,這個(gè)控件可以讓他們選擇給菜品分配的星星數(shù)量铡溪。有很多方法可以實(shí)現(xiàn)這個(gè)要求漂辐,但是在本課中,關(guān)注一個(gè)簡(jiǎn)單的方法棕硫,通過(guò)已存在的視圖和控件來(lái)構(gòu)建一個(gè)自定義控件髓涯。你將創(chuàng)建一個(gè)棧視圖子類(lèi)來(lái)管理一排相當(dāng)于星的按鈕。你將在代碼中完全定義你的自定義控件哈扮,然后把它添加到storyboard中纬纪。

評(píng)分控件顯示為一排星星。

image: ../Art/ICC_ratingcontrol_2x.png

用戶能夠?yàn)椴似愤x擇一個(gè)評(píng)分滑肉。當(dāng)用戶點(diǎn)擊一顆星星時(shí)包各,這顆星和它之前的星都會(huì)被填充。如果用戶點(diǎn)擊最右邊被填充的星星(與當(dāng)前評(píng)分相關(guān)的星星)靶庙,那么評(píng)分將會(huì)被清除问畅,所有的星星都顯示為空。

開(kāi)始設(shè)計(jì)UI、交互护姆、控件行為之前矾端,需要?jiǎng)?chuàng)建一個(gè)自定義的UIStackView子視圖,

創(chuàng)建UIStackView的子類(lèi)

  1. 選擇File > New > File(或者按下Command-N)卵皂。
  2. 在彈出的對(duì)話框顯示時(shí)秩铆,選擇iOS。
  3. 選擇Cocoa Touch Class渐裂,點(diǎn)解Next豺旬。
  4. 在Class字段,輸入RatingControl柒凉。
  5. 在Subclass of字段族阅,選擇UIStackView。
  6. 確保Language選型為Swift膝捞。


    image: ../Art/ICC_newviewclass_2x.png
  7. 點(diǎn)擊Next坦刀。
    默認(rèn)保存位置是你的項(xiàng)目目錄。
    Group選項(xiàng)默認(rèn)是你的應(yīng)用名蔬咬,F(xiàn)oodTracker鲤遥。
    再Targets部分,選中你的應(yīng)用林艘,而應(yīng)用的測(cè)試不要選中盖奈。
  8. 對(duì)話框的其余內(nèi)容不變,點(diǎn)擊Create狐援。
    Xcode創(chuàng)建一個(gè)定義RatingControl類(lèi)的文件:RatingControl.swift钢坦。RatingControl是UIView的自定義子類(lèi)。
  9. 如有必要啥酱,在Project navigation中爹凹,拖拽RatingControl.swift文件到其他Swift文件的下方。


    image: ../Art/ICC_dragfile_2x.png
  10. 在RatingControl.swift中镶殷,刪除所有模版自帶的注釋?zhuān)@樣你就可以使用一個(gè)空的區(qū)域開(kāi)始工作了『探矗現(xiàn)在這個(gè)實(shí)現(xiàn)文件看上去是這樣的。
import UIKit     
class RatingControl: UIStackView {      
}

通常創(chuàng)建一個(gè)視圖有兩種方式:通過(guò)編程方式初始化視圖绘趋,或者通過(guò)storyboard加載視圖颤陶。每一種方法都有一個(gè)相應(yīng)的初始化器:inti(farme:)用于編程初始化視圖的,init:(coder:)用于從storyboard加載的視圖埋心≈赣簦回想一下,初始化器是一個(gè)方法拷呆,這個(gè)方法為類(lèi)實(shí)例能夠使用做準(zhǔn)備闲坎。它涉及到為每個(gè)類(lèi)的實(shí)例變量設(shè)置值疫粥,以及需要的任何其他設(shè)置。

這兩種方法你都要在自定義控件中實(shí)現(xiàn)腰懂。在設(shè)計(jì)應(yīng)用時(shí)梗逮,當(dāng)你將視圖添加到畫(huà)布中時(shí)邀层,Interface Builder會(huì)實(shí)例化它量蕊。在運(yùn)行時(shí)逾冬,應(yīng)用會(huì)從storyboard中加在視圖不同。

重寫(xiě)初始化器(initializer)

  1. 在RatingControl.swift,在class行的下面业岁,添加一個(gè)注釋宪哩。
//MARK: Initialization
  1. 在注釋后面隧枫,開(kāi)始鍵入init锚沸。
    代碼補(bǔ)全會(huì)顯示出來(lái)跋选。


    image: ../Art/ICC_init_codecompletion_2x.png
  2. 從可選列表中選擇 init(frame: CGRect) ,然后按下回車(chē)鍵哗蜈。
init(frame: CGRect) {       
}
  1. 如果有錯(cuò)誤前标,代碼旁邊會(huì)出現(xiàn)錯(cuò)誤和警告的圖標(biāo),黃色三角圖標(biāo)是警告距潘,紅色圓圈是錯(cuò)誤×读校現(xiàn)在,init(frame:)方法有一個(gè)錯(cuò)誤音比。點(diǎn)擊錯(cuò)誤圖標(biāo)俭尖,會(huì)顯示出這個(gè)錯(cuò)誤更多的信息。


    image: ../Art/ICC_init_fixit_2x.png
  2. 雙擊Fix-it來(lái)插入override關(guān)鍵字
override init(frame: CGRect) {
}

Swift編譯器知道init(frame:)方法時(shí)必須被標(biāo)記的洞翩,所以提供了一個(gè)fix-it(修復(fù)選項(xiàng))目溉,以便在代碼中進(jìn)行修復(fù)。Fix-its是編譯器提供的菱农,作為代碼中錯(cuò)誤的潛在解決方案。

  1. 添加下面的方法來(lái)調(diào)用超類(lèi)的初始化器:
super.init(frame: frame)    
  1. 在init(frame:)方法下面柿估,再次鍵入init循未,這次在代碼補(bǔ)全選項(xiàng)中選擇init(coder: NSCoder)。按下回車(chē)鍵秫舌。Xcode插入初始化器框架的妖。
init(coder: NSCoder) {    
}
  1. 使用Fix-it插入required關(guān)鍵字。

注意
Swift處理初始化器不同于其他方法足陨。如果你沒(méi)有提供任何初始化器嫂粟,Swift類(lèi)自動(dòng)繼承所有超類(lèi)定義的初始化器。如果你實(shí)現(xiàn)任何初始化器墨缘,你就不能再繼承超類(lèi)的初始化器了星虹;但是零抬,超類(lèi)能標(biāo)記一個(gè)或多個(gè)它的初始化器為required。子類(lèi)必須實(shí)現(xiàn)(或自動(dòng)繼承)所有這些必須的初始化器宽涌。此外平夜,子類(lèi)必須標(biāo)記這些的初始化器為requrd,表明它們的子類(lèi)也必須實(shí)現(xiàn)這些初始化器卸亮。

  1. 添加下面這行來(lái)調(diào)用超類(lèi)的初始化器忽妒。
super.init(coder: coder)

初始化器看上去是這樣的:

override init(frame: CGRect) {
    super.init(frame: frame)
}
         
required init(coder: NSCoder) {
    super.init(coder: coder)
}

現(xiàn)在,你的初始化器僅僅是占位符兼贸,只是簡(jiǎn)單調(diào)用了超類(lèi)的實(shí)現(xiàn)而已段直。你將在本課接下來(lái)的步驟中添加額外的設(shè)置。

顯示自定義視圖

為了顯示自定義控件溶诞,你需要在storyboard中添加一個(gè)stack view鸯檬,在stack view 和你寫(xiě)的代碼之間建立連接。

顯示視圖

  1. 打開(kāi)storyboard很澄。
  2. 在storyboard中京闰,使用Object library找到Horizontal Stack View(水平棧視圖)對(duì)象,并且把它拖拽到storyboard場(chǎng)景中甩苛,讓它在image view下方蹂楣。


    image: ../Art/ICC_addhorizontalstack_2x.png
  3. 在horizontal stack view選中狀態(tài)下,打開(kāi)Identity inspector讯蒲。
    回想一下痊土,Identify inspector讓你可以在storyboard中編輯一個(gè)對(duì)象的身份屬性,例如它屬于哪個(gè)類(lèi)墨林。


    image: ../Art/ICC_inspector_identity_2x.png
  4. 在Identity inspector中赁酝,找到Class字段并設(shè)置為RatingControl。


    image: ../Art/ICC_identity_ratingcontrol_2x.png

在視圖中添加按鈕

到這兒旭等,你已經(jīng)有了一個(gè)名為RatingControl的自定UIStackView子類(lèi)酌呆。接下來(lái),你需要添加一些按鈕到視圖中搔耕,以便讓用戶可以選擇評(píng)分隙袁。從簡(jiǎn)單開(kāi)始,在視圖中顯示一個(gè)紅色按鈕弃榨。

在視圖中創(chuàng)建按鈕

無(wú)論使用哪個(gè)初始化方法菩收,都要確保按鈕被添加。為此鲸睛,添加一個(gè)私有方法娜饵,setupButtons(),并且讓兩個(gè)初始化器都調(diào)用它官辈。

  1. 在RatingControl.swift中箱舞,在初始化器方法下面遍坟,添加注釋
//MARK: Private Methods

在注釋隔一點(diǎn)空間創(chuàng)建私有方法——在func前加上private來(lái)創(chuàng)建這個(gè)方法。私有方法只能在聲明它的類(lèi)中被調(diào)用褐缠。這可以讓你封裝和保護(hù)方法政鼠,確保它們不會(huì)被外部文件意外的調(diào)用。

  1. 在注釋下面队魏,添加下面的方法:
private func setupButtons() {    
        }
  1. 在setupButtons()方法中公般,添加下面幾行代碼來(lái)創(chuàng)建紅色的按鈕。
// Create the button
        let button = UIButton()
        button.backgroundColor = UIColor.red

這里胡桨,你使用了UIButton類(lèi)的方便初始化器官帘。這個(gè)初始化器調(diào)用init(frame:)并傳遞給長(zhǎng)方形的尺寸為0。從0長(zhǎng)度開(kāi)始是沒(méi)有問(wèn)題的昧谊,因?yàn)槟闶褂肁uto Layout(自動(dòng)布局)刽虹。這個(gè)stack view 將自動(dòng)定義按鈕的位置,并且你將通過(guò)約束來(lái)定義按鈕的尺寸呢诬。
你使用紅色以便可以看到它的位置涌哲。如果你喜歡,可以使用其他UIColor預(yù)定義的顏色尚镰,比如blue或green阀圾。

  1. 在最后一行下面,添加按鈕的約束狗唉。
// Add constraints
        button.translatesAutoresizingMaskIntoConstraints = false
        button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
        button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true    

第一行代碼禁用按鈕的自動(dòng)生成的約束初烘。當(dāng)你代碼實(shí)例化視圖,它的translatesAutoresizingMaskIntoConstraints屬性默認(rèn)為true分俯。這告訴布局引擎基于視圖的frame和autoresizingmask屬性來(lái)創(chuàng)建它的約束肾筐。然而,當(dāng)你使用Auto Layout時(shí)缸剪,你是要將這些自動(dòng)生成的約束替換掉吗铐。所以,把translatesAutoresizingMaskIntoConstraints屬性值設(shè)置為false可以移除這些自動(dòng)生成的約束杏节。

注意
這一行不是絕對(duì)的抓歼。當(dāng)你添加視圖到stack view,這個(gè)stack view會(huì)自動(dòng)設(shè)置視圖的translatesAutoresizingMaskIntoConstraints屬性為false拢锹。但是,當(dāng)使用Auto Layout時(shí)萄喳,無(wú)論何時(shí)都禁用自動(dòng)生成約束是一個(gè)好習(xí)慣卒稳。這樣就不會(huì)在真的需要的時(shí)候忘了寫(xiě)了。
button.heightAnchor 和button.widthAnchor開(kāi)始的行創(chuàng)建了定義按鈕高和寬的約束他巨。每行都執(zhí)行下面的步驟:

  1. 按鈕的heightAnchor 和 widthAnchor屬性給訪問(wèn)布局錨(layout anchor)提供了入口充坑。你使用布局錨來(lái)創(chuàng)建約束——在本例中减江,約束分別定義視圖高和寬。
  2. constraint(equalToConstant:)方法返回一個(gè)約束捻爷,它為視圖的高或?qū)挾x了一個(gè)常量辈灼。
  3. 約束的isActive屬性是用來(lái)激活和禁用這個(gè)約束的。當(dāng)你設(shè)置這個(gè)屬性為true時(shí)也榄,系統(tǒng)添加這個(gè)約束到當(dāng)前視圖巡莹,并激活它。
    這兩行代碼加在一起把按鈕定義為一個(gè)固定大小的對(duì)象(44點(diǎn)*44點(diǎn))甜紫。
  4. 最后降宅,添加這個(gè)按鈕到棧中:
// Add the button to the stack
addArrangedSubview(button)

這個(gè)addArrangedSubview()方法添加你創(chuàng)建的按鈕到RatingControl棧視圖管理的視圖列表中。這個(gè)操作把視圖添加為RatingControl的子視圖囚霸,并讓RatingControl創(chuàng)建必要的約束來(lái)管理按鈕在控件中的位置腰根。

你的setupButtons() 方法看上去應(yīng)該是這樣:

private func setupButtons() {
            
            // Create the button
            let button = UIButton()
            button.backgroundColor = UIColor.red
            
            // Add constraints
            button.translatesAutoresizingMaskIntoConstraints = false
            button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
            button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
            
            // Add the button to the stack
            addArrangedSubview(button)
        }

現(xiàn)在在初始化器方法中調(diào)用這個(gè)方法,像下面這樣:

override init(frame: CGRect) {
            super.init(frame: frame)
            setupButtons()
        }
         
        required init(coder: NSCoder) {
            super.init(coder: coder)
            setupButtons()
        }

檢查點(diǎn):運(yùn)行應(yīng)用拓型。你應(yīng)該能看到一個(gè)小的紅色正方形視圖额嘿。這個(gè)紅色的正方形就是你在初始化器里添加的按鈕。

image: ../Art/ICC_sim_1redbutton_2x.png

你需要為這個(gè)按鈕添加動(dòng)作(稍后你還要為其他按鈕添加)劣挫。最后册养,即娘使用這個(gè)按鈕來(lái)改變菜品的評(píng)分;但是揣云,現(xiàn)在你需要檢查這個(gè)操作是否正常捕儒。

給按鈕添加一個(gè)動(dòng)作

  1. 在RatingControl.swift,在//MARK Initialization部分后 main邓夕,添加注釋?zhuān)?/li>
//MARK: Button Action
  1. 在注釋下面刘莹,添加代碼:
    func ratingButtonTapped(button: UIButton) {
            print("Button pressed ??")
        }

使用print()函數(shù)來(lái)檢查ratingButtonTapped(_:)動(dòng)作是否按預(yù)期連接到了按鈕。這個(gè)函數(shù)會(huì)在Xcode調(diào)試控制臺(tái)打印標(biāo)準(zhǔn)輸出焚刚〉阃洌控制臺(tái)一個(gè)有用的調(diào)試機(jī)制,它出現(xiàn)在編輯器的底部矿咕。
稍后抢肛,你將把這個(gè)調(diào)試實(shí)現(xiàn)替換為更有用的實(shí)現(xiàn)。

  1. 找到setupButtons()方法:
private func setupButtons() {
            
            // Create the button
            let button = UIButton()
            button.backgroundColor = UIColor.red
            
            // Add constraints
            button.translatesAutoresizingMaskIntoConstraints = false
            button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
            button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
            
            // Add the button to the stack
            addArrangedSubview(button)
        }
  1. 緊挨著 // Add the button to the stack注釋上面碳柱,添加下面這段代碼:
// Setup the button action
        button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)

在之前的課程中捡絮,你使用target-action模式來(lái)連接storyboard中的元素到代碼中的action方法。這個(gè)addTarget(, action:, for:)方法在代碼中起到相同的作用莲镣。你把 ratingButtonTapped(:)action方法添加到了button對(duì)象上福稳,它將在.TouchDown事件發(fā)生的時(shí)候被觸發(fā)。
這段代碼做了很多事瑞侮。這是分析:

  • 目標(biāo)是self的圆,它指向當(dāng)前的類(lèi)的實(shí)例鼓拧。在本例中,它指向的是設(shè)置這些按鈕的RatingControl對(duì)象越妈。
  • 這個(gè)#selector表達(dá)式返回的是提供方法的Selector(選擇器)的值季俩。一個(gè)selector是一個(gè)識(shí)別這個(gè)方法的不透明的值。雖然很多新的API用block代替了selector梅掠,但仍然有很多方法酌住,比如performSelector(:) 和 addTarget(:action:forControlEvents:),在使用selector作為參數(shù)瓤檐。它系統(tǒng)在按鈕被點(diǎn)擊的時(shí)候調(diào)用動(dòng)作方法赂韵。
  • UIControlEvents選項(xiàng)定義了一些控件能夠響應(yīng)的事件。通常按鈕響應(yīng)的是.touchUpInside事件挠蛉。當(dāng)用戶觸摸按鈕然后在按鈕的范圍內(nèi)抬起手指的時(shí)候發(fā)生祭示。這個(gè)事件比 .touchDown更好,因?yàn)橛脩裟軌虬咽种敢苿?dòng)到按鈕范圍之外再抬起谴古,這樣就可以取消事件了质涛。
  • 注意因?yàn)槟銢](méi)有使用Interface Builder,所以你不需要使用IBAction屬性定義action方法掰担;你只需要像定義其他方法一樣定義動(dòng)作方法就好了汇陆。你能使用的方法可以是不帶參數(shù)的、只帶sender參數(shù)的带饱,或者帶sender和event兩個(gè)參數(shù)毡代。
func doSomething()
        func doSomething(sender: UIButton)
        func doSomething(sender: UIButton, forEvent event: UIEvent)

現(xiàn)在你的setupButtons()方法看上去是這樣的:

private func setupButtons() {
            
            // Create the button
            let button = UIButton()
            button.backgroundColor = UIColor.red
            
            // Add constraints
            button.translatesAutoresizingMaskIntoConstraints = false
            button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
            button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
            
            // Setup the button action
            button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
            
            // Add the button to the stack
            addArrangedSubview(button)
        }

檢查點(diǎn):運(yùn)行應(yīng)用。當(dāng)你點(diǎn)擊紅色正方形時(shí)勺疼,你能夠在控制臺(tái)看到“Button pressed”消息教寂。

image: ../Art/ICC_console_buttonpressed_2x.png

現(xiàn)在是時(shí)候想一想關(guān)于RatingControl類(lèi)要用什么樣的信息來(lái)表示評(píng)分。你需要跟蹤評(píng)分值执庐,以及用戶用來(lái)點(diǎn)擊設(shè)置評(píng)分的按鈕酪耕。你可以使用Int類(lèi)型來(lái)表示評(píng)分值,按鈕可以存放在一個(gè)數(shù)組中轨淌。

添加評(píng)分屬性

  1. 在RattingControl.swift中迂烁,找到類(lèi)聲明行:
class RatingControl: UIView {
  1. 在這行下面,添加下面的代碼:
//MARK: Properties
        private var ratingButtons = [UIButton]()
         
        var rating = 0

這創(chuàng)建了兩個(gè)屬性递鹉。第一個(gè)屬性包含按鈕列表盟步。你不想讓RattingControl類(lèi)以外的的類(lèi)訪問(wèn)這些按鈕;所以躏结,你把它聲明為私有址芯。第二個(gè)屬性包含了控件的評(píng)分。你需要?jiǎng)e的類(lèi)能夠讀寫(xiě)這個(gè)值。默認(rèn)情況下時(shí)內(nèi)部訪問(wèn)谷炸,保持不變。這樣你就能從應(yīng)用內(nèi)的其他類(lèi)來(lái)訪問(wèn)這個(gè)值禀挫。

現(xiàn)在旬陡,在視圖中你有一個(gè)按鈕,但你一共需要5個(gè)按鈕语婴。使用for-in循環(huán)來(lái)創(chuàng)建5個(gè)按鈕描孟。for-in循環(huán)遍歷一個(gè)序列,例如數(shù)字范圍砰左,多次執(zhí)行一組代碼匿醒。

創(chuàng)建五個(gè)按鈕

  1. 在RatingControl.swift中,找到setupButtons()方法缠导,并在方法的內(nèi)容外層添加一個(gè)for-in循環(huán)廉羔,就像這樣:
for _ in 0..<5 {
            // Create the button
            let button = UIButton()
            button.backgroundColor = UIColor.red
            
            // Add constraints
            button.translatesAutoresizingMaskIntoConstraints = false
            button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
            button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
            
            // Setup the button action
            button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
            
            // Add the button to the stack
            addArrangedSubview(button)
        }
通過(guò)選中for-in中的所有行并按下Control-I,確保這些行都縮進(jìn)僻造。Make sure the lines in the for-in loop are indented properly by selecting all of them and pressing Control-I. 在半開(kāi)放范圍運(yùn)算符(half-open range operator憋他, ..< )不包含上限數(shù)字,所以這個(gè)范圍是0-4髓削,總共循環(huán)五次竹挡,繪制5個(gè)按鈕而不是僅僅一個(gè)。下劃線(_)表示一個(gè)通配符立膛,當(dāng)你不需要知道當(dāng)前正在執(zhí)行的迭代的次數(shù)時(shí)揪罕,可以使用它。
  1. 在循環(huán)的結(jié)束花括號(hào)({)上面宝泵,添加這個(gè)代碼:
    // Add the new button to the rating button array
        ratingButtons.append(button)

當(dāng)你創(chuàng)建一個(gè)按鈕好啰,你就把它添加到ratingButtons數(shù)組中,用來(lái)跟蹤它鲁猩。

現(xiàn)在你的setupButtons()方法看上去是這樣的:

private func setupButtons() {
            
            for _ in 0..<5 {
                // Create the button
                let button = UIButton()
                button.backgroundColor = UIColor.red
                
                // Add constraints
                button.translatesAutoresizingMaskIntoConstraints = false
                button.heightAnchor.constraint(equalToConstant: 44.0).isActive = true
                button.widthAnchor.constraint(equalToConstant: 44.0).isActive = true
                
                // Setup the button action
                button.addTarget(self, action: #selector(RatingControl.ratingButtonTapped(button:)), for: .touchUpInside)
                
                // Add the button to the stack
                addArrangedSubview(button)
                
                // Add the new button to the rating button array
                ratingButtons.append(button)
            }
        }

檢查點(diǎn):運(yùn)行應(yīng)用坎怪。注意stack view是如何布局這些按鈕的。它們被水平排列的廓握,但是它們之間沒(méi)有間隔——這使它們就像一個(gè)紅色塊搅窿。

image: ../Art/ICC_buttonswithoutspace_2x.png

為了修復(fù)這種情況,打開(kāi)Main.storyboard并選擇RatingControl棧視圖隙券,代開(kāi)Attributes inspector男应,設(shè)置Spacing屬性為8。


image: ../Art/ICC_setstackspacing_2x.png

檢查點(diǎn):再次運(yùn)行應(yīng)用∮樽校現(xiàn)在按鈕的布局符合期望了沐飘。注意,現(xiàn)在點(diǎn)擊任何一個(gè)按鈕仍然調(diào)用ratingButtonTapped(button:),并在控制臺(tái)上打印一條消息耐朴。

image: ../Art/ICC_sim_5redbuttons_2x.png

使用Debug區(qū)域開(kāi)關(guān)折疊控制臺(tái)借卧。


image: ../Art/debug_toggle_2x.png

(未完待續(xù)......)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市筛峭,隨后出現(xiàn)的幾起案子铐刘,更是在濱河造成了極大的恐慌,老刑警劉巖影晓,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镰吵,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡挂签,警方通過(guò)查閱死者的電腦和手機(jī)疤祭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)饵婆,“玉大人勺馆,你說(shuō)我怎么就攤上這事±卜” “怎么了谓传?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)芹关。 經(jīng)常有香客問(wèn)我续挟,道長(zhǎng),這世上最難降的妖魔是什么侥衬? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任诗祸,我火速辦了婚禮,結(jié)果婚禮上轴总,老公的妹妹穿的比我還像新娘直颅。我一直安慰自己,他們只是感情好怀樟,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布功偿。 她就那樣靜靜地躺著,像睡著了一般往堡。 火紅的嫁衣襯著肌膚如雪械荷。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天虑灰,我揣著相機(jī)與錄音吨瞎,去河邊找鬼。 笑死穆咐,一個(gè)胖子當(dāng)著我的面吹牛颤诀,可吹牛的內(nèi)容都是我干的字旭。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼崖叫,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼遗淳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起心傀,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤洲脂,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后剧包,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡往果,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年疆液,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陕贮。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡堕油,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出肮之,到底是詐尸還是另有隱情掉缺,我是刑警寧澤,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布戈擒,位于F島的核電站眶明,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏筐高。R本人自食惡果不足惜搜囱,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望柑土。 院中可真熱鬧蜀肘,春花似錦、人聲如沸稽屏。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)狐榔。三九已至坛增,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間荒叼,已是汗流浹背轿偎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留被廓,地道東北人坏晦。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親昆婿。 傳聞我的和親對(duì)象是個(gè)殘疾皇子球碉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容