
概覽
在 SwiftUI 的世界里,我們無(wú)數(shù)次都?jí)粝胫晥D可以自動(dòng)根據(jù)布局上下文“因勢(shì)而變”。大多數(shù)情況下,SwiftUI 會(huì)將每個(gè)視圖尺寸處理的井井有條,不過(guò)在某些時(shí)候我們還是得親力親為。

如上圖所示,無(wú)論頂部 TabView 容器里子視圖高度如何變化,TabView 本身的高度都能“隨遇而安”。如何用最簡(jiǎn)單、最現(xiàn)代化、最有趣且最切中要害的方法讓容器尺寸與子視圖的高度“如影隨形”呢?
在本篇博文中,您將學(xué)到如下內(nèi)容:
- 為什么要讓視圖高度自適應(yīng)?
- 初始工作
- 最古老的方法:GeometryReader
相信學(xué)完本課后,小伙伴們必能腦洞大開(kāi)、格局打開(kāi),用“千姿百態(tài)”的方法讓問(wèn)題的解決一發(fā)入魂、九轉(zhuǎn)功成!
那還等什么呢?Let‘s go!??!;)
1. 為什么要讓視圖高度自適應(yīng)?
對(duì)于一個(gè)具體的示例來(lái)說(shuō),設(shè)想這樣一種場(chǎng)景:每個(gè) SwiftUI 子視圖都包含了長(zhǎng)度不定的文本,如何穩(wěn)妥而優(yōu)雅的據(jù)此設(shè)定它們父容器的高度呢?
在下面的代碼中,我們的父容器 TabView 包含了若干個(gè) likeIdiomCard 視圖,每個(gè) likeIdiomCard 視圖對(duì)應(yīng)一條自己喜愛(ài)的成語(yǔ),其中每條成語(yǔ)又含有不固定長(zhǎng)度的文本來(lái)表示自身的釋義:
TabView {
ForEach(likeIdioms.chunked(into: 2), id: \.self) { idiomChunk in
HStack {
ForEach(idiomChunk) { idiom in
likeIdiomCard(idiom)
}
if idiomChunk.count == 1 {
Rectangle()
.foregroundStyle(.clear)
}
}
}
}
.tabViewStyle(.page)
.frame(height: 200)

注意,為了簡(jiǎn)便起見(jiàn)我們將整個(gè) TabView 的高度設(shè)置為了 200。不過(guò),這樣做的缺點(diǎn)也顯而易見(jiàn):
- 當(dāng)成語(yǔ)釋義太短時(shí),固定高度造成空間浪費(fèi);
- 當(dāng)成語(yǔ)釋義太長(zhǎng)時(shí),固定高度又會(huì)造成空間不足;
如下圖所示,左側(cè)的成語(yǔ)釋義很短,會(huì)導(dǎo)致底部殘留過(guò)多空間,而右側(cè)釋義很長(zhǎng)的成語(yǔ)會(huì)迫使 likeIdiomCard 在有限的空間里居中顯示,造成頂部成語(yǔ)名稱顯示不全。

在這個(gè)接地氣的例子中,如果 TabView 的高度可以根據(jù)每個(gè) likeIdiomCard 視圖的高度自動(dòng)調(diào)整那就天衣無(wú)縫了。
當(dāng)然,我們完全可以用自定義布局(Layout)來(lái)讓挑戰(zhàn)大功告成,可是這頗有些“導(dǎo)彈打蚊子”的感覺(jué)。不過(guò)為了確保整個(gè)討論的完整性,我們?nèi)匀粫?huì)在本系列最后一篇文章介紹如何用自定義布局完成 SwiftUI 視圖高度自適應(yīng)的第 6 種實(shí)現(xiàn)。
下面,就讓我們先選出 5 種稍顯“輕量級(jí)”的方法來(lái)讓問(wèn)題迎刃而解吧。
2. 初始工作
首先,為了保存所有 likeIdiomCard 子視圖的最大高度,我們需要在主視圖中創(chuàng)建一個(gè) maxHeight 狀態(tài):
@State private var maxHeight = 0.0
我們的基本思路是,依次獲取每個(gè) likeIdiomCard 視圖的高度,并始終將最大的那個(gè)保存到 maxHeight 里。
最后,我們只需設(shè)置 TabView 的高度為 maxHeight 即可:
TabView {
//...
}
.tabViewStyle(.page)
.frame(height: maxHeight)
3. 最古老的方法:GeometryReader
早在 SwiftUI(iOS 13)誕生那天,GeometryReader 視圖就作為一個(gè)重要成員與之?dāng)y手并肩了。

如果我沒(méi)記錯(cuò),GeometryReader 至今已經(jīng)快 6 年了。雖然有許多不足之處,但它們并不影響我們使用 GeometryReader 來(lái)得償所愿:
likeIdiomCard(idiom)
.background {
GeometryReader { proxy in
if proxy.size.height > maxHeight {
maxHeight = proxy.size.height
}
return Color.clear
}
}
在上面的代碼中,我們機(jī)智的將 GeometryReader 作為 likeIdiomCard 的背景,這樣做避免了它們的尺寸“各持己見(jiàn)”。利用 @ViewBuilder 語(yǔ)法的強(qiáng)大威力,我們將 maxHeight 狀態(tài)的設(shè)置邏輯與返回“占位”視圖完美的“融為一體”。
在下篇博文中,我們將繼續(xù) SwiftUI 視圖尺寸適配之旅,介紹更多有趣的方法,不見(jiàn)不散!
總結(jié)
在本篇博文中,我們介紹了為何要讓 SwiftUI 容器與子視圖的尺寸“唇齒相依”,并討論了一種“最古老”的解決之道。
感謝觀賞,我們下篇見(jiàn)!8-)