简介

在当前正式 SwiftUI 版本而言,很多控件都是缺少的。比如在 UIKit 框架里有 UICollectionView 组件,可以很方便地做 Gird 格子类型的视图。但是在 SwiftUI 这个框架里面,就没有对应 UICollectionView 的组件。我们当然可以用 UIViewRepresentable 来封装一个 UICollectionView ,但是本篇文章要探讨的是,如何使用 SwiftUI 来实现 Grid 格子视图,现在一起来实现吧。

实现思考

在思考前,我们先来定义生成随机颜色的函数,后面会用到的。

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 竖向布局你们已经掌握得很熟练了,比如竖向排列 6 个 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]
}
}
}

有些童鞋会疑问,为什么颜色也能算是视图呢?这是因为在 SwiftUI 中,View 是一个协议,而 Color 也遵循了 View 协议,所以 Color 也是一个视图,可以直接在界面上展示它。

说回来现在的例子,效果长这样。

只需将上面代码里的 VStack 换成 HStack,就会变成这样,代码就不贴了,直接上效果图。

那么是不是可以通过组合 HStack 与 VStack 能够实现我们想要的 Grid 视图呢?答案是可以肯定的。

你们肯定发现了,视图的上方和下方出现了空白,这是因为 iPhoneX 及之后的版本存在安全边距,只需通过设置edgesIgnoringSafeArea方法,参数为vertical,代表的是忽略垂直方向的安全边距。

.edgesIgnoringSafeArea(.vertical)

Grid 实现

为了简单起见,我们先来打造一行三列的 Grid 视图。定义一个 View 取名为 GCRowView,视图的大小按照屏幕的宽度三分之一进行计算,这里的视图宽和高是一致的,代码如下所示,关键的代码我会标注数字,在后面进行讲解。

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 - 每一行有多少个视图。

2 - 展示的视图数组,存储的类型为 AnyView,后面可以直接取用视图。.resizable() 方法是为了让图片可以调整大小,.aspectRatio 设置为 .fill 是为了让图片保持原有的比例,并填满整个 frame。

3 - 计算每个视图的宽高。

4 - HStack 默认是有 spacing 的,这里的布局是一个视图贴着一个的,因此设为0。

5 - 图像超出部分进行裁剪。

现在的效果是这样的。

可以看到视图正确地显示出来了。

现在创建 GCGirdContentView ,在其内实现一些算法,分别是计算总共有多少行和每一行展示的具体视图。先来实现 rowCount(contentNums:itemPerRow:) 方法计算总行数,参数分别是视图总数每行的视图数量

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

1 - 进行取余运算,余数为 0 则代表可以被整除

2 - 既然可以被整除,则可以直接计算商就可以了

3 - 若余数不为 0 ,则代表需要换行,因此除了计算商后还需要进行 +1

计算出每行排列的视图,返回视图数组,用于给 GCRowView 进行显示,方法的参数分别是当前行数每行的视图数量

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 - 循环遍历每行的视图数量

2 - 计算当前应该取出哪个视图

3 - 计算程序安全边界,若超出视图总数则忽略不计

4 - 取出视图并放入视图数组

接着把 GCRowView 封装得通用一点,把 itemPerRow 和 views 的默认值去除。

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

直到目前,我们已经完成了大部分的工作,现在来组装一下 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 函数里初始化 contentViews ,加入需要展示的图像视图

2 - VStack 设置为左边对齐,行间距设为 0 ,让视图紧贴着彼此

3 - 遍历循环行数,用到了刚刚定义的 rowCount(contentNums:itemPerRow:) 方法

4 - 显示的 GCRowView 行视图,配合当前行 i 并利用 rowViews(currentRow:itemPerRow:) 方法计算出需要显示的具体视图组

现在运行,最终效果图如下所示。

总结

在 SwiftUI 里实现 Grid 其实不算是复杂,通过组合 HStack 与 VStack 就能够助我们实现 Grid 视图。

在最新的 SwiftUI Beta 版里,苹果推出了如 LazyVGrid、LazyHGrid、GridItem 来实现管理 Grid 视图,我们就拭目以待吧,后续有机会再来更新一波。

源码下载

我已经把源码 GCGridView 上传到 GitHub 上,往期所有的 Demo 源码皆放在了SwiftUI-Tutorials,欢迎自取。如果该项目帮到你的话,请给我个 Star 告知,谢谢!喜欢本篇文章的小伙伴,欢迎给个关注,后续继续更新更多文章,谢谢!

关于作者

博文作者:GarveyCalvin

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

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

本文版权归作者,欢迎转载,但必须保留此段声明,并给出原文链接,谢谢合作!

公众号

欢迎关注我的公众号(对着月亮敲代码),获取往期文章阅读浏览,期待你们的关注!

QQ群 / 微信群

如需加群讨论(吃瓜),请加我 QQ ,本人将统一拉群,在此期待你们的加入!

SwiftUI - Grid View 的实现方法,逐步剖析助你实现的更多相关文章

  1. SwiftUI & Compose View

    SwiftUI & Compose View OK // // ContentView.swift // Landmarks // // Created by 夏凌晨 on 2020/10/2 ...

  2. Android自定义View的实现方法,带你一步步深入了解View(四)

    转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回 ...

  3. android view的setVisibility方法值的意思

    android view的setVisibility方法值的意思 有三个值 visibility  One of VISIBLE, INVISIBLE, or GONE. 常量值为0,意思是可见的 常 ...

  4. 【转】Android自定义View的实现方法,带你一步步深入了解View(四)

    原文网址: 转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到 ...

  5. The file “XXX” couldn’t be opened because you don’t have permission to view it.解决方法:

    The file “XXX” couldn’t be opened because you don’t have permission to view it.解决方法:   解决方法:直接点击Xcod ...

  6. 解析View的getDrawingCache方法

    1. View 的getDrawingCache方法 有时候需要将某个view的内容以图片的方式保存下来,感觉就和截图差不多,可以使用View 的getDrawingCache方法,返回一个Bitma ...

  7. 调用布局View的performClick()方法

    修改之前的xml片段.             <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/an ...

  8. 对View的onMeasure()方法的进一步研究

    在Android开发中,很多人对自定义View是望而生畏,但这又是向高级进阶的必经之路,主要是对View里面的很多方法不知道怎么理解,其中一个就是onMeasure()方法. 首先,我自定义一个MyV ...

  9. 解析6种常用View 的滑动方法

    View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout().offsetLeftAndRight()与offsetTo ...

随机推荐

  1. PHP fclose() 函数

    定义和用法 fclose() 函数关闭打开的文件. 该函数如果成功则返回 TRUE,如果失败则返回 FALSE. 语法 fclose(file) 参数 描述 file 必需.规定要关闭的文件. 实例 ...

  2. PDO::getAttribute

    PDO::getAttribute — 取回一个数据库连接的属性(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 mixed PDO::getAttrib ...

  3. day20:正则表达式

    单个字符的匹配 findall(正则表达式,字符串) 把符合正则表达式的字符串存在列表中返回 预定义字符集(8) \d 匹配数字 \D 匹配非数字 \w 匹配数字字母下划线 \W 匹配非数字或字母或下 ...

  4. 剑指 Offer 58 - I. 翻转单词顺序

    本题 题目链接 题目描述 我的题解 方法一:库函数split() 要注意str.split()函数: 字符串str前有 n 个空格时,分割出来的字符串列表中会多出 n 个空字符串: 字符串str某两个 ...

  5. Sharding-JDBC主键生成策略

    当使用分库分表等功能之后,就不能再依赖数据库自带的主键生成机制了,一方面主键ID不能重复,另外需要在新增之前就知道主键ID,才能保证ID能够均匀分布到不同的数据库或数据表中,所以要使用一个合理的主键生 ...

  6. TF签名为什么这么稳定?TF签名找微导流!

      TF签名作为目前最稳定的签名方式收到了业界开发者们的认可,而在如今鱼龙混杂的签名平台中,应该如何选择客厅的TF签名平台呢?下面就一起来看看TF签名为什么这么稳定?TF签名找微导流!   TF签名的 ...

  7. tp3.2 新增邮件类

    1.新建方法   调用发送邮件,我的目录在/admin下 2.新增邮件方法 类的发送配置功能 文件地址: 网站根目录\项目目录\Admin\Common\ 文件 名   :function.php   ...

  8. python IF while逻辑判断语句

    if判断语句 if 1==1 and 2==2: pass else: print('error') if 1==1 or 2==2: pass else: print('error') while循 ...

  9. 18、Java中的 数据结构

    Java2中引入了新的数据结构 集合框架 Collection,下一节再谈论(非常重要,面试也常问). 1.枚举 (Enumeration) 1.1 Enumeration 源码: public in ...

  10. Memcached高可用组件之repcached

    在前边的tomcat session server msm的那篇博客我们用memcached做tomcat session服务器,默认官方memcached是不支持主从同步的,为了解决memcache ...