// 2022.02.17 (+) add slider for ideal cell ratio, found bug of .readSize()
import SwiftUI
struct TestMyGrid: View {
@State private var size = CGSize(300, 200) // proposed size
@State private var ratio: CGFloat = 1 // current cell aspect ratio
@State private var rows = 0 // current # of rows
@State private var cols = 0 // current # of cols
@State private var idealCellAspectRatio: CGFloat = 1
let items = Array(1...10)
var body: some View {
VStack {
ScrollView {
myGrid.padding(40)
}
controls
}
}
}
extension TestMyGrid {
var myGrid: some View {
// 👔 MyGrid<Item, ItemView>
MyGrid(
items: items, // ⭐️ Item == Int (require: Int: Identifiable)
cellAspectRatio: idealCellAspectRatio
)
{ i in // ⭐️ viewForItem: (Item) -> ItemView
Color.purple
.border(.black)
.overlay { Text("\(i)").bold().shadow(radius: 3) }
}
// ⭐️ read proposed size from parent (.frame() modifier)
// ------------------------------------------------------------------------
// ⛔️ .readSize() 的大問題:
// 當本例中的 idealCellAspectRatio 改變時,rows, cols, ratio 卻沒發生任何變化,
// 這主要是因為 size 並沒有變化,然而 .readSize() 背後的運作機制是透過 PreferenceKey
// 來操作的,若 size 沒有發生變化,PreferenceKey 自然不會設定新的值,因此也不會觸發
// .readSize() 的 `onChange` closure,所以在這個 closure 裡面的計算也就自然地
// 被跳過,所以也不會更新 rows, cols, ratio 等變數 ❗️❗️❗️
// ------------------------------------------------------------------------
.readSize { size in
let layout = MyGrid<Int, Text>.Layout(
idealCellAspectRatio, count: items.count, in: size
)
rows = layout.rows
cols = layout.cols
ratio = layout.cellSize.aspectRatio
}
// ---------------------------------------------------------------
// 💊 解藥:
// 不要透過 PreferenceKey,直接用 GeometryReader❗️
// ⭐️ 注意:
// 如果直接使用 `.overlay{ emptyView }`,不用 GeometryReader 的話,
// 很神奇的是:rows, cols, ratio 依然不會更新❗️❗️❗️
// 換句話說,GeometryReader 擁有其他 View 所沒有的「神奇功能」。
// ---------------------------------------------------------------
// .overlay {
// GeometryReader { _ in emptyView }
// }
// ⭐️ parent's proposed size
.frame(size)
.dimension(.topLeading, arrow: .blue, label: .orange)
.shadowedBorder()
.padding(.bottom, 40)
}
// 💊 解藥:empty view
var emptyView: some View {
let layout = MyGrid<Int, Text>.Layout(
idealCellAspectRatio, count: items.count, in: size
)
rows = layout.rows
cols = layout.cols
ratio = layout.cellSize.aspectRatio
return EmptyView()
}
/// view controls
var controls: some View {
VStack {
Text("Cells try to maintain their aspect ratio.")
.font(.title3)
Group {
Text("ideal cell aspect ratio = \(idealCellAspectRatio.decimalPlaces(2))")
VStack {
Text("current cell aspect ratio = \(ratio.decimalPlaces(2))")
Text("rows: \(rows), cols: \(cols)")
}
.border(.pink)
}
.font(.caption).foregroundColor(.secondary)
HStack(alignment: .top, spacing: 40) {
// control ideal cell aspect ratio
SliderWithSubtitle(
value: $idealCellAspectRatio,
range: 0.3...3, step: 0.01,
subtitle: "ideal ratio",
decimalPlaces: 2,
tint: .green
)
// control proposed size
SlidersForSize($size)
}
}
.padding()
}
}
struct TestMyGrid_Previews: PreviewProvider {
static var previews: some View {
TestMyGrid()
}
}