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