dkk / WrappingHStack

A SwiftUI HStack with the ability to wrap contained elements

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Layout is incorrect when available width changes

rzulkoski opened this issue · comments

Describe the bug
The initial layout is correct, but changes to the available width do not cause the layout to update.

To Reproduce
Steps to reproduce the behavior:

  1. Create a view with a WrappingHStack containing a few dozen items.
  2. Notice the initial layout is correct.
  3. Take some action that affects the available width, such as device rotation.
  4. Notice the layout is now incorrect.

Expected behavior
When the available width changes, the layout should be recalculated correctly.

Screenshots
Correct initial layout in portrait

Incorrect layout after rotating to landscape

Incorrect layout after subsequent rotation back to portrait

Context:

  • WrappingHStack version: 2.2.9
  • Model: iPhone 14 Pro Max
  • OS: iOS 16.2

Additional context
After extensive debugging, I have determined that the issue lies with LineManager. Notice that it will only calculate the firstElementOfEachLine once with the initial width, but over time new width values are passed to InternalWrappingHStack by the GeometryReader. This will cause the InternalWrappingHStack to redraw, but it's still using stale firstItemOfEachLine values.

In my testing the InternalWrappingHStack is only redrawn when the width changes, which matches the desired lifetime of the cached firstItemOfEachLine value. I believe the fix would be to remove line manager and allow InternalWrappingHStack to own its logic once again.

Simplified Example
Paste this into ContentView.swift within a fresh project after adding WrappingHStack as an SPM dependency:

import SwiftUI
import WrappingHStack

struct ContentView: View {
    @State var itemIndexes: [Int] = Array(1...30)

    var body: some View {
        WrappingHStack(self.itemIndexes, id: \.self, lineSpacing: 8) { index in
            Text("Item: \(index)")
                .padding(3)
                .background(Rectangle().stroke())
        }
        .background(.gray.opacity(0.2))
        .padding()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Here's some screenshots of the behavior after applying the fix:

Correct initial layout in portrait

Correct layout after rotating to landscape

Correct layout after subsequent rotation back to portrait