# Grid

{% tabs %}
{% tab title="🌅  Grid" %}
⬆️ 需要： [gridlayout](grid/gridlayout "mention"), [.index-of](../../../../../swift/type/category/basic/array/.index-of "mention"), [.if](../../../../../custom/ext/view/.if "mention")

```swift
/*
 * ⭐️ Required:
 * - 🌀View + if
 * - 🌀Array + index
 * - 📦 GridLayout
 */

import SwiftUI

// 🌅 Grid
public struct Grid<Item: Identifiable, ItemView: View>: View {
    
    let items          : [Item]              // items to arrange
    let viewForItem    : (Item) -> ItemView  // turn item into some View
    let cellAspectRatio: CGFloat             // desired cell aspect ratio
    let showCellBorder : Bool                // show cell border or not
    
    // Grid(items){ item in ... }
    public init(
        _         items: [Item], 
        cellAspectRatio: CGFloat = 1,        // ⭐️ default: aspect ratio = 1
        showCellBorder : Bool    = false,    // ⭐️ default: don't show cell border
        viewForItem    : @escaping (Item) -> ItemView
    ) {
        self.items           = items
        self.viewForItem     = viewForItem
        self.cellAspectRatio = cellAspectRatio
        self.showCellBorder  = showCellBorder
    }
    
    public var body: some View {
        // ⭐️ GeometryReader
        GeometryReader { geo in self.itemViews(in: geo.size) }
    }
    
    // helper functions
    
    func itemViews(in size: CGSize) -> some View {
        let n = items.count
        // ⭐️ prepare best layout for items (require: 📦 GridLayout)
        let layout = GridLayout(itemCount: n, in: size, nearAspectRatio: cellAspectRatio)
        // ⭐️ ForEach
        return ForEach(items) { item in self.view(for: item, in: layout) }
    }
    
    func view(for item: Item, in layout: GridLayout) -> some View {
        let index = items.index(of: item)!                 // 🌀Array + index
        let center = layout.centerOfCell(at: index)        // 📦 GridLayout
        return viewForItem(item)
            // ⭐️ best layout cell size
            .frame(layout.cellSize)
            // show cell border or not
            .if(showCellBorder) { $0.border(Color.black) }  // 🌀View + if
            // ⚠️ 注意：
            //    一旦加上 .position 之後，.frame 會變成整個
            //    GeometryReader 的大小，不再是單一卡片的大小。
            // ⭐️ put item view at the right position
            .position(center)
    }
    
}
```
{% endtab %}

{% tab title="⬆️ 需要" %}
* [gridlayout](grid/gridlayout "mention") - help calculate best layout.
* [.index-of](../../../../../swift/type/category/basic/array/.index-of "mention")
* [.if](../../../../../custom/ext/view/.if "mention")
{% endtab %}

{% tab title="📗 參考" %}
* [ ] objc.io ⟩ [SwiftUI's Grid Views](https://www.objc.io/blog/2020/11/23/grid-layout)
{% endtab %}

{% tab title="👥  相關" %}
* [stacks](../../stacks "mention") - SwiftUI views for 1D layout.
* [..](.. "mention") - SwiftUI views for 2D layout.
* used in [emoji-game](../../../../../master/todo/tutorials/emoji-game "mention")
* [grids](../../../view/grids "mention") - draw grid lines in the background.
* [mygrid](mygrid "mention") 改編自這裡。
{% endtab %}
{% endtabs %}
