日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

SwiftUI - Grid View 的實(shí)現(xiàn)方法,逐步剖析助你實(shí)現(xiàn)

 頭號(hào)碼甲 2021-12-19

簡(jiǎn)介

在當(dāng)前正式 SwiftUI 版本而言,很多控件都是缺少的。比如在 UIKit 框架里有 UICollectionView 組件,可以很方便地做 Gird 格子類型的視圖。但是在 SwiftUI 這個(gè)框架里面,就沒(méi)有對(duì)應(yīng) UICollectionView 的組件。我們當(dāng)然可以用 UIViewRepresentable 來(lái)封裝一個(gè) UICollectionView ,但是本篇文章要探討的是,如何使用 SwiftUI 來(lái)實(shí)現(xiàn) Grid 格子視圖,現(xiàn)在一起來(lái)實(shí)現(xiàn)吧。

實(shí)現(xiàn)思考

在思考前,我們先來(lái)定義生成隨機(jī)顏色的函數(shù),后面會(huì)用到的。

extension Double {
    static func randomData() -> Double {
        Double(arc4random()) / Double(UInt32.max)
    }
}

extension Color {
    static func random() -> Color {
        .init(red: Double.randomData(), green: Double.randomData(), blue: Double.randomData())
    }
}

想必 HStack 橫向布局與 VStack 豎向布局你們已經(jīng)掌握得很熟練了,比如豎向排列 6 個(gè) Color 視圖。

var data: [Color] {
    [
        Color.random(),
        Color.random(),
        Color.random(),
        Color.random(),
        Color.random(),
        Color.random()
    ]
}

var body: some View {
    VStack {
        ForEach(0..<data.count) { index in
            self.data[index]
        }
    }
}

有些童鞋會(huì)疑問(wèn),為什么顏色也能算是視圖呢?這是因?yàn)樵?SwiftUI 中,View 是一個(gè)協(xié)議,而 Color 也遵循了 View 協(xié)議,所以 Color 也是一個(gè)視圖,可以直接在界面上展示它。

說(shuō)回來(lái)現(xiàn)在的例子,效果長(zhǎng)這樣。

只需將上面代碼里的 VStack 換成 HStack,就會(huì)變成這樣,代碼就不貼了,直接上效果圖。

那么是不是可以通過(guò)組合 HStack 與 VStack 能夠?qū)崿F(xiàn)我們想要的 Grid 視圖呢?答案是可以肯定的。

你們肯定發(fā)現(xiàn)了,視圖的上方和下方出現(xiàn)了空白,這是因?yàn)?iPhoneX 及之后的版本存在安全邊距,只需通過(guò)設(shè)置edgesIgnoringSafeArea方法,參數(shù)為vertical,代表的是忽略垂直方向的安全邊距。

.edgesIgnoringSafeArea(.vertical)

Grid 實(shí)現(xiàn)

為了簡(jiǎn)單起見(jiàn),我們先來(lái)打造一行三列的 Grid 視圖。定義一個(gè) View 取名為 GCRowView,視圖的大小按照屏幕的寬度三分之一進(jìn)行計(jì)算,這里的視圖寬和高是一致的,代碼如下所示,關(guān)鍵的代碼我會(huì)標(biāo)注數(shù)字,在后面進(jìn)行講解。

struct GCRowView: View {
    var itemPerRow = 3 // 1
    
    var views: [AnyView] = [ // 2
        AnyView(Image("1").resizable().aspectRatio(contentMode: .fill)),
        AnyView(Image("2").resizable().aspectRatio(contentMode: .fill)),
        AnyView(Image("3").resizable().aspectRatio(contentMode: .fill)),
    ]
    
    var itemWidth: CGFloat { // 3
        UIScreen.main.bounds.width / CGFloat(itemPerRow)
    }
    
    var body: some View {
        HStack(spacing: 0) { // 4
            ForEach(0..<views.count) { index in
                self.views[index]
                    .frame(width: self.itemWidth, height: self.itemWidth)
                    .clipped() // 5
            }
        }
    }
}

1 - 每一行有多少個(gè)視圖。

2 - 展示的視圖數(shù)組,存儲(chǔ)的類型為 AnyView,后面可以直接取用視圖。.resizable() 方法是為了讓圖片可以調(diào)整大小,.aspectRatio 設(shè)置為 .fill 是為了讓圖片保持原有的比例,并填滿整個(gè) frame。

3 - 計(jì)算每個(gè)視圖的寬高。

4 - HStack 默認(rèn)是有 spacing 的,這里的布局是一個(gè)視圖貼著一個(gè)的,因此設(shè)為0。

5 - 圖像超出部分進(jìn)行裁剪。

現(xiàn)在的效果是這樣的。

可以看到視圖正確地顯示出來(lái)了。

現(xiàn)在創(chuàng)建 GCGirdContentView ,在其內(nèi)實(shí)現(xiàn)一些算法,分別是計(jì)算總共有多少行和每一行展示的具體視圖。先來(lái)實(shí)現(xiàn) rowCount(contentNums:itemPerRow:) 方法計(jì)算總行數(shù),參數(shù)分別是視圖總數(shù)每行的視圖數(shù)量。

func rowCount(contentNums: Int, itemPerRow: Int) -> Int {
    if contentNums % itemPerRow == 0 {
        return contentNums / itemPerRow
    }

    return contentNums / itemPerRow + 1
}

1 - 進(jìn)行取余運(yùn)算,余數(shù)為 0 則代表可以被整除

2 - 既然可以被整除,則可以直接計(jì)算商就可以了

3 - 若余數(shù)不為 0 ,則代表需要換行,因此除了計(jì)算商后還需要進(jìn)行 +1

計(jì)算出每行排列的視圖,返回視圖數(shù)組,用于給 GCRowView 進(jìn)行顯示,方法的參數(shù)分別是當(dāng)前行數(shù)每行的視圖數(shù)量

func rowViews(currentRow: Int, itemPerRow: Int) -> [AnyView] {
    var views = [AnyView]()

    for i in 0..<itemPerRow { // 1
        let index = i + itemPerRow * currentRow // 2
        if index < contentViews.count { // 3
            views.append(contentViews[index]) // 4
        }
    }

    return views
}

1 - 循環(huán)遍歷每行的視圖數(shù)量

2 - 計(jì)算當(dāng)前應(yīng)該取出哪個(gè)視圖

3 - 計(jì)算程序安全邊界,若超出視圖總數(shù)則忽略不計(jì)

4 - 取出視圖并放入視圖數(shù)組

接著把 GCRowView 封裝得通用一點(diǎn),把 itemPerRow 和 views 的默認(rèn)值去除。

struct GCRowView: View {
    var itemPerRow: Int
    
    var views: [AnyView]
    
    //...

直到目前,我們已經(jīng)完成了大部分的工作,現(xiàn)在來(lái)組裝一下 GCGirdContentView 視圖。

struct GCGirdContentView: View {
    var itemPerRow = 3
    
    var contentViews: [AnyView] = []
    
    init() { // 1
        for i in 1...12 {
            contentViews.append(AnyView(Image("\(i)").resizable().aspectRatio(contentMode: .fill)))
        }
    }
    
    var body: some View {
        VStack(alignment: .leading, spacing: 0) { // 2
            ForEach(0..<rowCount(contentNums: contentViews.count, itemPerRow: itemPerRow)) { i in // 3
                GCRowView(itemPerRow: self.itemPerRow, views: self.rowViews(currentRow: i, itemPerRow: self.itemPerRow)) // 4
            }
        }
    }
}

1 - 在 init 函數(shù)里初始化 contentViews ,加入需要展示的圖像視圖

2 - VStack 設(shè)置為左邊對(duì)齊,行間距設(shè)為 0 ,讓視圖緊貼著彼此

3 - 遍歷循環(huán)行數(shù),用到了剛剛定義的 rowCount(contentNums:itemPerRow:) 方法

4 - 顯示的 GCRowView 行視圖,配合當(dāng)前行 i 并利用 rowViews(currentRow:itemPerRow:) 方法計(jì)算出需要顯示的具體視圖組

現(xiàn)在運(yùn)行,最終效果圖如下所示。

總結(jié)

在 SwiftUI 里實(shí)現(xiàn) Grid 其實(shí)不算是復(fù)雜,通過(guò)組合 HStack 與 VStack 就能夠助我們實(shí)現(xiàn) Grid 視圖。

在最新的 SwiftUI Beta 版里,蘋果推出了如 LazyVGrid、LazyHGrid、GridItem 來(lái)實(shí)現(xiàn)管理 Grid 視圖,我們就拭目以待吧,后續(xù)有機(jī)會(huì)再來(lái)更新一波。

源碼下載

我已經(jīng)把源碼 GCGridView 上傳到 GitHub 上,往期所有的 Demo 源碼皆放在了SwiftUI-Tutorials,歡迎自取。如果該項(xiàng)目幫到你的話,請(qǐng)給我個(gè) Star 告知,謝謝!喜歡本篇文章的小伙伴,歡迎給個(gè)關(guān)注,后續(xù)繼續(xù)更新更多文章,謝謝!

關(guān)于作者

博文作者:GarveyCalvin

微博:https://weibo.com/feiyueharia

博客園:https://www.cnblogs.com/GarveyCalvin

本文版權(quán)歸作者,歡迎轉(zhuǎn)載,但必須保留此段聲明,并給出原文鏈接,謝謝合作!

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多