Grid

⬆️ 需要: GridLayout, arr.index(of:), .if(_:then:)

/*
 * ⭐️ 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)
    }
    
}

Last updated