一个 SwiftUI 包,用于以宽度自适应的网格模式排列子视图,并在垂直方向上展开,例如用于一组标签 (Tags)。
起初,似乎内置的 LazyVStack
可以做到这一点,但尽管它可以创建自适应列,但该尺寸对于所有行都是固定的。
最低目标版本是 macOS 13
或 iOS 16
(为了使用 Layout
)。
import SwiftUI
import AdaptiveGridLayout
struct SampleView: View {
let itemHeight: CGFloat = 50
@State var items: [(width: CGFloat, color: Color)] = (0..<40).map { _ in
(CGFloat.random(in: 10..<120),
Color(hue: .random(in: 0..<1.0),
saturation: .random(in: 0.1..<0.8),
brightness:.random( in: 0.5..<0.9))
)
}
var body: some View {
AdaptiveVGrid(spacing: 2) { // <------------
ForEach(items.indices, id: \.self) { index in
Rectangle()
.fill(items[index].color)
.frame(width: items[index].width, height: itemHeight)
.overlay { Text(index, format: .number) }
}
}
.border(.blue)
.containerRelativeFrame(.vertical)
}
}
import SwiftUI
import AdaptiveGridLayout
// MARK: View
struct TagsView: View {
var model = FoodModel()
var body: some View {
AdaptiveVGrid(spacing: 6) {
ForEach(model.fruits) { fruit in
TagView(word: LocalizedStringKey(fruit.name))
}
}
}
struct TagView: View {
let word: LocalizedStringKey
var body: some View {
Text(word)
.font(.body.weight(.semibold).monospaced())
.padding(.vertical, 4)
.padding(.horizontal)
.background(Color.red, in: Capsule(style: .circular))
.foregroundStyle(.background)
}
}
}
// MARK: Model
class FoodModel {
var fruits: [Fruit] = ["Tangerine", "Honeydew", "Fig", "Zucchini", "Orange", "Cherry", "Papaya", "Dragon Fruit", "Dates", "Lemon", "Apple", "Nectarine", "Raspberry", "Banana"]
struct Fruit: Identifiable, ExpressibleByStringLiteral {
let name: String
var id: String { name }
init(stringLiteral value: StringLiteralType) {
name = value
}
}
}
元素自己处理动画。 为添加和删除元素添加一些动画的最简单方法是使用 matchedGeometry
。 对于上面的示例,将 .matchedGeometryEffect(id: fruit.id, in: namespace)
添加到每个元素,然后添加 .animation(.spring, value: model.fruits)
。