problem with .readSize()
Last updated
Was this helpful?
Last updated
Was this helpful?
⭐️ 建議:此方法應改名為 .onChangeSize() 才能符合程式碼的原意。
⛔️ .readSize() 的大問題:
當本例中的 idealCellAspectRatio
改變時,rows
, cols
, ratio
卻沒發生任何變化, 這主要是因為 size
並沒有變化,然而 .readSize() 背後的運作機制是透過 PreferenceKey ()來操作的,若 size
沒有發生變化,PreferenceKey 自然不會設定新的值,因此也不會觸發 .readSize() 的 onChange
closure,裡面的計算自然被跳過,所以也不會更新 rows
, cols
, ratio
等變數 ❗️❗️❗️
💊 解藥:
不要透過 PreferenceKey,直接用 GeometryReader❗️
(註:現在已經包裝在 .getSize() 裡面。)
// 💊 解藥:GeometryReader
.getSize { size in
// recompute the current layout
let layout = MyGrid<Int, Text>.Layout(
idealCellAspectRatio, count: items.count, in: size
)
// update view states
rows = layout.rows
cols = layout.cols
ratio = layout.cellSize.aspectRatio
}
⬆️ 需要: SlidersForSize, (Revised from MyGrid)
// 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()
}
}
this topic involves GeometryReader and PreferenceKey.
this is a problem of .readSize().
can be fixed with .getSize().
revised from MyGrid.
problem with nested types in generics & subtypes, 👉 to nest or not to nest❓
2022.02.18
現在已經包裝成一個 view extension: .getSize()
// 💊 解藥:GeometryReader
.overlay {
GeometryReader { _ in emptyView }
}
// 💊 解藥:(normal code inside GeometryReader's closure)
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()
}
⭐️ 注意:
如果直接用 .overlay{ emptyView }
,不用 GeometryReader 的話,很神奇的是:rows
, cols
, ratio
依然不會更新❗️❗️❗️
⟩
⟩
.
⟩ ⟩ ⟩
.
.