动态网格视图 CustomGrid
我们都知道在UIKit中可以使用CollectionView可以实现瀑布流效果,在SwiftUI中我们能更简单实现可动态添加列数的网格视图。
首先,先理清楚我们的需求,我们的网格是可以显示所有遵循View协议的任何类我们可以让外部传入数据,只要数据支持Identifiable协议即可,方便我们在内部对该数据进行Loop操作。
1、创建CustomGrid类
- 声明属性以及初始化
//Content 为最后整合数据并用于展示的内容 comforms to View 协议
// T 数据 为任何comforms Identifiable Hashable 的数据
struct CustomGrid<Content: View, T: Identifiable>: View where T: Hashable{
var columns: Int // 内容展示的列数
var spacing: CGFloat = 20 // 视图之间的间距
var showIndicator: Bool = false // 是否显示指示条
var list:[T] // 存放数据的数组
var content:(T) -> Content // 闭包函数,用于生成整合数据后的视图
//初始化方法
init(columns: Int, spacing: CGFloat = 20, showIndicator: Bool = false,lists: [T], @ViewBuilder content: @escaping (T) -> Content) {
self.lists = lists
self.content = content
self.columns = columns
self.showIndicator = showIndicator
self.spacing = spacing
}
}
2、设置数据
func setupData() -> [[T]] {
var outputData: [[T]] = Array(repeating: [], count:columns) //根据所设定的列数创建对应的二维数组。
var currentIndex = 0
guard outputData.count > 0 else { return outputData }
for object in lists {
//将lists的数据平均分到outputData中的没个数组中
outputData[currentIndex].append(object)
if currentIndex == (columns - 1) {
currentIndex = 0
} else {
currentIndex += 1
}
}
return outputData
}
3、将数据进行展示
var body: some View {
ScrollView(.vertical,showsIndicators: showIndicator) {
HStack (alignment: .top) {
ForEach(setupData(), id: \.self) { data in
LazyVStack(spacing: spacing) {
ForEach(data,id: \.self) { post in
content(post) //这里直接调用闭包函数。将外部传进来的内容进行展示
}
}
}
}
.padding(.vertical)
}
}
4、代码调用
在ContentView中 实现如下代码
struct ContentView: View {
@State var lists:[Post] = [] //数据源数组 Post 遵循Identifiable,Hashable 协议
@State var columns: Int = 2
@Namespace var namespace //用与进行matchedGeometryEffect展示增加和减少列数时内容动态变换的动画
var body: some View {
NavigationView {
CustomGrid(columns: columns, spacing: 20, lists: lists) { post in
CardView(post: post)
.matchedGeometryEffect(id: post.id, in: namespace)
}
.padding(.horizontal)
.navigationTitle("CustomGrid")
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
columns += 1
} label: {Image(systemName: "plus")}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button {
columns = max(columns - 1, 0)
} label: {Image(systemName: "minus")}
}
}
.animation(.spring(response: 0.3, dampingFraction: 0.8, blendDuration: 0.3), value: columns)
}
.onAppear{
for index in 1...15 {
lists.append(Post(imageName: "\(index)"))//
}
}
}
}
struct CardView: View {
var post: Post
var body: some View {
Image(post.imageName)
.resizable()
.aspectRatio(contentMode: .fit)
.cornerRadius(10)
}
}
struct Post: Identifiable,Hashable {
let id = UUID().uuidString
var imageName: String
}