๐ฆ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))
}
}
algorithm revised from Grid example.
Last updated