SwiftUI Vertical ScrollView not Updating Width when Content Changes


As of iOS 13.4.1 and Xcode 11.4.1 there is an issue with the SwiftUI ScrollView. It looks like the vertical ScrollView will be stuck with the initial width of its content, so if it's created with content of width zero, then it will stay with the width of zero points even when content changes, which results in no content being displayed.


We can look at an example when we initially have an empty array of items, and then append items to that array when the user taps a button.


struct ContentView: View {

    @State var items: [String] = []
    
    var body: some View {
        NavigationView {
            ScrollView {
                ForEach(items, id: \.self) { item in
                    Text(item)
                }
            }
            .navigationBarItems(trailing:
                Button("Add") {
                    self.items.append(UUID().uuidString)
                }
            )
        }
    }
}


Even when we have multiple items, we still cannot see them on screen, because the ScrollView didn't update its width.


We can try initializing our state with a short String and see that when we add new longer items, the ScrollView still has the width of the first short item.


@State var items: [String] = ["foo"]


We submitted a bug report to Apple (FB7666644), but in the mean time there are some workarounds that we can use in order to display our content properly.



Possible Solutions


  1. We can set a fixed width on the content of the ScrollView if we know exactly how wide we want it to be.


ScrollView {
    ForEach(items, id: \.self) { item in
        Text(item)
    }
    .frame(width: 500)
}


2. We can make it take the whole available width by embedding its content in a HStack with Spacers.


ScrollView {
    HStack {
        Spacer()
        VStack {
            ForEach(items, id: \.self) { item in
                Text(item)
            }
        }
        Spacer()
    }
}


3. We can embed the content in a GeometryReader and dynamically calculate its width.


ScrollView {
    GeometryReader { geo in
        VStack {
            ForEach(self.items, id: \.self) { item in
                Text(item)
            }
        }
        .frame(width: geo.size.width * 0.9)
    }
}


You can find the code for this article on our GitHub page.