26

XCode Version 13.0 beta (13A5155e) & Targeting iOS 14 or 15

My goal is to create a chat view in SwiftUI. This requires creating a ScrollView with content of varying heights .

After extensive debugging, I've determined that if you have views within the ScrollView that do not have a fixed height, it will stutter when you scroll to the top of the view.

––––

PROJECT: Download this project and try for yourself

struct Message: Identifiable {
  let id = UUID()
  var text: String
}

struct ContentView: View {
  @State var items: [Message] = MockData.randomMessages(count: 100)
  
  var body: some View {
    VStack {
      Button("Shuffle items") {
        items = MockData.randomMessages(count: 100)
      }
      ScrollView {
        LazyVStack(spacing: 10) {
          ForEach(items) { item in
            Text(item.text)
              .background(colors.randomElement()!)
          }
        }
      }
    }
  }
}

My conclusion right now is that LazyVStack only works with child views that have fixed height. This issue alone prevents SwiftUI from being production ready.

Has anyone else tackled this?

RESPONSE FROM APPLE (July 27, 2021):

"On your Mac target this all works but I see there are scrolling issues on iOS. This issue is definitely a bug with SwiftUI on iOS. I recommend that rather than rewrite your app you use a UIViewRepresentable for your UIScrollView (or actually UITable / UICollection View would make the most sense here). If you use a re-usable view such as a table or collection these issues will almost certainly go away. You shouldn't need to rewrite your app but you should add a UIViewRepresentable if this issue is preventing a release."

8
  • offset is an unstable identifier. see this answer. Let me know if it was related. Commented Jul 23, 2021 at 21:32
  • This does not resolve the stuttering issue. You're right though, and I've updated the code to reflect a better approach. Commented Jul 24, 2021 at 0:26
  • Perhaps you can just implement this screen in UIKit instead? SwiftUI is still very new, it will probably take few more years till it gets close to UIKit in terms of reliability and stability. Commented Jul 24, 2021 at 17:54
  • Yeah, that's the fallback. This is one of the main views of the app — a chat view. To rewrite this whole thing in UIKit will be quite an undertaking, but I was hoping that SwiftUI could pull it off. This is literally the only thing that's stopping it :( The other alternative is I can use a List and lose my smooth transitions, use a VStack and never show more than 30 items at once, or rewrite it in UIKit. I'll probably revisit the List approach first. Commented Jul 24, 2021 at 19:33
  • I have pretty much the same issue and the answer from Apple is disappointing. If you go for List as I did, you will probably run into this: stackoverflow.com/questions/68383332/… -- so test early for leaks. ;) Still on my list to try is use List but with a limited amount of items to avoid the leaking. Or try other alternatives such as CACollectionView. Please update on your progress! Thanks for sharing! Commented Jul 28, 2021 at 17:59

3 Answers 3

2

For me the jittering was caused by having an onAppear modifier attached to the view within the ForEach (so it runs on every item in the collection). The jittering occurred even if the hook was running in a background thread and even if it wasn't doing anything at all.

Didn't fix it 100%, but my solution was to only add the onAppear modifier to the elements which truly needed it. In my case, I was using onAppear for continuous scroll (pagination). By only adding onAppear to the nth item in the list, was able to greatly reduce the jittering to a small blip when approaching the end of the list.

Here's a good article on how to apply a modifier conditionally.

Not sure this will help anyone who can't yet bump their min version to iOS 15, but hope it does!

Sign up to request clarification or add additional context in comments.

1 Comment

It's still the same in 2025 but in iOS 14, 15, 16 there is no issues . It's more in iOS 18
1

In iOS 15 this bug appears to be fixed.

In iOS 14 I would suggest showing the first n items (enough to fill the height of the screen with buffer) in a VStack and the rest in a LazyVStack. I found in most cases this removes the jitter.

ScrollView {
  VStack(spacing: 10) {
    ForEach(items.prefix(10)) { item in
      Text(item.text)
        .background(colors.randomElement()!)
      }
    }

    LazyVStack(spacing: 10) {
      ForEach(items.dropFirst(10)) { item in
        Text(item.text)
          .background(colors.randomElement()!)
        }
      }
    }
  }
}           

2 Comments

This still exhibits the same jittering behavior unless only the first 10 items are in the viewport
I've adjusted the code. I forgot the LazyVStack has to be inside the other VStack for it to work.
-1

works without any problems on macos 12.beta, xcode 13.beta, target ios 15 and macCatalyst. Tested on ios15 devices and macos 12. I also tried using 10000, and that works well. Maybe your issue happens on older ios and macos. You maybe interested in Swift UI overwhelmed by high-frequency @StateObject updates? where code struggles on ios14 but not ios15.

You could try other ways to see if you can improve the performance, such as:

 ForEach(items.indices, id: \.self) { index in
     Text(items[index]).background(colors.randomElement()!)
 }

or

 ForEach(Array(items.enumerated()), id: \.0) { index, item in
     Text(item).background(colors.randomElement()!)
 }

1 Comment

Thanks for taking a look! I targetted iOS 15 and it still happens on both the device and the simulator. I have iPhone 11 Pro Max and a pretty fast iMac, so I don't think performance is the issue here. Did you try shuffling the items and dragging down many times? Happens almost every time for me.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.