利用 Stack,Rectangle & overlay 實現元件一樣大,固定比例 & 照片牆

製作 SwiftUI iOS App 頁面時,我們時常想讓元件一樣大或維持固定比例,比方照片牆頁面和顯示 16:9 或正方形的照片。想實現這樣的功能有很多方法,比方以下兩種方法:

方法 1: 透過 Stack,Rectangle & overlay。

方法 2: 利用 GeometryReader 實現元件一樣大的排版。

現在就讓我們以可愛的幾米插畫圖片為例實驗看看第一種做法吧。

照片牆頁面實作

我們想要一排有 2 張照片,照片等寬,高度 250。首先讓我們試試感覺 ok 但實際上有問題的寫法。

struct ContentView: View {
var body: some View {
HStack {
ForEach(1 ..< 3) { index in
Image("pic\(index)")
.resizable()
}
}
.frame(height: 250)
}
}

如下圖所示,現在 2 張圖等寬,而且高度也是 250。看來似乎十分完美。但請放大眼睛再看清楚點,圖片已經變形了呀。

不然加個 scaledToFit 讓圖片維持比例吧。

struct ContentView: View {
var body: some View {
HStack {
ForEach(1 ..< 3) { index in
Image("pic\(index)")
.resizable()
.scaledToFit()
}
}
.frame(height: 250)
}
}

如下圖所示,scaledToFit 將讓照片不變形,但因為留白的關係,左邊圖片的高度不再是 250。

不然我們試試 scaledToFit 的好兄弟 scaledToFill 吧。

struct ContentView: View {
var body: some View {
HStack {
ForEach(1 ..< 3) { index in
Image("pic\(index)")
.resizable()
.scaledToFill()
}
}
.frame(height: 250)
}
}

如下圖所示,照片的高度現在變 250 了,但是 2 張圖卻不再等寬。

到底要如何讓圖片等寬,高度固定 250 而且還不變形呢 ? 接下來就讓我們看看 Rectangle 的威力吧。

struct ContentView: View {
var body: some View {
HStack {
ForEach(1 ..< 3) { index in
Rectangle()
}
}
.frame(height: 250)
}
}

我們在 HStack 裡放 2 個代表長方形的 Rectangle,由於 Rectangle 的世界人人平等,所以很自然地 2 個 Rectangle 平分 HStack 的空間。

接下來我們再用 overlay 將 Image 加在 Rectangle 上,控制圖片的大小等同於 Rectangle。

struct ContentView: View {
var body: some View {
HStack {
ForEach(1 ..< 3) { index in
Rectangle()
.overlay(
Image("pic\(index)")
.resizable()
.scaledToFill()
)
.clipped()

}
}
.frame(height: 250)
}
}

剛剛的程式有個要特別注意的地方,要記得從 Rectangle 呼叫 clipped,這樣才能將圖片超出 Rectangle 的部分切掉,以下即為忘了呼叫 clipped 的慘劇。

現在我們距離完成照片牆只差最後一步了。剛剛的程式再搭配 List,即可實現顯示多張照片的照片牆。(ps: List 裡的 Row 預設水平排列,所以可省略 HStack)

struct ContentView: View {
var body: some View {
List(0 ..< 5) { index in
ForEach(1 ..< 3) { index in
Rectangle()
.overlay(
Image("pic\(index)")
.resizable()
.scaledToFill()
)
.clipped()
}
.frame(height: 250)
}
}
}

設定圖片以固定比例呈現

接下來我們再試試讓圖片以固定比例呈現,我們希望圖片寬度等於螢幕寬度,寬高比 3:4。

首先我們一樣先苦後甘,先試試感覺 ok 但實際上有問題的寫法。

Image("pic1")   .resizable()   .aspectRatio(0.75, contentMode: .fit)

我們利用 aspectRatio 控制寬高比,因此以上程式 Image 的寬高比為 0.75,也就是 3:4,然而此時圖片卻變形了。

也許是 contentMode 的問題,我們將它從 fit 改成 fill 試試。

Image("pic1")   .resizable()   .aspectRatio(0.75, contentMode: .fill) 

如下圖所以,圖片比的確是 3:4,但是它卻變成幾乎整個填滿螢幕,而且還有部分超出。

所以我們又要靠 Rectangle 來救我們了。當我們在 Rectangle 上呼叫 aspectRatio(0.75, contentMode: .fit) 時,它將乖乖聽話地維持比例 3:4,而且寬度等於螢幕的寬度。

struct ContentView: View {
var body: some View {
Rectangle()
.aspectRatio(0.75, contentMode: .fit)

}
}

接著我們再用一樣的技巧,利用 overlay 將圖片疊在它身上,即可實現圖片寬度等於螢幕寬度,寬高比 3:4 的心願。

struct ContentView: View {
var body: some View {
Rectangle()
.aspectRatio(0.75, contentMode: .fit)
.overlay(
Image("pic1")
.resizable()
.scaledToFill()
)
.clipped()
}
}

掌握利用 Stack 跟 Rectangle 排版,然後用 overlay 疊加圖片的技巧後,我們可以實現各種有趣的畫面,比方以下一排顯示 3 張正方形照片的照片牆。

struct ContentView: View {
var body: some View {
List(0 ..< 5) { index in
ForEach(1 ..< 4) { index in
Rectangle()
.aspectRatio(1, contentMode: .fit)
.overlay(
Image("pic\(index)")
.resizable()
.scaledToFill()
)
.clipped()
}
}
}
}

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com