# problem with .readSize()

{% hint style="info" %}
⭐️ 建議：此方法應改名為 <mark style="color:green;">**.onChangeSize()**</mark> 才能符合程式碼的原意。
{% endhint %}

{% embed url="https://youtu.be/-HbR9arfZdE" %}

{% tabs %}
{% tab title="🐞 bug" %}
{% hint style="danger" %}
⛔️ [.readsize](../../../view/.readsize "mention") 的大問題：&#x20;

當本例中的 <mark style="color:purple;">`idealCellAspectRatio`</mark> 改變時，<mark style="color:orange;">`rows`</mark>, <mark style="color:orange;">`cols`</mark>, <mark style="color:orange;">`ratio`</mark> 卻<mark style="color:red;">沒發生任何變化</mark>， 這主要是因為 <mark style="color:purple;">`size`</mark> 並沒有變化，然而 .readSize() 背後的運作機制是透過 [preferencekey](../../../../data-flow/preferences/preferencekey "mention") ([.onPreferenceChange()](https://developer.apple.com/documentation/swiftui/view/onpreferencechange\(_:perform:\)))來操作的，若 <mark style="color:purple;">`size`</mark> 沒有發生變化，PreferenceKey 自然<mark style="color:red;">**不會設定新的值**</mark>，因此也<mark style="color:red;">**不會觸發**</mark> .readSize() 的 <mark style="color:red;">`onChange`</mark> closure，裡面的計算自然被跳過，所以也不會更新 <mark style="color:orange;">`rows`</mark>, <mark style="color:orange;">`cols`</mark>, <mark style="color:orange;">`ratio`</mark> 等變數 ❗️❗️❗️
{% endhint %}

{% hint style="success" %}
💊 <mark style="color:green;">**解藥**</mark>：&#x20;

不要透過 [preferencekey](../../../../data-flow/preferences/preferencekey "mention")，直接用 [geometryreader](../../../measure/geometryreader "mention")❗️

(註：現在已經包裝在 [.getsize](../../../view/.getsize "mention") 裡面。)

```swift
// 💊 解藥：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
}
```
{% endhint %}

{% embed url="https://youtu.be/xkPbmO1pkhU" %}
problem fixed
{% endembed %}
{% endtab %}

{% tab title="👁️ 預覽：TestMyGrid" %}
⬆️ 需要： [slidersforsize](../../../../../custom/control/slidersforsize "mention"), (Revised from [mygrid](mygrid "mention"))

```swift
// 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()
    }
}
```
{% endtab %}

{% tab title="👥 相關" %}
* this topic involves [geometryreader](../../../measure/geometryreader "mention") and [preferencekey](../../../../data-flow/preferences/preferencekey "mention").
* this is a problem of [.readsize](../../../view/.readsize "mention").
* can be fixed with [.getsize](../../../view/.getsize "mention").
* revised from [mygrid](mygrid "mention").
* problem with [nested-types](../../../../../swift/type/category/nested-types "mention") in [generics-and-subtypes](../../../../../swift/type/category/generic-types/generics-and-subtypes "mention"), 👉 [to-nest-or-not-to-nest](../../../../../swift/type/category/nested-types/to-nest-or-not-to-nest "mention")
{% endtab %}

{% tab title="🗣 討論" %}
* [Why isn't onPreferenceChange being called if it's inside a ScrollView in SwiftUI?](https://stackoverflow.com/questions/58720495/why-isnt-onpreferencechange-being-called-if-its-inside-a-scrollview-in-swiftui)
{% endtab %}

{% tab title="📘 手冊" %}
* [SwiftUI](https://developer.apple.com/documentation/swiftui)  ⟩ &#x20;
  * [State & Data Flow](https://developer.apple.com/documentation/swiftui/state-and-data-flow)  ⟩  [**PreferenceKey**](https://developer.apple.com/documentation/swiftui/preferencekey)
    * .[**reduce**(value:nextValue:)](https://developer.apple.com/documentation/swiftui/preferencekey/reduce\(value:nextvalue:\))
  * [Views & Controls](https://developer.apple.com/documentation/swiftui/views-and-controls)  ⟩  [View](https://developer.apple.com/documentation/swiftui/view)  ⟩  [State](https://developer.apple.com/documentation/swiftui/view-state)  ⟩ &#x20;
    * .[**preference**(key:value:)](https://developer.apple.com/documentation/swiftui/view/preference\(key:value:\))
    * .[**onPreferenceChange**(\_:perform:)](https://developer.apple.com/documentation/swiftui/view/onpreferencechange\(_:perform:\))
{% endtab %}
{% endtabs %}

## History

{% tabs %}
{% tab title="✏️" %}
1. 2022.02.18
{% endtab %}

{% tab title="1" %}
現在已經包裝成一個 view extension: [.getsize](../../../view/.getsize "mention")

```swift
// 💊 解藥：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()
}
```

{% hint style="danger" %}
⭐️ <mark style="color:red;">**注意**</mark>：&#x20;

如果直接用 <mark style="color:blue;">`.overlay{ emptyView }`</mark>，不用 <mark style="color:red;">**GeometryReader**</mark> 的話，很神奇的是：<mark style="color:orange;">`rows`</mark>, <mark style="color:orange;">`cols`</mark>, <mark style="color:orange;">`ratio`</mark> <mark style="color:red;">**依然不會更新**</mark>❗️❗️❗️
{% endhint %}
{% endtab %}
{% endtabs %}
