MyGrid

Files

⬆️ 需要: arr.index(of:)

// 2022.02.15

import SwiftUI

/// 👔 MyGrid<Item, ItemView>
/// ```
/// MyGrid(items: items) { item in 
///   /* build item view */ 
/// }
/// ```
struct MyGrid<Item: Identifiable, ItemView: View>: View {
    
    // 1. 使用者要提供:
    // • items          : 排版的項目
    // • cellAspectRatio: 希望的格子比例    (default = 1)
    // • viewForItem    : 繪製項目視圖的方法 (@ViewBuilder closure)
    let items: [Item]
    var cellAspectRatio: CGFloat = 1
    @ViewBuilder let viewForItem: (Item) -> ItemView
    
    // 2. 當 MyGrid 開始排版時:
    public var body: some View {
        // ⭐️ 2.1 MyGrid 會利用 GeometryReader 得到 proposed size,
        GeometryReader { geo in 
            // ⭐️ 2.2 然後用 `self.itemViews(in: size)` 繪製所有項目的視圖。
            self.itemViews(in: geo.size)
        }
    }
}

extension MyGrid {
    
    // 2.2
    @ViewBuilder func itemViews(in size: CGSize) -> some View {
        
        // ⭐️ 2.2.1: 委託 MyGrid.Layout 計算「最佳的排版方式」(layout)。
        //
        //    layout 擁有以下資訊:
        //      • .rows, .cols:「該切成幾行幾列」
        //      • .cellSize   :「格子比例」
        //      • .centerOfCell(at: i):「第幾個項目應該排在哪個位置」
        //
        // 👔 MyGrid<Item, ItemView>.Layout
        let layout = Layout(      
            cellAspectRatio, count: items.count, in: size
        )
        
        // ⭐️ ForEach
        ForEach(items) { item in 
            // ⭐️ 2.2.2: 委託 `self.view(for: item, in: layout)` 繪製每個項目的視圖。
            self.view(for: item, in: layout)
        }
    }
    
    // 2.2.2
    @ViewBuilder func view(for  item: Item, in layout: Layout) -> some View 
    {
        // ⭐️ 2.2.2.1: 計算項目編號,得知它是第幾個項目。
        let i = index(of: item)
        
        // ⭐️ 2.2.2.2: 計算項目的中心位置
        let center = layout.centerOfCell(at: i)  // 👔 MyGrid<Item, ItemView>.Layout
        
        // ⭐️ 2.2.2.3: 繪製項目視圖,並放在該放的位置。
        viewForItem(item)
            .frame(layout.cellSize)   // ⭐️ cell size
            // -------------------------------------------
            // ⚠️ 注意:
            //    一旦加上 .position 之後,.frame 會變成整個
            //    GeometryReader 的大小,不再是單一卡片的大小。
            // -------------------------------------------
            .position(center)         // ⭐️ put item view in position
    }
    
    /// 2.2.2.1: index of item
    func index(of item: Item) -> Int {
        items.index(of: item)!        // 🌀Array + .index(of:)
    }                                 //     where Element: Identifiable
}

Last updated

Was this helpful?