Skip to content

Instantly share code, notes, and snippets.

@allfinlir
Created December 16, 2023 12:31
Show Gist options
  • Save allfinlir/8dbaeb881d73523593a513fd5690d6a4 to your computer and use it in GitHub Desktop.
Save allfinlir/8dbaeb881d73523593a513fd5690d6a4 to your computer and use it in GitHub Desktop.
import SwiftUI
import Foundation
struct MasonryLayoutRows: View {
@State private var rows = 3
@State private var views = (0..<20).map { _ in
CGSize(width: .random(in: 10...500), height: .random(in: 10...500))}
var body: some View {
ScrollView(.horizontal) {
LazyHStack {
MasontyLayoutRowsExample(rows: rows) {
ForEach(0..<20) { i in
APlaceHolderView(size: views[i])
}
}
.padding(.vertical, 5)
}
}
.safeAreaInset(edge: .bottom) {
Stepper("Rows: \(rows)", value: $rows.animation(), in: 1...5)
.padding()
.background(.regularMaterial)
}
}
}
struct APlaceHolderView: View {
let color: Color = [.blue, .cyan, .green, .orange, .red, .indigo, .mint, .pink, .purple].randomElement()!
let size: CGSize
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(color)
Text("\(Int(size.width))x\(Int(size.height))")
.foregroundStyle(.white)
.font(.headline)
}
.aspectRatio(size, contentMode: .fill)
}
}
struct MasontyLayoutRowsExample: Layout {
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) -> CGSize {
let height = proposal.replacingUnspecifiedDimensions().height
let viewFrames = frames(in: subviews, in: height)
let width = viewFrames.max { $0.maxX < $1.maxX } ?? .zero
return CGSize(width: width.maxX, height: height)
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout Void) {
let viewFrames = frames(in: subviews, in: bounds.height)
for index in subviews.indices {
let frame = viewFrames[index]
let position = CGPoint(x: bounds.minX + frame.minX, y: bounds.minY + frame.minY)
subviews[index].place(at: position, anchor: .bottom, proposal: ProposedViewSize(frame.size))
}
}
var rows: Int
var spacing: Double
init(rows: Int = 3, spacing: Double = 5) {
self.rows = max(1, rows)
self.spacing = spacing
}
func frames(in subviews: Subviews, in totalHeight: Double) -> [CGRect] {
let totalSpacing = spacing * Double(rows - 1)
let rowHeight = (totalHeight - totalSpacing) / Double(rows)
let rowHeightWithSpacing = rowHeight + spacing
let proposedSize = ProposedViewSize(width: nil, height: rowHeight)
var viewFrames = [CGRect]()
var rowWidths = Array(repeating: 0.0, count: rows)
for subview in subviews {
var selectedRow = 0
var selectedWidth = Double.greatestFiniteMagnitude
for (rowIndex, width) in rowWidths.enumerated() {
if width < selectedWidth {
selectedRow = rowIndex
selectedWidth = width
}
}
let x = rowWidths[selectedRow]
let y = Double(selectedRow) * rowHeightWithSpacing
let size = subview.sizeThatFits(proposedSize)
let frame = CGRect(x: x, y: y, width: size.width, height: size.height) // jag tror att jag måste fixa in våra tag entries här?
rowWidths[selectedRow] += size.width + spacing
viewFrames.append(frame)
}
return viewFrames
}
}
#Preview {
MasonryLayoutRows()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment