Write in the beginning

This article assumes that the reader already knows the basic usage of LazyVGrid+LazyHGrid.

  1. Fixed: Columns have a fixed width. The width is not affected by the size of the space. A column is displayed
  2. Flexible: You can specify the minimum and maximum values, depending on the size of the space, but the final display width must be within the range, not infinitely smaller or larger, display a column
  3. Adaptive: You can specify minimum and maximum values, which are affected by the size of the space, to display as many columns as possible

Note: No spacing is set – the default spacing is 8

Just lift the chestnuts

fixed

[GridItem(.fixed(60)))Copy the code

Conclusion: Regardless of container width, use fixed width and render width of 60

fixed+fixed

[
GridItem(.fixed(60)),
GridItem(.fixed(100)))Copy the code

Conclusion: Regardless of container width, use fixed width and render width of 60 + 8 + 100

flexible

[GridItem(.flexible(minimum: 100, maximum: 200)))Copy the code

Render width Max (min(container width, 200), 100)

flexible+flexible

GridItem(.flexible(minimum: 100)),
GridItem(.flexible(minimum: 180))
Copy the code

Things get interesting. You might have guessed 100+8+180 at first, but it turns out to be completely different. Let’s look at the layout process through calculation.

1. The recommended width is 300, the width is 300-8 = 292 after subtraction of the spacing, so the recommended width of each column is 292/2 = 146. The minimum value is 100 when comparing the first column, so the first column directly uses 146 as the recommended width, the remaining width is 146, and the minimum value is 180 when comparing the second column. Choosing 180 as the suggested width, the final calculated width is 146 + 8 + 180 = 334

Start rendering, (334-8) / 2 = 163, the first column is enough, render width is 163, the second column is not enough, use 180 directly, so the final render effect is 163 + 8 + 180

Note: the display is not centered, the center effect is (326 + 8-300) / 2 = 17 at the end of the first step, and 351-300-17 = 34 when the second step is not drawn, so the final display effect is 17 more on the left and 34 more on the right


And then you think you’re invincible, but reality hits you in the ass. Or [Flexible + Flexible]

GridItem(.flexible(minimum: 180)),
GridItem(.flexible(minimum: 100))
Copy the code

Do not doubt that life, in fact, or the original formula, or the original taste, let us begin

1. The recommended width is 300, and the width is 300-8=292 after subtraction of the spacing, so the recommended width of each column is 292/2 = 146. Compare the first column, and the minimum value is 180, so the first column directly takes up 180, and the remaining width is 292-180 = 112. The minimum value is 100, so the second column can occupy 112, and the final result is 180 + 8 + 112 = 300

2. Start rendering with 300, same as 1


flexible + fixed + flexible

GridItem(.flexible(minimum: 180)),
GridItem(.fixed(minimum: 100)),
GridItem(.flexible(minimum: 100))
Copy the code

1. Start width300Is after subtracting the fixed width and spacing300 minus 100 minus 8 minus 8 is 184After subtracting the fixed columns, there are two columns left, so the recommended width of the first column is184/2 is 92, compared with the first column, the minimum value is180So the first column is directly occupied180, the remaining width is184 minus 180 is 4, compare the second column, the minimum value100So the second column is occupied100, the final calculation result is180 plus 8 plus 100 plus 8 plus 100 is 396

The render width of the first column is 280/2 = 140. The minimum value for the first column is 180, so the first column occupies 180. The remaining width is 280-180 = 100, compared to the second column, the minimum value is 100, so the second column occupies 100, rendering, rendering width is 180 + 8 + 100 + 8 + 100 = 396

Note: the renderer starts and ends with the same width, and the display is centered


GridItem(.flexible(minimum: 100)),
GridItem(.fixed(minimum: 100)),
GridItem(.flexible(minimum: 180))
Copy the code

After subtracting the fixed width and spacing, it is 300-100-8-8 = 184. After subtracting the fixed columns, there are two remaining columns, so the recommended width of the first column is 184/2 = 92. Compared with the first column, the minimum value is 100, so 100 is used. The remaining width is 184-100 = 84. Compared with the second column, the minimum value is 180, so the second column occupies 180. The final calculation result is 100 + 8 + 100 + 8 + 180 = 396

2. The render width of the first column is 280/2 = 140. The render width of the first column is 280/2 = 140. The minimum value is 100, so the first column takes up 140 and the remaining width is 280-140 = 140, compared to the second column, the minimum value is 180, so the second column takes up 180 and the final render width is 140 + 8 + 100 + 8 + 180 = 436

Note: start 396 as the initial calculation render, start center layout is 48-300-48, start position is fixed, render width is 436, final render is 48-300-88

Conclusion – Render width = F (f(suggested width))

Step 1: Calculate

Flexible: Select the column width based on the average of the remaining width and the number of remaining columns and the current column limit. Then subtract the column width from the remaining width. Fixed: Direct adaptive: Next chapter - to be determined 4. Finally get the initial render widthCopy the code

Step 2: Render

1. Start from the result of step 1. Repeat Step 1 to Step 5 to get the final rendering resultCopy the code

Following the conclusion step, write the calculation code for this process briefly

extension View {

    func draw(_ items: [GridItem].contentWidth: CGFloat)- > [CGFloat] {
        let result1 = calculate(items, contentWidth: contentWidth).reduce(into: 0) { $0 + = The $1 }
        let spacings = items.dropLast().reduce(into: 0) { $0 + = (The $1.spacing ?? 8)}return calculate(items, contentWidth: result1 + spacings)
    }

    

    func calculate(_ items: [GridItem].contentWidth: CGFloat)- > [CGFloat] {
        var result = Array<CGFloat>(repeating: 0, count: items.count)

        var dynamicWidth = contentWidth
        
        var dynamicCount = items.count

        /// Subtract the width of the fixed-width column and the spacing between the columns
        for (index, item) in items.enumerated() {
            /// The spacing of the last line is not counted
            if index ! = items.count - 1 {
                dynamicWidth - = (item.spacing ?? 8)}if case .fixed(let width) = item.size {
                dynamicCount - = 1
                dynamicWidth - = width
            }

        }

        if dynamicCount > 0 {
            /// iterate over all columns
            for (index, item) in items.enumerated() {
                switch item.size {
                case let .flexible(minimum, maximum):
                    let singleWidth = dynamicWidth / CGFloat(dynamicCount)
                    let width = min(maximum, max(minimum, singleWidth))
                    result[index] = width

                    dynamicCount - = 1
                    dynamicWidth - = width
                case .fixed(let width):
                    result[index] = width
                case .adaptive:
                    // complete the next chapter here
                    continue
                @unknown default:
                    continue}}}return result
    }
}
Copy the code

Supporting the Demo