Skip to content

Instantly share code, notes, and snippets.

@Koshimizu-Takehito
Last active June 3, 2024 04:39
Show Gist options
  • Save Koshimizu-Takehito/03c1302dbe0283c5b01eaee770afdba1 to your computer and use it in GitHub Desktop.
Save Koshimizu-Takehito/03c1302dbe0283c5b01eaee770afdba1 to your computer and use it in GitHub Desktop.
改行を考慮した FlowLayout
import SwiftUI
struct FlowLayoutSampleView: View {
@State var width: CGFloat = 180
let tags: [String] = [
"Objective-C",
"Swift",
// "SwiftSwiftSwiftSwiftSwiftSwiftSwiftSwift",
// "SwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwiftSwift",
"Ruby", "Python", "JavaScript",
"Java", "C++", "C#", "Go", "Kotlin", "Rust"
]
var body: some View {
VStack {
Spacer()
Slider(value: $width.animation(), in: 100...360)
}
.background {
MyFlowLayout(vSpacing: 8.0, hSpacing: 8.0) {
ForEach(tags, id: \.self) { tag in
Text(tag)
.lineLimit(nil)
.font(.body)
.fontWeight(.semibold)
.fontDesign(.monospaced)
.foregroundStyle(.white)
.padding(.vertical, 6)
.padding(.horizontal, 12)
.background(.black)
.clipShape(RoundedRectangle(cornerRadius: 16))
}
}
.frame(maxWidth: width)
.background(.pink.opacity(0.2))
}
.padding(20)
}
}
struct MyFlowLayout: Layout {
var vSpacing: CGFloat = 8.0
var hSpacing: CGFloat = 8.0
func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
let proposalWidth = proposal.width ?? .zero
var remainWidth = proposalWidth - hSpacing
var currentHeight = CGFloat.zero
var totalSize = CGSize.zero
for subview in subviews {
let size = subview.sizeThatFits(.init(width: proposalWidth - 2 * hSpacing, height: .infinity))
if remainWidth - (size.width + hSpacing) < 0 {
totalSize.height += currentHeight
remainWidth = proposalWidth - hSpacing
currentHeight = .zero
}
remainWidth -= size.width + hSpacing
currentHeight = max(size.height + vSpacing, currentHeight)
}
totalSize.height += currentHeight + vSpacing
totalSize.width = proposalWidth
return totalSize
}
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
var offset = CGPoint.zero
offset.y += vSpacing
offset.x += hSpacing
var remainWidth = bounds.width
var currentHeight = CGFloat.zero
for subview in subviews {
let size = subview.sizeThatFits(.init(width: bounds.width - 2 * hSpacing, height: .infinity))
if remainWidth - (size.width + hSpacing) < 0 {
offset.y += currentHeight + vSpacing
offset.x = hSpacing
currentHeight = .zero
remainWidth = (bounds.width - hSpacing)
}
let point = CGPoint(x: bounds.origin.x + offset.x, y: bounds.origin.y + offset.y)
subview.place(at: point, proposal: .init(size))
offset.x += size.width + hSpacing
remainWidth -= size.width + hSpacing
currentHeight = max(size.height, currentHeight)
}
}
}
#Preview {
FlowLayoutSampleView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment