I am making a CustomGridView without using Apple Grid, my codes works fine but it has an extra rendering issue when I add new row! As long as I see in my codes, if I add new row, Xcode should render just for new Items! But in the fact it renders all the items! which I can not find the issue! I am okay if Xcode renders all Items if I add new column, because the size put effect on every single Item, but with adding row, there is no general effect on items!
import SwiftUI
struct GridItemView: View {
let rowItem: GridItemRowType
let columnItem: GridItemColumnType
let string: String
let size: CGFloat
@State private var color: Color = Color.randomColor
init(rowItem: GridItemRowType, columnItem: GridItemColumnType, size: CGFloat) {
self.rowItem = rowItem
self.columnItem = columnItem
self.size = size
self.string = "(" String(describing: rowItem.index) "," String(describing: columnItem.index) ")"
}
var body: some View {
print("rendering for:", string)
return RoundedRectangle(cornerRadius: 10.0)
.fill(color)
.padding(5.0)
.frame(width: size, height: size)
.overlay(
Text(string)
.bold()
.foregroundColor(Color.white)
.shadow(color: .black, radius: 2, x: 0.0, y: 0.0)
)
.onTapGesture {
color = Color.white
}
.shadow(radius: 20.0)
}
}
extension Color {
static var randomColor: Color {
get { return Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1)) }
}
}
struct CustomGridView: View {
let gridItemColumn: [GridItemColumnType]
let gridItemRow: [GridItemRowType]
var body: some View {
GeometryReader { proxy in
let size: CGFloat = (proxy.size.width - 5.0)/CGFloat(gridItemColumn.count)
ScrollView {
VStack(spacing: .zero) {
ForEach(gridItemRow) { rowItem in
HStack(spacing: .zero) {
ForEach(gridItemColumn) { columnItem in
GridItemView(rowItem: rowItem, columnItem: columnItem, size: size)
}
}
.transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .top), removal: AnyTransition.move(edge: .top)))
}
Spacer()
}
.transition(AnyTransition.asymmetric(insertion: AnyTransition.move(edge: .trailing), removal: AnyTransition.move(edge: .trailing)))
}
.position(x: proxy.size.width/2.0, y: proxy.size.height/2.0)
}
.animation(Animation.default, value: [gridItemRow.count, gridItemColumn.count])
}
}
struct GridItemColumnType: Identifiable {
let id: UUID = UUID()
var index: Int
static let initializingGridItemColumn: [GridItemColumnType] = [GridItemColumnType(index: 0),
GridItemColumnType(index: 1),
GridItemColumnType(index: 2),
GridItemColumnType(index: 3),
GridItemColumnType(index: 4)]
}
struct GridItemRowType: Identifiable {
let id: UUID = UUID()
var index: Int
static let initializingGridItemRowType: [GridItemRowType] = [GridItemRowType(index: 0),
GridItemRowType(index: 1),
GridItemRowType(index: 2)]
}
struct ContentView: View {
@State private var gridItemColumn: [GridItemColumnType] = GridItemColumnType.initializingGridItemColumn
@State private var gridItemRow: [GridItemRowType] = GridItemRowType.initializingGridItemRowType
var body: some View {
CustomGridView(gridItemColumn: gridItemColumn, gridItemRow: gridItemRow)
Spacer()
Button("add Column") { gridItemColumn.append(GridItemColumnType(index: gridItemColumn.count)) }
Button("add Row") { gridItemRow.append(GridItemRowType(index: gridItemRow.count)) }
}
}
CodePudding user response:
You need to make cell view equatable, so SwiftUI would know if it should be re-rendered.
Tested with Xcode 13.2 / iOS 15.2
struct GridItemView: View, Equatable {
// The `string` property is used just for demo, in real you
// need some unique identifier which represents that view
// has same content
static func == (lhs: GridItemView, rhs: GridItemView) -> Bool {
lhs.string == rhs.string
}
// ... other code
}
and then inside grid use it as
ForEach(gridItemColumn) { columnItem in
EquatableView(content: GridItemView(rowItem: rowItem,
columnItem: columnItem, size: size))
}