63

XCode Version 12.4 (12D4e)

I have encountered this every time I have implemented a Lazy stack within a ScrollView:

  1. Add a LazyHStack to a horizontal ScrollView or a LazyVStack to a vertical ScrollView
  2. Add enough content such that the content size of the scroll view exceeds its bounds

Scenario 1 - Pull the scrollview beyond the bounds (as if you were pulling to refresh)

Expected behavior: It behaves as expected where the scrollview stays under your finger

Observed behavior: It stutters and jumps

Scenario 2 - Scroll fast to the edge so that it has to bounce

Expected behavior: It bounces smoothly

Observed behavior: It stops and jitters when it reaches the edge, but doesn't bounce

My theory My theory is that due to using a Lazy stack, when a view goes off the screen it gets removed from the view hierarchy, creating a stutter.

I'm wondering if anyone else has encountered this? Is this a bug in SwiftUI? I've reliably reproduced this for months across different projects and end up resorting to not using Lazy stacks which I wish I could.

Sample code

        ScrollView {
          LazyVStack {
            ForEach(viewModel.items) { items in
              SomeView(viewModel: .init(context: viewModel.context, item: item))
            }
          }

Note: Stutter only happens at the top of the scroll view

** Updated July 10, 2021 **

This is still happening in iOS 15, Version 13.0 beta (13A5155e).

In the video below, notice the behavior of the scrollbar and the stuttering when we get to the bottom:

https://youtu.be/z2pybl5yYqk

** Updated July 19, 2021 **

I ripped everything out in my view and built it back up one by one — the LazyVStack begins to stutter as soon as I put a VStack/HStack/ZStack around a simple Text element.

If I add fixedSize(horizontal: false, vertical: true) to the Text element it seems to stop stuttering. As soon as I add a UIViewRepresentable of variable height, it starts to stutter again.

It seems like in a LazyStack, every child needs to be some sort of fixed size or a purely SwiftUI view to work.

I'll keep digging in. Must... solve...

chrysb
  • 1,351
  • 2
  • 13
  • 20
  • your theory is not correct, once Lazy Stack load a View and that View does not need update, it will stay at memory until for new update of that view. – ios coder Mar 08 '21 at 03:11
  • 1
    @swiftPunk Can you link to any documentation? I didn't say it was removed from memory, I was theorizing it's removed from the view hierarchy when it's off screen – chrysb Mar 08 '21 at 18:51
  • 1
    I can confirm that I am also experiencing this issue. I filed an issue in feedback, and also filed a code-level support ticket. Interestingly, I did not receive a response for the code-level support ticket, which I've never had happen before. This is happening in multiple areas of the app, so it's a terrible user experience. If I hear back, I'll report back here. This is the first post that I've been able to find with repro steps & result. I'll email Apple about the code-level support ticket. I reported this Nov, Dec FB8899884, FB8941709, & DTS last month 762092933 (no reply). – Tom GODDARD Mar 16 '21 at 10:05
  • 1
    I'm having the exact same issues with a LazyVStack in a ScrollView. Hope it can be resolved. Your post very clearly explains the issue so thanks for that. It's very different than the expected bounce behavior which I get when I switch to a list view. Hope they fix it for the LazyVStack/HStack soon – alionthego Apr 03 '21 at 11:24
  • I'm struggling with this very bug right now. Did you have any news on those tickets at all @TomGODDARD ? I haven't been able to find a work around yet. – nodediggity Apr 08 '21 at 17:08
  • Hi @chrysb, have you also faced an issue where LazyVStack is not loading some view? Let's say a screen has multiple views and each view is loading its data from some service. Sometimes, one of the views is not showing the data until some UI update event happens. Let me know if you need more information. – schinj Jul 19 '21 at 20:33
  • @schinj I haven't experienced that. Please create a new question and send me the link and I'm happy to take a look! – chrysb Jul 19 '21 at 22:53
  • I have done more debugging and summarized everything here, including a sample project: https://stackoverflow.com/questions/68459594/content-with-variable-height-in-a-lazyvstack-inside-a-scrollview-causes-stutteri – chrysb Jul 20 '21 at 18:15
  • 2
    It has been foretold that one day Apple will produce software that actually works. But its not this day. – Petar Aug 27 '21 at 02:00
  • Any solutions? Running into this problem currently. As soon as I change to a scaled to fit child view everything works smoothly. – Trevor Apr 07 '22 at 02:11
  • 2
    Bug is in iOS 16 – Jonny Mar 22 '23 at 08:13

7 Answers7

16

I got a response from the DTS, and they confirmed it’s a bug, but no workaround. You can reference my feedback ID and file a feedback item. I imagine they’ll address it with the new swift version, because I think it’s probably a legacy defect, and might cause breaking changes. In other words, it has to do with the native components and navigation bar, and they have to break some things to fix it. This means SwiftUI apps in iOS 14 might not be compatible, ever. But I’m just speculating. I’ll let everyone know if I get any news. It’s really a major blocker, and completely ruins the user experience IMO.

Tom GODDARD
  • 641
  • 6
  • 9
  • 1
    I got another confirmation today from Apple that this is still a bug: "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." – chrysb Jul 27 '21 at 18:01
  • Finding this answer saved me a lot of time searching for what *I* did wrong... Appreciate you! – Sam Doggett Oct 07 '22 at 17:38
7

I can confirm the stutter issue is still present in iOS 15 with Xcode 13. I am not sure what is causing it, but it seems related to the way LazyStacks create and layout items.

This is a MWE that reproduces the problem:

ScrollView(.horizontal) {
    LazyHStack {
        Color.red.frame(width: 450)
        Color.green.frame(width: 250)
        Color.blue.frame(width: 250)
    }
}
.frame(width: 350)

In this example the frame of the red color seems "wide enough" to cause the stutter when bouncing on the leading edge of the screen.

Reducing the width slightly makes the stutter go away:

ScrollView(.horizontal) {
    LazyHStack {
        Color.red.frame(width: 400)
        Color.green.frame(width: 250)
        Color.blue.frame(width: 250)
    }
}
.frame(width: 350)

Note: tested on an iPhone Xs Max with Xcode 13 beta 1 and iOS 15 beta 1. For this particular example the issue only happens on the device (maybe because I cannot scroll fast enough on the simulator). But I've had this issue on more complex views on the simulator too.

  • I tested on Xcode 13.4.1 on an iPhone 11 running iOS 15.6.1, and while I didn't see the stutters, I did see strange behavior with the scroll indicator on the bottom of the screen. Seems this might still need more work... – Learn OpenGL ES Sep 08 '22 at 14:23
2

I added a clear rectangle

ScrollView {
    LazyVStack {
        Rectangle().foregroundColor(.clear).frame(height: 1.0)
        ...
    }
}

and it seemed to solve most of my problems I have with stuttering in the LazyVStack under ScrollView.

0

Try disabling the ScrollView bounce. Add the below line in onAppear or init,

UIScrollView.appearance().bounces = false

0

I had a very similar issue and disabling the edgesForExtendedLayout worked for me.

If the SwiftUI view is used within a UIHostingController you could try adding the following to the UIHostingController subclass:

class MyViewController: UIHostingController<MyView> {

  init() {
        let view = MyView()
        super.init(rootView: view)
  }

  override func viewDidLoad() {
      super.viewDidLoad()
      edgesForExtendedLayout = []
  }

This was reproducible under xcode 13.2.1 and iOS 15.2

yannxou
  • 71
  • 2
  • 4
0

This still seems to be an issue on IOS 16. I am unable to fix it on the simulator but the answer by user14518353 seems to work on a device. For those who cannot find the mentioned users answer add this to onAppear or init -

UIScrollView.appearance().bounces = false

EDIT:

While it mostly seems fixed i have managed to reproduce the issue once with the above fix & adding Rectangle().foregroundColor(.clear).frame(height: 1.0) to the top of the LazyVStack seems to also help. Currently I recommend setting both bounces and adding in the rectangle as that combination seems to be the best fix for me.

SereneYeti
  • 39
  • 3
-2

I have confirmed that the issue is resolved in iOS 15. Unsure if this helped, but also recompiled the app with Xcode 13.

Tom GODDARD
  • 641
  • 6
  • 9
  • 5
    Tom, thanks for the hand! Unfortunately, I just reproduced this in iOS 15. I have a LazyVStack in a ScrollView and when I fill it with a ton of items, it starts to stutter. I notice that the scrollbar on the right hand side also seems to change size and jump up and down as I scroll. A hint that something odd is happening. – chrysb Jul 11 '21 at 03:38
  • @chrysb I would ask you to report this using the Feed Assistant as I did already. Unfortunately, the assistant says there are no similar reports so it would be great if we could start getting more people to report this issue to Apple. My feedback title was something along the lines of "Stutter while bouncing on a ScrollView using LazyStacks". – seriouslysupersonic Jul 12 '21 at 14:22
  • 7
    This is definitely still happening on iOS 15 and Xcode. – Danny Bravo Nov 15 '21 at 16:26
  • 4
    Still not resolved on iOS 16... – domfz Oct 26 '22 at 13:30
  • 1
    I have reported the bug also to Apple Ticket FB12397238. I genuinely hope the issue will be resolved soon enough. It will be with me for many years to come until its fixed. If anyone finds a solution please share. Thanks – Wael Jun 20 '23 at 10:24