Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Last active June 2, 2024 14:42
Show Gist options
  • Save Koshimizu-Takehito/5c799dc5f56e7c951e609a5e183d111f to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/5c799dc5f56e7c951e609a5e183d111f to your computer and use it in GitHub Desktop.
グリッドのマスと隙間に View を配置する Layout プロトコルのサンプル実装
import SwiftUI
enum CustomGridItem: LayoutValueKey, Hashable {
enum Bar: Hashable {
case vertical
case horizontal
}
case cell
case bar(Bar)
static var defaultValue: Self = .cell
}
struct ContentView: View {
@State private var value: (column: Double, numOfItems: Double) = (5, 25)
private var column: Int { Int(value.column) }
private var numOfItems: Int { Int(value.numOfItems) }
private var numOfVSpace: Int {
return (numOfItems/column) * (column - 1) + (numOfItems % column)
}
private var numOfHSpace: Int {
return (numOfItems/column) * column - (numOfItems % column == 0 ? column : 0)
}
var body: some View {
VStack {
Spacer()
Slider(
value: $value.column,
in: 5...10
)
Slider(
value: $value.numOfItems,
in: 25...100
)
}
.background {
CustomGridLayout(column: column) {
ForEach(Array(0..<numOfItems), id: \.self) { _ in
Color.cyan
.mask { RoundedRectangle(cornerRadius: 4.0) }
.layoutValue(key: CustomGridItem.self, value: .cell)
}
ForEach(Array(0..<numOfVSpace), id: \.self) { _ in
Color.mint.opacity(0.7)
.mask { RoundedRectangle(cornerRadius: 4.0) }
.layoutValue(key: CustomGridItem.self, value: .bar(.vertical))
}
ForEach(Array(0..<numOfHSpace), id: \.self) { _ in
Color.pink.opacity(0.4)
.mask { RoundedRectangle(cornerRadius: 4.0) }
.layoutValue(key: CustomGridItem.self, value: .bar(.horizontal))
}
}
}
.padding()
}
}
struct CustomGridLayout: Layout {
var column: Int
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let edge = min(proposal.width ?? 0, proposal.height ?? 0)
let cellCount = subviews.filter { $0[CustomGridItem.self] == .cell }.count
let (quotient, remainder) = cellCount.quotientAndRemainder(dividingBy: column)
let row = quotient + (remainder == 0 ? 0 : 1)
let ratio = (CGFloat(row) / CGFloat(column))
return CGSize(width: edge, height: max(ratio, 1) * edge)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
let space = min(bounds.width, bounds.height) * (8.0 / 360.0)
let edge = (min(bounds.width, bounds.height) - CGFloat(column - 1) * space) / CGFloat(column)
var count = (cell: 0, vertical: 0, horizontal: 0)
for index in subviews.indices {
let subview = subviews[index]
let item = subview[CustomGridItem.self]
switch item {
case .cell:
var point = bounds.origin
point.x += CGFloat(count.cell % column) * (edge + space)
point.y += CGFloat((count.cell / column)) * (edge + space)
subview.place(at: point, proposal: .init(width: edge, height: edge))
count.cell += 1
case .bar(.vertical):
var point = bounds.origin
point.x += edge + CGFloat(count.vertical % (column - 1)) * (edge + space) + 0.2 * space
point.y += CGFloat((count.vertical / (column - 1))) * (edge + space)
subview.place(at: point, proposal: .init(width: 0.6 * space, height: edge))
count.vertical += 1
case .bar(.horizontal):
var point = bounds.origin
point.x += CGFloat((count.horizontal % (column))) * (edge + space)
point.y += edge + CGFloat(count.horizontal / (column)) * (edge + space) + 0.2 * space
subview.place(at: point, proposal: .init(width: edge, height: 0.6 * space))
count.horizontal += 1
}
}
}
}
#Preview {
ContentView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment