๐Ÿ“ฆRatioRetainingLayout

SwiftUI โŸฉ Layout โŸฉ Grids โŸฉ examples โŸฉ ItemsView โŸฉ RatioRetainingLayout

โฌ†๏ธ ้œ€่ฆ๏ผš Vector2D, floating +โˆ’โจ‰รท int

import SwiftUI

// ๆ นๆ“š้ …็›ฎๆ•ธ้‡ใ€proposed sizeใ€่ˆ‡ๅธŒๆœ›็š„ cell ๆฏ”ไพ‹๏ผŒ่จˆ็ฎ—ๆœ€ไฝณ็š„ๅˆ—ๆ•ธใ€่กŒๆ•ธใ€‚
// ๅฆ‚ๆžœๅˆ‡ๅ‰ฒๅ‡บไพ†็š„ๆ ผๅญ๏ผŒๅ…ถ้•ทๅฏฌๆฏ”ๆœ€ๆŽฅ่ฟ‘ไฝฟ็”จ่€…ๆŒ‡ๅฎš็š„ๆฏ”ไพ‹๏ผŒๅ‰‡็‚บๆœ€ไฝณๆŽ’็‰ˆๆ–นๅผใ€‚

/// help to compute layout informations for item views that
/// will try to maintain their aspect ratio.
/// ```
/// RatioRetainingLayout(itemCount: n, in: size, cellAspectRatio: r)
/// ```
struct RatioRetainingLayout: IndexedGridLayout {
    
    // โญ proposed size
    let size: CGSize
    
    // โญ best layout in rows x cols
    //   (to be calculated in `init`, with all available space taken)
    private(set) var rows: Int = 0   
    private(set) var cols: Int = 0
    
    init(
        _ idealCellAspectRatio: CGFloat = 1,   // ideal cell aspect ratio
        count n: Int,           // item count
        in size: CGSize         // proposed size
    ) {
        
        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 `idealAspectRatio`
        var bestLayout: (rows: Int, cols: Int) = (1, n)    // start from 1 row
        var smallestVariance: CGFloat?
        
        // start to find best layout from 1 column
        for cols in 1...n {
            
            // โญ calculate how many columns we need
            let rows = (n / cols) + (n % cols > 0 ? 1 : 0)
            
            // cellAspectRatio = (cell width) / (cell height)
            //                 = (width/cols) / (height/rows)
            //                 = (width/height) / (cols/rows)
            //                 = size.aspectRatio * rows/cols
            // -----------------------------------------------------------
            // โญ๏ธ rows: โ†˜ (decreasing)
            //    cols: โ†—๏ธŽ (strictly increasing) 
            //    โ‡’ cellAspectRatio: โ†˜ (strictly decreasing)
            // -----------------------------------------------------------
            // ๐Ÿ…ฟ๏ธ Vector2D (๐ŸŒ€CGSize + .aspectRatio)
            // ๐ŸŒ€CGFloat * Int (custom operator)
            let cellAspectRatio = size.aspectRatio * rows / cols
            
            // current variance with ideal aspect ratio
            let variance = self.variance(cellAspectRatio, idealCellAspectRatio)
            
            // โญ๏ธ if `smallestVariance` not set, or `variance` 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 for loop
                break  
            }
        }
        
        // save best layout found
        self.rows = bestLayout.rows
        self.cols = bestLayout.cols
    }
}

extension RatioRetainingLayout {
    
    /// cell size
    public var cellSize: CGSize {
        (rows == 0 || cols == 0) 
        ? .zero 
        // ๐Ÿ…ฟ๏ธ Vector2D (๐ŸŒ€CGSize(w,h)), CGFloat / Int
        : CGSize(size.width  / cols, size.height / rows)
    }
    
    /// center point of cell at index `i`
    public func cellCenter(at i: Int) -> CGPoint {
        let rowIndex = i / cols
        let colIndex = i % cols
        return (rows == 0 || cols == 0) 
        ? .zero 
        // ๐Ÿ…ฟ๏ธ Vector2D, CGFloat + Int
        : cellSize.point ** [0.5 + colIndex, 0.5 + rowIndex]
    }
    
    /// 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))
    }
}

Last updated