在 ScrollView 内的 LazyVStack 中具有可变高度的内容会导致卡顿/跳跃

Content with variable height in a LazyVStack inside a ScrollView causes stuttering / jumping

提问人:chrysb 提问时间:7/21/2021 最后编辑:chrysb 更新时间:8/11/2022 访问量:4050

问:

XCode 版本 13.0 beta (13A5155e) & 针对 iOS 14 或 15

我的目标是在 SwiftUI 中创建一个聊天视图。这需要创建一个具有不同高度内容的 ScrollView。

经过大量调试,我确定如果 ScrollView 中的视图没有固定高度,则当您滚动到视图顶部时,它会卡顿。

––––

项目下载此项目并亲自尝试

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()!)
          }
        }
      }
    }
  }
}

我现在的结论是,它只适用于具有固定高度的子视图。仅此问题就阻止了 SwiftUI 为生产做好准备。LazyVStack

有没有人解决这个问题?

APPLE 的回应(2021 年 7 月 27 日):

“在您的 Mac 目标上,这一切都有效,但我看到 iOS 上存在滚动问题。这个问题绝对是 iOS 上 SwiftUI 的错误。我建议不要重写您的应用程序,而是将 UIViewRepresentable 用于 UIScrollView(或者实际上 UITable / UICollection View 在这里最有意义)。如果使用可重用的视图(如表或集合),这些问题几乎肯定会消失。你不需要重写你的应用,但如果这个问题阻止了发布,你应该添加一个 UIViewRepresentable。

iOS SwiftUI UI滚动视图

评论

0赞 Mojtaba Hosseini 7/24/2021
offset是一个不稳定的标识符。请看这个答案。如果相关,请告诉我。
0赞 chrysb 7/24/2021
这不能解决卡顿问题。不过你是对的,我已经更新了代码以反映更好的方法。
0赞 Leszek Szary 7/25/2021
也许您可以在 UIKit 中实现此屏幕?SwiftUI 仍然很新,在可靠性和稳定性方面可能还需要几年时间才能接近 UIKit。
0赞 chrysb 7/25/2021
是的,这就是后备方案。这是应用程序的主要视图之一 - 聊天视图。在 UIKit 中重写整个过程将是一项艰巨的任务,但我希望 SwiftUI 能够实现它。从字面上看,这是阻止它的唯一原因:(另一种选择是,我可以使用 a 并失去平滑过渡,使用 VStack 并且永远不会一次显示超过 30 个项目,或者在 UIKit 中重写它。我可能会先重新审视这种方法。ListList
0赞 Nicolas 7/29/2021
我有几乎相同的问题,苹果的答案令人失望。如果你像我一样去做,你可能会遇到这样的情况:stackoverflow.com/questions/68383332/......--所以要尽早测试是否有泄漏。;)我仍然在我的清单上尝试使用,但物品数量有限,以避免泄漏。或尝试其他替代方法,例如.请更新您的进度!感谢分享!ListListCACollectionView

答:

-1赞 workingdog support Ukraine 7/21/2021 #1

在 macOS 12.beta、Xcode 13.beta、目标 ios 15 和 macCatalyst 上没有任何问题。在 ios15 设备和 macOS 12 上测试。我也尝试使用 10000,效果很好。也许您的问题发生在较旧的 ios 和 macos 上。您可能对被高频@StateObject更新淹没的 Swift UI 感兴趣? 代码在 iOS14 上挣扎,但在 iOS15 上却没有。

您可以尝试其他方法来查看是否可以提高性能,例如:

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

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

评论

0赞 chrysb 7/21/2021
感谢您的观看!我以 iOS 15 为目标,它仍然发生在设备和模拟器上。我有iPhone 11 Pro Max和相当快的iMac,所以我认为性能不是问题所在。您是否尝试过多次洗牌并拖拽项目?对我来说几乎每次都会发生。
0赞 ryanlintott 6/27/2022 #2

在 iOS 15 中,此错误似乎已修复。

在 iOS 14 中,我建议在 VStack 中显示前 n 个项目(足以用缓冲区填充屏幕高度),其余项目在 LazyVStack 中显示。我发现在大多数情况下,这消除了抖动。

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()!)
        }
      }
    }
  }
}           

评论

0赞 akerra 7/9/2022
这仍然表现出相同的抖动行为,除非只有前 10 个项目在视口中
0赞 ryanlintott 7/10/2022
我已经调整了代码。我忘记了 LazyVStack 必须在另一个 VStack 中才能工作。
1赞 akerra 8/11/2022 #3

对我来说,抖动是由于将修饰符附加到视图中引起的(因此它在集合中的每个项目上运行)。即使钩子在后台线程中运行,即使它根本没有做任何事情,也会发生抖动。onAppearForEach

没有 100% 修复它,但我的解决方案是只将修饰符添加到真正需要它的元素中。就我而言,我用于连续滚动(分页)。通过仅添加到列表中的第 n 项,能够在接近列表末尾时将抖动大大减少到一个小点。onAppearonAppearonAppear

这是一篇关于如何有条件地应用修饰符的好文章

不确定这是否会帮助那些还不能将他们的最低版本升级到 iOS 15 的人,但希望它确实如此!