SwiftUI – 一起來仿寫微信APP之一首頁列表視圖

簡介

最近在學習 SwiftUI ,我一般都是先去學習介面布局,所以就想著仿寫一下經常使用的軟體的介面,所以先拿微信開刀。因為不想一次性發太多的內容,所以只好將主題分解,一部分一部分地去講,接下來我們一起來學習吧。

如果你嘗試過使用 SwiftUI 編寫介面,你會發現是如此地舒心,我已深深地愛上了它。當然它的坑並不少,畢竟才剛出來,最低支援系統是 iOS13,估計還得等個幾年才會慢慢在公司里使用上吧。但是這並不妨礙我們的學習。

在這篇文章里,我會一步一步編寫微信的首頁列表視圖,一步一步將程式碼呈現上來,並仔細地講解,我相信你們都可以看懂的,先來看看效果圖。

很簡單吧?是很簡單,但是在編寫的時候還是有些技巧在裡面,畢竟,簡單才不容易勸退嘛。在開始前先講一下這篇文章將會用到的一些布局與組件,先給大家一個印象,方便後面的閱讀理解。

HStack – 水平布局

VStack – 垂直布局

Text – 文本控制項

Spacer – 擴展空間,使容器填滿布局空間

Image – 影像控制項

List – 列表控制項

Divider – 分隔線控制項

工作環境

Xcode – Version 11.3.1 (11C504)

Swift – version 5.1.3 (swiftlang-1100.0.282.1 clang-1100.0.33.15)

開始編寫程式碼

編寫列錶行

我們來先把頭像添加進來。

Image("1")
    .resizable() // 1
    .frame(width: 46, height: 46) // 2
    .cornerRadius(6)// 3

1 – 在 SwiftUI 中,如果需要控制影像的大小,則必須先調用resizable修飾

2- 設置影像大小

3 – 設置圓角大小,四個角的大小都相同

因為布局是橫向的,所以我們在外層使用HStack包裹起來,然後添加聯繫人名字和最後發消息時間。

HStack {
    // 頭像

    HStack {
        Text("女神")
            .font(.body) // 1

        Spacer()

        Text("下午 2:55")
            .font(.caption)
            .foregroundColor(Color.gray.opacity(0.5)) // 2
    }
}

1 – 使用 font 修飾字體,這裡使用了蘋果提供的標準字體,蘋果還提供了 largeTitle, title, headline, subheadline, body, callout, footnote, caption。

2 – 使用 foregroundColor 修飾字體顏色,因為 gray 的灰色還是太黑了,所以這裡又使用了 opacity 去修飾透明度為50%,使它顯得更淡一點。

名字下方顯示的是是最後發送或接收的消息內容,因此我們在外層使用 VStack 包裹起來。

VStack(alignment: .leading, spacing: 6) { // 1
    // 名稱和時間

    Text("對不起,你是個好人")
        .font(.callout)
        .foregroundColor(Color.gray)
}

// 1 – 設定VStack里子控制項居左對齊,默認是居中對齊。再設定子控制項的間隙為 6 個像素,這樣比較符合微信上面的設計。

現在樣子已經出來了,我們先預覽下效果。

我們給最外層的HStack增加padding,使它更美觀一些,參數填寫.all代表四周都需要邊框。經過我的眼力觀察,它的默認是 16px 的樣子。

HStack {
    // 頭像、名稱、時間、消息內容
}
.padding(.all)

有了間距,好看多了。

接下來創建一個視圖,它負責裝載行視圖,起名為GCMainRow

struct GCMainRow: View {
    var body: some View {
        HStack {
            Image("1")
                .resizable()
                .frame(width: 46, height: 46)
                .cornerRadius(6)
            
            VStack(alignment: .leading, spacing: 6) {
                HStack {
                    Text("女神")
                        .font(.body)

                    Spacer()

                    Text("下午 2:55")
                        .font(.caption)
                        .foregroundColor(Color.gray.opacity(0.5))
                }
                Text("對不起,你是個好人")
                    .font(.callout)
                    .foregroundColor(Color.gray)
            }
        }
        .padding(.all)
    }
}

然後在ContentView改為調用GCMainRow(),這樣程式碼就好看很多了。

struct ContentView: View {
    var body: some View {
        GCMainRow()
    }
}

編寫列表 List

好了,現在讓我們來編寫列表視圖吧。我們在最外層使用List包裹GCMainRow,循環 20 個視圖,數據多點才可以讓我們滾動。

List(0 ..< 20) { _ in // 1
    GCMainRow()
}

1 – 因為我們不需要用到循環的一些數據,所以我們使用 _ 去忽略它。

List控制項默認的都會有邊距,下圖黃色是GCMainRow的大小,可以看得出來旁邊有空白的填充,這對我們當前的設計來說不太友好,因此我們需要想辦法去掉這些邊距填充。

List提供了listRowInsets來控制行的邊距(上下左右),我們來試著使用一下。

List(0 ..< 20) { item in
    GCMainRow()
        .listRowInsets(EdgeInsets())
}

我們發現,這樣寫是沒有作用的,listRowInsets的生效條件是「不能直接在List中使用,需要配合For Each語句才能生效」,我們再修改一下程式碼。

List {
    ForEach(0 ..< 20) { item in
        GCMainRow()
            .listRowInsets(EdgeInsets())
    }
}

好了,這次生效了,這就是我們要的結果。

自定義分隔線

我們仔細觀察一下分隔線,在微信里分隔線是左對齊在名稱和消息內容的,所以我們需要把現有的分隔線隱藏掉,然後再實現它。

在這裡講解一下,List是基於UITableView去實現的,這意味著我們可以通過appearance全局修改它的所有屬性,正如我們現在需要取消它默認的分隔線,將separatorStyle設置為.none即可。

init() {
    UITableView.appearance().separatorStyle = .none
}

因為分隔線是貼著右邊緣的,所以我們需要在包裹著名稱、時間、消息內容的VStack外層再包裹一層VStack,在其中再添加分隔線Divider,並將裡層的VStack設定右邊距,最後將最外層的HStackpadding改為上和左邊距。是不是聽得有點懵?沒關係,看看程式碼就很容易理解了。

HStack(alignment: .top) { // edit
    // 頭像
    VStack { // new
        VStack(alignment: .leading, spacing: 6) {
            // 名稱、時間、消息內容
        }
        .padding(.trailing) // new
        Divider() // new
    }
}
.padding(.top) // edit
.padding(.leading) // edit

我們現在來看一下效果。

未讀消息小紅點

未讀消息在頭像的右上方,小紅點的中心點是位於頭像的右上頂端。我們可以使用overlay疊加一個視圖,來製作小紅點吧。

Image("1")
		// ...
    .overlay(
        Color.red // 1
            .frame(width: 16, height: 16)
            .cornerRadius(8)
            .offset(x: 23, y: -23) // 2
)

// 1 – Color 本身也是一個視圖組件,這是官方的定義 @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) extension Color : View { }

// 2 – 設定視圖的偏移量,那麼23是怎樣得出來的呢?很簡單,因為默認的overlay視圖是位於父視圖的中心,那麼我們要將它放置在右上角,那麼只需要寬高都除以2就可以了,那麼這裡的結果就是,x軸增加23px,y軸減少23px

接下來是未讀數量,和上面的相似,在Color視圖上利用overlay疊加Text視圖就可以了。

Color.red
    .overlay( // 1
        Text("1")
            .font(.caption)
            .foregroundColor(.white)
)
    .frame(width: 16, height: 16)
    .cornerRadius(8)
    .offset(x: 23, y: -23)

// 1- 值得注意的是,因為文本也是需要偏移到右上角的,所以必須放在前面,不然它默認就是居中的

至此,程式碼演示結束,預覽一下靜態圖,文章開頭有 gif 動態圖效果。

總結

好了,這篇文章就到這裡,篇幅有點長了。不過沒關係,我們來總結一下關鍵點:

  1. List默認是有行邊距的,要取消或修改它的行邊距,我們必須通過For Each再配合上listRowInsets才能實現。
  2. List是基於UITableView去實現的,這意味著我們可以通過appearance全局修改它的所有屬性。
  3. 使用overlay可以給視圖疊加一個視圖。

Demo 源碼下載

我已經把 Demo 上傳至 GitHub 上面,項目名字是 SwiftUI-Tutorials,目錄名為GCWechatList,有需要的朋友可以去下載運行一下,當然你也可以跟著文章去做一遍,這樣更有利於你掌握此方面的知識。

文章篇幅有點長,雖然教的東西也挺簡單,但概述得比較詳細。任何東西都是先從簡單入手的,才不會造成勸退不是嗎?哈哈,此文章針對於新手而言還是很友好的,對於已經會的人來講就可能廢話有點多了,如果必須要噴,請輕噴,我比較玻璃心。

如果本文章對你有幫助,請關注我,你的關注就是我後續寫文章的動力,下期會更精彩噢!

關於作者

博文作者:GarveyCalvin

微博://weibo.com/feiyueharia

部落格園://www.cnblogs.com/GarveyCalvin

本文版權歸作者,歡迎轉載,但必須保留此段聲明,並給出原文鏈接,謝謝合作!

公眾號

作者第一次運營公眾號,請你們一定要關注我的公眾號,給我點動力,後期主要運營公眾號為主。這是第二篇發布的文章,需要你們的支援,謝謝你們!

QQ群

一起討論 SwiftUI,群主喜歡看熱鬧,當吃瓜人員。進來時填寫你在哪裡看到此文章的,並介紹下自己,一句話就行。

Tags: