You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复SwiftUI tvOS ScrollView滚动卡顿抖动及ScrollViewProxy调用崩溃问题

问题根因

  1. tvOS 14.7版本中SwiftUI原生ScrollView对长列表的性能优化逻辑存在缺陷,会在滑动过程中暂停视图渲染,仅在滑动结束后才更新内容,导致滚动过程画面停滞。
  2. 使用ScrollViewReader实现时出现崩溃的核心原因是onFocusChange回调的触发时机处于视图更新周期内,此时直接修改@State变量、调用scrollTo方法都违反了SwiftUI的视图更新规则,因此触发崩溃和运行时警告。

方案一:修复现有ScrollViewReader实现的崩溃问题

仅需将onFocusChange闭包内的所有状态修改、滚动操作放入主队列异步执行,跳出当前视图更新周期即可解决崩溃问题,同时保留原有的顺滑滚动效果。
修改后的完整代码如下:

import Foundation
import SwiftUI

struct FocusableButton: View {
    @State var isFocused = false
    let index: Int
    var scrollView: ScrollViewProxy
    
    var body: some View {
        VStack {
            Text("Button - \(index)")
        }
        .id(index)
        .frame(width: 200, height: 100)
        .padding()
        .scaleEffect(isFocused ? 1.1 : 1)
        .background(isFocused ? Color.white : Color.blue)
        .focusable(true, onFocusChange: { focused in
            // 异步执行,避开视图更新周期
            DispatchQueue.main.async {
                isFocused = focused
                if focused {
                    scrollView.scrollTo(index)
                }
            }
        })
    }
}

struct TestView: View {
    var body: some View {
        ScrollViewReader { scrollView in
            ScrollView(.vertical, showsIndicators: false) {
                ForEach(0..<600) { index in
                    FocusableButton(index: index, scrollView: scrollView)
                }
            }
        }
    }
}

方案二:桥接UIKit UIScrollView彻底规避SwiftUI问题

如果需要更稳定的表现,避免旧版tvOS上SwiftUI的兼容性问题,可以用UIViewRepresentable封装UIKit的UIScrollView,完全由UIKit处理滚动逻辑,不存在原生SwiftUI ScrollView的滚动卡顿问题,也不需要依赖ScrollViewReader,还可以灵活关闭自动分页行为。
完整实现代码如下:

import SwiftUI
import UIKit

struct TVScrollView<Content: View>: UIViewRepresentable {
    let showsIndicators: Bool
    let disablePagination: Bool
    let content: Content
    
    init(showsIndicators: Bool = false, 
         disablePagination: Bool = true,
         @ViewBuilder content: () -> Content) {
        self.showsIndicators = showsIndicators
        self.disablePagination = disablePagination
        self.content = content()
    }
    
    func makeUIView(context: Context) -> UIScrollView {
        let scrollView = UIScrollView()
        scrollView.showsVerticalScrollIndicator = showsIndicators
        // 适配tvOS遥控器触摸输入
        scrollView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]
        // 按需求关闭自动分页
        scrollView.isPagingEnabled = !disablePagination
        
        // 托管SwiftUI内容视图
        let hostingVC = UIHostingController(rootView: content)
        hostingVC.view.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(hostingVC.view)
        
        NSLayoutConstraint.activate([
            hostingVC.view.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            hostingVC.view.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            hostingVC.view.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            hostingVC.view.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),
            hostingVC.view.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor)
        ])
        return scrollView
    }
    
    func updateUIView(_ uiView: UIScrollView, context: Context) {}
}

// 使用示例
struct TestView: View {
    var body: some View {
        TVScrollView {
            VStack(spacing: 20) {
                ForEach(0..<600) { index in
                    Button(action: {}) {
                        Text("Button - \(index)")
                            .frame(width: 200, height: 100)
                            .foregroundColor(.white)
                            .background(Color.blue)
                    }
                    .buttonStyle(.plain)
                }
            }
            .padding(.vertical, 30)
        }
        .edgesIgnoringSafeArea(.all)
    }
}

内容的提问来源于stack exchange,提问作者Shaybc

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 12:42:03