# GridLayout

{% hint style="info" %}
協助 🌅 [Grid](/ios/swiftui/view/layout/grids/examples/grid.md) 計算**最佳的佈局方式**：

當收到來自<mark style="color:red;">**母視件**</mark> (通常是 [GeometryReader](/ios/swiftui/view/measure/geometryreader.md)) 所提議的佈局大小 (<mark style="color:red;">**proposed size**</mark>) 時，<mark style="color:purple;">**GridLayout**</mark> 會根據使用者所希望的<mark style="color:orange;">**格子比例**</mark> (<mark style="color:purple;">**cellAspectRatio**</mark>)，自動計算出最佳的<mark style="color:orange;">**行數**</mark> (<mark style="color:purple;">**cols**</mark>) 與<mark style="color:orange;">**列數**</mark> (<mark style="color:purple;">**rows**</mark>)，並且可以提供<mark style="color:orange;">**格子的大小**</mark> (<mark style="color:purple;">**cellSize**</mark>) 與<mark style="color:orange;">**每個格子的中心點**</mark>**位置** (<mark style="color:purple;">**centerOfCell(at: index)**</mark>)。
{% endhint %}

{% tabs %}
{% tab title="📦 GridLayout" %}

```swift
import SwiftUI
import Extensions   // for 🌀CGSize+aspectRatio, 🌀Int+cgfloat

// 📦 GridLayout
public struct GridLayout {
    
    let size: CGSize                 // size proposed by parent view
    private(set) var rows: Int = 0   // best layout in rows x cols
    private(set) var cols: Int = 0   // (to be calculated in init)
    
    public init(
        itemCount n: Int,               // number of items
        in     size: CGSize,            // proposed size
        nearAspectRatio r: CGFloat = 1  // desired cell aspect ratio
    ) {
        
        self.size = size
        
        // if zero width or height or itemCount is not > 0,
        // do nothing.
        guard size.width != 0, size.height != 0, n > 0 else { return }
        
        // find the bestLayout
        // which results in cells whose aspectRatio
        // has the smallestVariance from desiredAspectRatio
        var bestLayout: (rows: Int, cols: Int) = (1, n)
        var smallestVariance: CGFloat?

        // start to find
        for rows in 1...n {
            
            // calculate how many columns we need
            let cols = (n / rows) + (n % rows > 0 ? 1 : 0)
            
            // cellAspectRatio = (width/cols) : (height/rows)
            //                 = size.aspectRatio * rows/cols
            // -----------------------------------------------------------
            // ⭐️ because rows is strictly increasing, cols is decreasing,
            //    cellAspectRatio will be strictly increasing.
            // -----------------------------------------------------------
            // - size.aspectRatio: 🌀CGSize + aspectRatio
            // - row.cgfloat     : 🌀Int + cgfloat
            let cellAspectRatio = size.aspectRatio * (rows.cgfloat/cols.cgfloat)
            
            // current variance with desired aspect ratio
            let variance = self.variance(cellAspectRatio, r)
            
            // if variance is not set, or getting smaller,
            // set new smallestVariance and new bestLayout
            if smallestVariance == nil || variance < smallestVariance! {
                smallestVariance = variance
                bestLayout = (rows: rows, cols: cols)
            } else { 
                // smallVariance is set, and variance is getting larger
                break  // break for loop
            }
        }
        
        // record best layout found
        rows = bestLayout.rows
        cols = bestLayout.cols
    }
    
    /* ------- Public Methods ------- */
    
    // cell size
    public var cellSize: CGSize {
        (rows == 0 || cols == 0) ? .zero :
            CGSize(
                width : size.width  / CGFloat(cols),
                height: size.height / CGFloat(rows)
            )
    }
    
    // center point of cell at index
    public func centerOfCell(at index: Int) -> CGPoint {
        CGPoint(
            x: (CGFloat(index % cols) + 0.5) * cellSize.width,
            y: (CGFloat(index / cols) + 0.5) * cellSize.height
        )
    }
    
    /* ------- Helper Methods ------- */
    
    // variance between two aspect ratios
    func variance(_ r1: CGFloat, _ r2: CGFloat) -> CGFloat {
        precondition(r1 > 0 && r2 > 0)
        // ⭐️ 做對數計算，才不會造成 1/3 = 0.33 的比例比 2/1 = 2 的比例更接近 1/1 = 1
        return abs(log(r1) - log(r2))
    }
    
}
```

{% endtab %}

{% tab title="⬇️ 應用" %}

* [Grid](/ios/swiftui/view/layout/grids/examples/grid.md) - help calculate best layout.
  {% endtab %}

{% tab title="👥  相關" %}

* helps [Grid](/ios/swiftui/view/layout/grids/examples/grid.md) to calculate the best layout.
* [Emoji Memory Game 📚](/ios/master/todo/tutorials/emoji-game.md)
* [SwiftUI's Grid Views](https://www.objc.io/blog/2020/11/23/grid-layout) - objc.io
* [.grids()](/ios/swiftui/view/view/grids.md)
  {% endtab %}
  {% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://lochiwei.gitbook.io/ios/swiftui/view/layout/grids/examples/grid/gridlayout.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
