I'm trying to construct a two-column grid of quadratic views from an array of colors where one view expands to the size of four small views when clicked.
Javier from swiftui-lab.com gave me kind of a breakthrough with the idea of adding Color.clear as a "fake" view inside the ForEach to trick the VGrid into making space for the expanded view. This works fine for the boxes on the left of the grid. The boxes on the right, however, give me a no ends of trouble because they expand to the right and don't cause the VGrid to reallign properly:
I've tried several things like swapping the colors in the array, rotating the whole grid when one of the views on the right is clicked, adding varying numbers of Color.clear views - nothing has done the trick so far.
Here's the current code:
struct ContentView: View {
@State private var selectedColor : UIColor? = nil
let colors : [UIColor] = [.red, .yellow, .green, .orange, .blue, .magenta, .purple, .black]
private let padding : CGFloat = 10
var body: some View {
GeometryReader { proxy in
ScrollView {
LazyVGrid(columns: [
GridItem(.fixed(proxy.size.width / 2 - 5), spacing: padding, alignment: .leading),
GridItem(.fixed(proxy.size.width / 2 - 5))
], spacing: padding) {
ForEach(0..<colors.count, id: \.self) { id in
if selectedColor == colors[id] && id % 2 != 0 {
Color.clear
}
RectangleView(proxy: proxy, colors: colors, id: id, selectedColor: selectedColor, padding: padding)
.onTapGesture {
withAnimation{
if selectedColor == colors[id] {
selectedColor = nil
} else {
selectedColor = colors[id]
}
}
}
if selectedColor == colors[id] {
Color.clear
Color.clear
Color.clear
}
}
}
}
}.padding(.all, 10)
}
}
RectangleView:
struct RectangleView: View {
var proxy: GeometryProxy
var colors : [UIColor]
var id: Int
var selectedColor : UIColor?
var padding : CGFloat
var body: some View {
Color(colors[id])
.frame(width: calculateFrame(for: id), height: calculateFrame(for: id))
.clipShape(RoundedRectangle(cornerRadius: 20))
.offset(y: resolveOffset(for: id))
}
// Used to offset the boxes after the expanded one to compensate for missing padding
func resolveOffset(for id: Int) -> CGFloat {
guard let selectedColor = selectedColor, let selectedIndex = colors.firstIndex(of: selectedColor) else { return 0 }
if id > selectedIndex {
return -(padding * 2)
}
return 0
}
func calculateFrame(for id: Int) -> CGFloat {
selectedColor == colors[id] ? proxy.size.width : proxy.size.width / 2 - 5
}
}
I would be really grateful if you could point me in the direction of what I'm doing wrong.
P.S. If you run the code, you'll notice that the last black box is also not behaving as expected. That's another issue that I've not been able to solve thus far.