最初教程由Jawwad Ahmad原創。Kevin Colligan更新支援iOS 11、Xcode 9和Swift 4。
iOS開發當中是不是遇到需要動態新增或者刪除檢視元素的需求。你是上基hub去嗨一遍第三方庫還是自行擼frame,或者用Auto Layout的約束更新。就這點破需求,反正我是堅決不選前兩者,即便是萬不得已,最多也是更新約束(雖然Auto Layout的所見即所得極大縮短了開發耗時,增加我擼貓的快樂時光,但不得不承認我是很反感用程式碼去改約束,視覺化編碼還要去code約束,腦子有病!!!)。 處理這類需求,總之一句話。
好在,現在大傢伙應該都只支援到iOS9了,用上UIStackView
來處理這類需求簡直是美滋滋,艾瑪真香~~~
在本教程中,闊以瞭解UIStackView
如何提供一種簡單的方式來處理水平或垂直佈局。還闊以瞭解如何通過使用對齊、分佈和間距等屬性來獲取檢視,以便自我調整方便自適應。
本教程假定觀者基本熟悉Auto Layout。如果不熟悉,請移步傳送門Beginning Auto Layout。
教程開始
在這份UIStackView
教程中,我們將使用一個名為“Vacation Spots”的應用程式。這個簡單的應用闊以展示度假地點的一些基礎資訊。
不要方方髒髒的就開始動手,因為有幾個問題需要我們先用UIStackView
來處理下,而且比單獨使用自動佈局要簡單得多。首先下載這個UIStackView教程的入門專案。開啟專案並在iPhone模擬器上執行。你會看到一份度假地點清單。
點選London那一欄,進入倫敦的資訊檢視。乍一看,好像還不錯,但有幾個問題。
- 看看檢視底部的一排按鈕。他們目前的位置是固定的,所以他們不適應螢幕寬度。不信按Command - left將模擬器橫向來看看。
- 點選WEATHER旁邊的Hide按鈕。它成功地隱藏了文字,但是它沒有重新定位它下面的部分,留下一塊空白。
- 可以改進這些部分的展示順序。如果what to see部分正好位於why visit部分之後,而不是在兩者之間有weather部分,這將會更合乎邏輯。
- 在橫屏模式下,按鈕的底部離檢視的底部邊緣太近了一點。如果能減少元素之間的間距,那就更好看。
既然我們已經羅列好了改進工作,那就動手開始吧!!!
開啟Main.storyboard。第一次這樣做時,會要求您選擇初始裝置檢視。這個檢視在執行時沒有效果,它針對不同的裝置調整大小,也只是讓故事板的使用變得更加容易。iPhone 7或者8就行了。
你闊以隨時點選故事板的操作區域的正下方,選擇View As: iPhone 7 ,或者隨便改變也行。 現在開啟Spot Info View Controller看看,這東一塊西一坨的顏色是什麼鬼?這些標籤和按鈕的各種背景色,執行時會被清除。在Storyboard中,它們只是視覺上的輔助,幫助顯示改變StackView的各種屬性將如何影響其嵌入檢視的佈局。
你現在不用管這些,如果在執行應用程式的時候你真的想看到背景色,你可以在SpotInfoViewController內部的**viewDidLoad()**中臨時註釋出以下幾行。
// Clear background colors from labels and buttons
for view in backgroundColoredViews {
view.backgroundColor = UIColor.clear
}
複製程式碼
此外,所有的UI元素都使用了明確的佔位內容,以便連結屬性的時候更加明確方便,減小連錯的可能性。
@IBOutlet weak var whyVisitLabel: UILabel!
複製程式碼
叨逼叨完了,let’s get started!
第一次
使用stack view
第一件事是修復按鈕底部行之間的間距。stack view
可以以各種方式沿其軸分佈其檢視,其中一種方式是每個檢視之間的間距相等。
蘋果為了推廣新控制元件,我們可以直接把想要的處理的控制元件元素直接一鍵壓入stack view
。開啟Spot Info View Controller故事版場景,按住Command
選中底部三個按鈕。
點選右下角Show Document Outlin:
在左側控制元件集列表內確認下是不是選對了按鈕:
選中後,單擊故事板畫布右下角自動佈局工具欄中的“新建堆疊”按鈕:
按鈕將嵌入到新的堆疊檢視中:
現在出現了約束警告,
stack view
只是一種排版形式,是繼承自UIView,它也是需要約束來確定frame,只有確定好自身的frame以後才能處理內部控制元件佈局。
在stack view
中嵌入控制元件時,將刪除對其的任何約束。例如,在將按鈕嵌入stack view
之前,Submit Rating按鈕的頂部有一個連線到Ratinglabel的垂直間距約束:
點選選中Submit Rating按鈕可以看到它不在包含任何約束。
也闊以通過**Size inspector (⌥⌘5)**來檢視控制元件約束情況:
接下來我們需要為stack view
新增約束控制,如果整個頁面的控制元件太多導致不好選中某個控制元件,直接從左側的控制元件列表就好。
另外有個小技巧,按住Shift
再單擊右鍵,Xcode會幫大忙。
最後通過右下角的Auto Layout toolbar新增約束即可。
保持Constrain to margins選中,然後新增如下約束。
Top: 20, Leading: 0, Trailing: 0, Bottom: 0
複製程式碼
按照如圖所示輸入數字直接tab就好,簡單而快速的新增約束才是敏捷開發的首選。
控制元件之間現在看起來像是這樣,stack view
會拉伸第一個控制元件來填充空間。
決定stack view
內部控制元件的分佈屬性為distribution
。目前,它被設定為Fill
,這意味著被包含的控制元件將沿著其軸完全填充。為了實現這一點,stack view
將只拉伸其中一個檢視來填充額外的空間;具體來說,它會拉伸content hugging
優先順序最低的檢視,或者如果所有控制元件優先順序相同,它拉伸第一個檢視。
然鵝,我們只是向他們等距分佈就好。切換到Attributes inspector屬性編輯器,把Distribution的值從Fill改到Equal Spacing即可:
跑一下看看底部按鈕是不是等距分佈了,無論是豎屏還是橫盤都是穩穩的,不過還不夠完美,當你切換到小螢幕上,比如使用iPhone SE模擬器來跑一下看看。
好在蘋果也考慮到這種情況,我們改變Distribution屬性,從Equal Spacing改為Fill Proportionally,spacing值改為10。
現在,在iPhone SE模擬器上再跑一次,這次必須棒棒噠。
咋樣,第一次上手stack view
484真香,so easy。
在沒有stack view
,你必須使用間隔檢視來佔位,每對按鈕之間有一個佔位。要正確定位間隔檢視,您必須向所有間隔檢視新增等寬約束以及許多附加約束。
它看起來會像下面這樣。為了在螢幕截圖中可見,間隔檢視被賦予了淺灰色背景:
如果只是偶爾這麼做一次其實還好,如果做多了,小夥子身體可吃不消啊。特別是動態新增檢視這種。比如隱藏例子中的WEATHER這種。
現在我們只是簡單的設定間距值,如果你想在某個檢視中設定特別的間距,在iOS11裡提供的新的Api: setCustomSpacing:afterView。
沙場老司機
經過上面的演練,下面我們就能很輕鬆的把SpotInfoViewController裡需要的結構轉換成stack view
。
評級部分
選中評級RATING部分的兩個控制元件。
接著還是同樣的點選Stack按鈕將其嵌入到stack view
裡。
這次我們只需要新增三個約束即可:
Top: 20, Leading: 0, Bottom: 20
複製程式碼
spacing值這是為8
:
你可能會看到一個錯位的檢視警告,如下圖所示,其中星星標籤已經超出了檢視的範圍(這種警告可能因為某些beta版本的原因才會出現,沒出現那就恭喜你繼續):
如果確定自己的約束是正確的,因為Xcode某些版本的bug引起的檢視錯位,直接用右下角的Refresh Layout按鈕矯正即可。
現在看起來就正常了。
還原檢視
如果發現有的時候手殘,或者是處於實驗性目的,多上了stack view
,直接選中需要撤銷的控制元件,按住Option,左擊Stack選Unembed即可撤銷。
或者選中控制元件從選單Editor \ Unembed撤銷也行。
試試第一個垂直方式
現在我們來建立垂直方式的堆疊檢視,選中WHY VISIT和**<whyVisitLabel>**兩個文字控制元件。
Xcode會根據控制元件的分佈排列來確實方向,就比如現在這樣,直接生成的就是垂直分佈的堆疊檢視。
現在我們來確定約束,只需要把這部分的上左右約束都設定為0,只不過底部約束的物件是相對WEATHER控制元件,間距是20,一看即懂。
設定完後預設對齊是左對齊,如下圖所示:
對齊屬性
對齊屬性確定堆疊檢視如何垂直於其軸佈局。對於垂直堆疊檢視,可以設定的對齊方式為:
- 填充·Fill
- 靠左對齊·Leading
- 居中對齊·Center
- 靠右對齊·Trailing
水平堆疊檢視對齊屬性的值略有不同:
水平佈局的對齊方式分別為:
- 頂部對齊·Top
- 靠左對齊·Leading
- 底部對齊·Bottom
- 靠右對齊·Trailing
- 第一元素基準線·FirstBaseline:意思是以第一個UI元素的的內容基準線為準,後面元素內容以此基準線對齊。
- 最後元素基準線·LastBaseline:意思是以最後一個UI元素的的內容基準線為準,前面元素內容以此基準線對齊。
大家闊以選中剛才設定好的垂直堆疊檢視,調整下對齊屬性看下變化就淺顯易懂了。 填充·Fill:
靠左對齊·Leading: 居中對齊·Center: 靠右對齊·Trailing:跑起來看一下佈局穩當冇問題。
轉換“what to see”部分
這部分轉換和前面一節沒啥區別,直接操作關鍵點即可。
- 首先選中WHAT TO SEE和**<whatToSeeLabel>**兩個文字元素。
- 其次壓入堆疊檢視。
- 對齊方式選中填充·Fill。
- 最後設定好如下約束即可。
Top: 20, Leading: 0, Trailing: 0, Bottom: 20
複製程式碼
設定完以後大概就長這樣:
轉換“weather”部分
由於需要隱藏天氣,所以這部分是比較難搞的。
我們先把這部分的所有元素都壓入堆疊檢視。先選中WEATHER文字欄和Hide按鈕,壓入水平堆疊檢視,然後在按住Command選中下面的**<weatherInfoLabel>**文字欄部分,將水平堆疊檢視和這部分一起壓入垂直堆疊檢視。
其實可以發現,水平的堆疊檢視被Hide按鈕給頂開了,如果覺得不喜歡,闊以選中底部對齊的方式來看看效果。
也還是不太滿意,那根據我的經驗,要不就是修改水平堆疊檢視內UI元素的約束優先順序,要不就是把Hide按鈕給拆出去,單獨處理約束。為了容易理解我們還是單獨處理。
自行撤回到天氣部分的原始轉態。選中WEATHER和**<weatherInfoLabel>**兩個文字欄部分:
將其壓入垂直堆疊檢視:
然後設定好如下約束:
Top: 20, Leading: 0, Trailing: 0, Bottom: 20
複製程式碼
對齊方式為填充:
因為Hide按鈕被拆出去,所以我們需要對其新增約束以保證按鈕位置的正確性。因為需要設定約束的相對控制元件為WEATHER文字欄,所以我們還得再改造改造。 直接在控制元件列表欄選中WEATHER或使用組合拳Control-Shift-click:
壓入堆疊檢視:
對齊方式·Alignment選中為靠左對齊·Leading,排版·Axis確定為垂直·Vertical排版:
執行看看是啥情況:
我頂你個肺,啥情況,按鈕跑偏了?實際上是我們拆出Hide按鈕之後忘記給設定約束。 按住Control選中Hide按鈕拖動指向到WEATHER文字欄新增約束:
只要完成如下兩條約束,間距和縱向位置的約束即可:
再重新跑一下,這回就必須妥當了。
堆疊化
在左側控制元件列表裡選中我們之前所有做個堆疊化的控制元件:
全部壓入堆疊:
然後給最外層堆疊檢視新增約束,確保勾選上Constrain to margins,四邊邊距約束為0即可。 然後間隙·Spacing設定為20,對齊方式·Alignment設定為填充·Fill。
再次執行還是會有剛才那個屌問題。
只要記住如果有外設相對控制元件的約束,只要堆疊化,約束就會被幹掉,只能再次新增了:
完善層級
最後我們需要把what to see的部分挪動到weather部分上面去,最好的方式是在左側控制元件列表裡拖動:
如果在Storyboard裡直接去拖動,很可能會因為選錯控制元件元素而導致打亂結構。
根據螢幕大小配置間隙
由於在橫屏的情況下,垂直方向上的空間比較珍貴,所以針對橫屏,把間隙改為10比較穩妥。 找到間隙·Spacing的左側的**+**號按鈕:
選擇Any Width > Compact Height,然後新增·Add Variation:
給wAny hC欄新增值為10即可:
最後執行一下看看,包括橫屏(⌘←)也看看,484看起來比較舒爽了。
動畫
當前的隱藏和呈現動畫效果看起來有木有點生硬,感覺怪怪的。我們還是新增點動畫讓細節看起來比較絲滑為妙。
開啟SpotInfoViewController.swift,定位到updateWeatherInfoViews(hideWeatherInfo:animated:)
方法,現在看起來長這樣:
weatherInfoLabel.hidden = shouldHideWeatherInfo
複製程式碼
替換成如下程式碼即可:
if animated {
UIView.animate(withDuration: 0.3) {
self.weatherInfoLabel.isHidden = shouldHideWeatherInfo
}
} else {
weatherInfoLabel.isHidden = shouldHideWeatherInfo
}
複製程式碼
找到@IBAction func weatherHideOrShowButtonTapped(_ sender: UIButton)
方法,替換成如下程式碼:
@IBAction func weatherHideOrShowButtonTapped(_ sender: UIButton) {
let shouldHideWeatherInfo = sender.titleLabel!.text! == "Hide"
updateWeatherInfoViews(hideWeatherInfo: shouldHideWeatherInfo, animated: shouldHideWeatherInfo)
shouldHideWeatherInfoSetting = shouldHideWeatherInfo
}
複製程式碼
這樣跑一下看,隱藏起來就不會辣麼尬了。。。
寫在最後
一切照舊,懶癌患者或者閱讀理解不過關的胖友闊以直接下載最終程式碼。
有關UIStackView
的使用就到這裡,關於使用UIStackView
做高階動畫是稍稍有點複雜的,以後有空還是需要專門開一篇來做介紹。