iOS App 程式開發

SwiftUI 小技巧:利用 Stack 簡單構建彈性的卡片視圖

SwiftUI 框架讓我們輕易構建 App UI。在這篇文章中,你將實作一個常見的手機 UI 設計 —— Card UI。透過使用堆疊 (HStack 和 VStack)、圖像、和文本視圖,來創建一個能夠接受不同圖片與文字的彈性卡片視圖 (card view)。
SwiftUI 小技巧:利用 Stack 簡單構建彈性的卡片視圖
SwiftUI 小技巧:利用 Stack 簡單構建彈性的卡片視圖
In: iOS App 程式開發, Swift 程式語言, SwiftUI 框架, Xcode

歡迎閱讀 SwiftUI 小技巧系列教程!在這篇文章中,我們將實作一個常見的手機 UI 設計 —— Card UISwiftUI 框架讓我們輕易構建 App UI,我們會在文章中展示這一點。你將會看到我們可以使用堆疊 (stack)、圖像、和文本視圖,來像這樣創建卡片視圖 (card view)。

swiftui-card-ui-view

請注意,本篇教程需要你在 macOS Catalina (v10.15)上運行 Xcode 11。

建立一個卡片式的 UI

如果你還沒有開啟 Xcode,請打開它,並使用 Single View Application 模板來建立一個新專案。在下一個畫面,設定專案名稱為 SwiftUIScrollView (或你喜歡的名稱),並填入所需要的值。請確認你有在 User Interface 選項中選取 SwiftUI

如果你還不熟悉 SwiftUI,你可能會在 ContentView.swift 文件中為 UI 編寫程式碼。這當然沒有問題,但是我想向你展示一個更好的方法來組織程式碼。為了實作卡片視圖,讓我們為它建立一個單獨的文件。在專案導航器中,右鍵單擊SwiftUIScrollView,並選擇 New File…

User Interface 區塊,選取 SwiftUI View 模板並點擊 Next 來建立檔案。將檔案命名為 CardView,並儲存在專案資料夾內。

swiftui-card-ui-view-4

CardView.swift 中的程式碼與 ContentView.swift 的程式碼很接近。同樣地,你可以在畫布中檢視 UI。

準備圖片檔

現在我們準備要寫卡片視圖的程式碼。首先,你需要準備圖檔並將其匯入素材目錄。如果你不想要準備自己的圖片,你可以在這裡下載圖片檔,將圖片檔解壓縮後,選取 Assets.xcassets,並將其拖曳至素材目錄。

實作卡片視圖

現在切回 CardView.swift 檔。如果你再看一下以下圖片,這個卡片視圖是由兩個部分組成的:上面的部分是圖片,而下面的部分是文字敘述。

swiftui-card-ui-view

讓我們從圖片開始。我將會設計讓圖片可以調整大小,讓它因應畫面縮放,同時保持長寬比例。你可以撰寫程式碼如下:

struct CardView: View {
    var body: some View {
        Image("swiftui-button")
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}

如果你忘記了這兩個修飾器是甚麼,可以回去有關 Image 物件的章節閱讀一下。接下來實作文字敘述部分,你可以將程式碼撰寫如下:

VStack(alignment: .leading) {
    Text("SwiftUI")
        .font(.headline)
        .foregroundColor(.secondary)
    Text("Drawing a Border with Rounded Corners")
        .font(.title)
        .fontWeight(.black)
        .foregroundColor(.primary)
        .lineLimit(3)
    Text("Written by Simon Ng".uppercased())
        .font(.caption)
        .foregroundColor(.secondary)
}

很明顯的,你需要使用 Text 來建立文字視圖。因為我們在敘述中有三種文字視圖,以垂直方式並排,所以我們使用一個 VStack 來嵌入它們。對於 VStack,我們指定以 .leading 來對齊,這會將文字視圖對齊堆疊視圖的左側。

我們已經在之前的教學文章討論過這些 Text 的修飾器,如果你對任何一個修飾器有所疑問的話,可以回去參考看看。不過這裡要提出來說明的,是 .primary.secondary 顏色。當你在 foregroundColor 修飾器指定標準顏色,像是 .black.purple,iOS 13 導入一套系統顏色,包括了三原色 (primary colors)、三間色 (secondary colors) 以及再間色 (tertiary colors) 的變化,透過這個顏色變化,你的 App 可以很容易地支援淡色與深色模式,舉例來說,文字視圖的主色預設以淡色模式設為黑色。當 App 切換到深色模式,主色將會被調整成白色。這都會由 iOS 自動來調整,所以你不用再另外寫支援深色模式的程式碼。我們將在後面的章節深入討論深色模式。

要將圖片與這些文字視圖垂直排列,我們使用 VStack 來嵌入它們,目前的佈局如下圖所示。

但我們還沒完成,還有幾件事需要實作。首先,如果敘述區塊要對齊圖片的邊緣,該如何做呢?

依照我們所學,我們可以在一個 HStack 嵌入文字視圖的 VStack。然後,我們將使用一個彈性空間 (Spacer) 來將  VStack 往左推。讓我們來看看是否可行。

如果你已經變更程式碼如下,這個文字視圖的 VStack 會對齊其畫面的左側。不過,前端被截掉了。

調整佈局順序

文字堆疊與彈性空間 (spacer) 預設佔據了父視圖的一半,這也是標題無法完整呈現的原因。要解決這個問題,你需要使用 layoutPriority 修飾器來調整文字堆疊的佈局優先度 (layout priority)。數值越大表示優先度越高。這表示如果我們設定文字視圖的 VStack 佈局優先度為較大的值時,iOS 就會在配置空間給彈性空間前,提供更多空間來完全渲染文字視圖。下圖為最後的結果。

如果在 HStack 加上一些間距 (padding) 應該會更好。插入 padding 修飾器如下:

HStack {
    VStack(alignment: .leading) {
        .
        .
        .
    }
    .layoutPriority(100)

    Spacer()
}
.padding()

最後是外框部分。在上一篇文章,我們有討論過如何畫出具有圓角的外框。我們可以使用 overlay 修飾器,並使用 RoundedRectangle 來畫出外框。以下是完整的程式碼:

struct CardView: View {
    var body: some View {
        VStack {
            Image("swiftui-button")
                .resizable()
                .aspectRatio(contentMode: .fit)

            HStack {
                VStack(alignment: .leading) {
                    Text("SwiftUI")
                        .font(.headline)
                        .foregroundColor(.secondary)
                    Text("Drawing a Border with Rounded Corners")
                        .font(.title)
                        .fontWeight(.black)
                        .foregroundColor(.primary)
                        .lineLimit(3)
                    Text("Written by Simon Ng".uppercased())
                        .font(.caption)
                        .foregroundColor(.secondary)
                }
                .layoutPriority(100)

                Spacer()
            }
            .padding()
        }
        .cornerRadius(10)
        .overlay(
            RoundedRectangle(cornerRadius: 10)
                .stroke(Color(.sRGB, red: 150/255, green: 150/255, blue: 150/255, opacity: 0.1), lineWidth: 1)
        )   
        .padding([.top, .horizontal])
    }
}

除了外框之外,我也在頂部、和左右兩側分別加入了一些間距。現在,我們已經建立好卡片視圖的佈局了。

讓卡片視圖更彈性

雖然目前卡片視圖目前看起來沒問題,但我們是把圖片與文字用程式碼固定寫入。為了讓它更彈性,我們重構一下程式碼。首先,在 CardView 宣告 image、category、heading 與 author 這些變數:

var image: String
var category: String
var heading: String
var author: String

接下來,將 ImageText 視圖以這些變數替代:

VStack {
    Image(image)
        .resizable()
        .aspectRatio(contentMode: .fit)

    HStack {
        VStack(alignment: .leading) {
            Text(category)
                .font(.headline)
                .foregroundColor(.secondary)
            Text(heading)
                .font(.title)
                .fontWeight(.black)
                .foregroundColor(.primary)
                .lineLimit(3)
            Text(author.uppercased())
                .font(.caption)
                .foregroundColor(.secondary)
        }
        .layoutPriority(100)

        Spacer()
    }
    .padding()
}

做完變更之後,你將會在 CardView_Previews 結構中見到一個錯誤。這是因為我們在 CardView 導入了一些變數,所以在使用它時,我們要指定參數。

將程式碼以下面這段替代:

struct CardView_Previews: PreviewProvider {
    static var previews: some View {
        CardView(image: "swiftui-button", category: "SwiftUI", heading: "Drawing a Border with Rounded Corners", author: "Simon Ng")
    }
}

錯誤將會被修正,現在你已經建立了一個能夠接受不同圖片與文字的彈性 CardView。你可以基於構建好的內容,進一步應用此卡片視圖來構建一個流暢的列表 UI。

swiftui-card-view-list

你覺得這個  SwiftUI 小技巧教學系列怎樣呢?如果你喜歡本篇文章,覺得它對你有幫助,請留言讓我知道。另外,如果你對我們的下一個技巧有任何建議,也請留言告訴我們。

譯者簡介:王豪勳 -渥合數位服務創辦人,畢業於台灣大學應用力學研究所,曾在半導體產業服務多年,近年來專注於協助客戶進行 App 軟體以及網站開發,平常致力於研究各式最軟硬體技術,擁有多本譯作。

作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 17 App 程式設計實戰心法》、《iOS 17 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。