SwiftUI - Grid View 的实现方法,逐步剖析助你实现
简介
在当前正式 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 的实现方法,逐步剖析助你实现的更多相关文章
- SwiftUI & Compose View
SwiftUI & Compose View OK // // ContentView.swift // Landmarks // // Created by 夏凌晨 on 2020/10/2 ...
- Android自定义View的实现方法,带你一步步深入了解View(四)
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到第四篇了,回 ...
- android view的setVisibility方法值的意思
android view的setVisibility方法值的意思 有三个值 visibility One of VISIBLE, INVISIBLE, or GONE. 常量值为0,意思是可见的 常 ...
- 【转】Android自定义View的实现方法,带你一步步深入了解View(四)
原文网址: 转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/17357967 不知不觉中,带你一步步深入了解View系列的文章已经写到 ...
- 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 ...
- 解析View的getDrawingCache方法
1. View 的getDrawingCache方法 有时候需要将某个view的内容以图片的方式保存下来,感觉就和截图差不多,可以使用View 的getDrawingCache方法,返回一个Bitma ...
- 调用布局View的performClick()方法
修改之前的xml片段. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/an ...
- 对View的onMeasure()方法的进一步研究
在Android开发中,很多人对自定义View是望而生畏,但这又是向高级进阶的必经之路,主要是对View里面的很多方法不知道怎么理解,其中一个就是onMeasure()方法. 首先,我自定义一个MyV ...
- 解析6种常用View 的滑动方法
View 的滑动是Android 实现自定义控件的基础,实现View 滑动有很多种方法,在这里主要讲解6 种滑动方法,分别是layout().offsetLeftAndRight()与offsetTo ...
随机推荐
- luogu P2304 [NOI2015]小园丁与老司机 dp 上下界网络流
LINK:小园丁与老司机 苦心人 天不负 卧薪尝胆 三千越甲可吞吴 AC的刹那 真的是泪目啊 很久以前就写了 当时记得特别清楚 写到肚子疼.. 调到胳膊疼.. ex到根不不想看的程度. 当时wa了 一 ...
- 5.15 牛客挑战赛40 E 小V和gcd树 树链剖分 主席树 树状数组 根号分治
LINK:小V和gcd树 时限是8s 所以当时好多nq的暴力都能跑过. 考虑每次询问暴力 跳父亲 这样是nq的 4e8左右 随便过. 不过每次跳到某个点的时候需要得到边权 如果直接暴力gcd的话 nq ...
- 授人以渔:stm32资料查询技巧
摘要:本章以stm32f103作为案例向大家讲解arm公司和st公司的关系以及我们在对stm32开发时需要如何正确的查找手册. ARM公司和ST公司的关系 这里要从一块芯片的生产说起,比如我们要生成一 ...
- Android Studio项目组织结构
任何一个新建的项目都会默认使用一个Android模式的项目结构,这个结构是被Android Studio转换过的,适合快速开发,但不易于理解,切换到Project模式后如下: 重点认识一下重要的几个文 ...
- 用 Python 可以实现侧脸转正脸?我也要试一下!
作者 | 李秋键 责编 | Carol 封图 | CSDN 下载自视觉中国 很多人学习python,不知道从何学起.很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手.很多已经做案例 ...
- C语言学习笔记之进制之间的转换
这一篇主要是对进制之间转换的讲解,方便查看,以防忘记 二进制 逢二进一 八进制 逢八进一 以0开头, 0就是8进制的标志 十进制 逢十进一 ...
- Java Redis系列2 (redis的安装与使用+redis持久化的实现))
Java Redis系列2 (redis的安装与使用+redis持久化的实现) 什么是Redis? Redis是用C语言开发的一个开源的高性能键值对(key-value)数据库,官方提供测试数据,50 ...
- java循环语句while与do-while
一 while循环 while循环语句和选择结构if语句有些相似,都是根据条件判断来决定是否执行大括号内的执行语句. 区别在于,while语句会反复地进行条件判断,只要条件成立,{}内的执行语句就会执 ...
- Python多进程实现并行化随机森林
文章目录 1. 前言 2. 随机森林原理 3.实现原理 3.1并行化训练 3.1.1训练函数 3.1.2 单进程训练函数 生成数据集模块--生成部分数据集 单进程训练函数代码 3.2 并行化预测 3. ...
- MongoDB学习4:MongoDB复制集机制和原理,搭建复制集
1.复制集的作用 1.1 MongoDB复制集的主要意义在于实现服务高可用 1.2 它的实现依赖于两个方面的功能: · 数据写入时将数据迅速复制到另一个独立节点上 · 在接收写入的 ...