โœจTestLittleSquares

โฌ†๏ธ ้œ€่ฆ๏ผš VGridForEach, .dimension(), .shadowedBorder(), .clamped(in:)

// 2022.02.14
import SwiftUI

// ----------------------------
//     ๐Ÿ‘” TestLittleSquares
// ----------------------------

struct TestLittleSquares: View {
    
    // view state
    @State private var width: CGFloat = 300    // offered width
    
    var body: some View {
        VStack {
            controls                                 // slider
                .padding(.horizontal)
                .padding(.bottom)
            LittleSquaresView()                      // ๐Ÿ‘” LittleSquaresView
                .frame(width: width, height: 200)    // โญ๏ธ parent's offered width
                .dimension(.top, arrow: .blue)       // ๐ŸŒ€View+.dimension()
                .shadowedBorder()                    // ๐ŸŒ€View+.shadowedBorder()
        }
    }
}

extension TestLittleSquares {
    /// view controls
    var controls: some View {
        VStack(spacing: 20) {
            // ๐ŸŽ› `Slider` to control the offered width
            Slider(value: $width, in: 200...500, step: 1)
                .frame(width: 300)
            // label for offered width
            Text("offered width: \(Int(width))")
                .font(.system(.caption, design: .monospaced))
                .foregroundColor(.secondary)
        }
        .padding()
    }
}

struct TestLittleSquares_Previews: PreviewProvider {
    static var previews: some View {
        TestLittleSquares()
    }
}

// ----------------------------
//     ๐Ÿ‘” LittleSquaresView
// ----------------------------

struct LittleSquaresView: View {
    
    var count: Int = 25           // squares count
    var size: CGFloat = 50        // square size
    var spacing: CGFloat = 5      // spacing between squares
    
    var adaptiveCols: [GridItem] {
        [GridItem(
            .adaptive(minimum: size, maximum: size), 
            spacing: spacing      // spacing between cols
        )]
    } 
    
    var body: some View {
        GeometryReader { geo in
            // ๐Ÿ‘” VGridForEach = LazyVGrid + ForEach
            VGridForEach(
                0 ..< count, 
                columns: adaptiveCols,     // adaptive columns
                spacing: spacing           // spacing between rows
            ) { i in
                (
                    i < maxItemCount(in: geo.size)// max item count inside frame
                    ? Color.yellow                // inside frame
                    : Color.pink.opacity(0.7)     // outside frame
                )
                    .frame(self.size)    // ๐ŸŒ€View+.frame()
                    .overlay {
                        Text("\(i+1)").font(.caption).foregroundColor(.black)
                    }
            }
        }// end: GeometryReader
    }
}

extension LittleSquaresView {
    /// calculate how many columns can fit in the offered size.
    func cols(in size: CGSize) -> Int {
        Int(((size.width + spacing) / (self.size + spacing)))
            .clamped(in: 0...count)
    }
    /// max rows that can fit into the offered size.
    func maxRows(in size: CGSize) -> Int {
        Int(((size.height + spacing) / (self.size + spacing)))
            .clamped(in: 0...)
    }
    /// max item count that can fit into the offered size.
    func maxItemCount(in size: CGSize) -> Int {
        (cols(in: size) * maxRows(in: size)).clamped(in: 0...count)
    }
}

Last updated