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

iOS 16中NavigationStack滚动视图惯性滚动返回崩溃问题求助

iOS 16.3中NavigationStack与ScrollView惯性滚动导致的并发崩溃解决方案

问题本质

这是iOS 16.x版本SwiftUI的已知bug:当绑定到NavigationStack的NavigationPath(存储在ObservableObject的@Published属性中),在ScrollView处于惯性滚动状态时被修改(比如点击返回按钮触发路径弹出),会出现并发读写冲突,触发Simultaneous accesses崩溃。iOS 17.3及以上版本苹果已修复该问题。


解决方案

方案1:使用本地@State管理导航路径(最简方案)

将导航路径从全局ObservableObject迁移到RootView的本地@State变量,避免跨对象属性绑定带来的线程安全风险:

struct RootView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            VStack {
                Button("Go to child") {
                    path.append(ChildRoute())
                }
                .buttonStyle(.borderedProminent)
            }
            .navigationDestination(for: ChildRoute.self) { _ in
                ScrollView {
                    LazyVStack {
                        Color.indigo.frame(height: 400)
                        Color.red.frame(height: 400)
                        Color.green.frame(height: 400)
                        Color.purple.frame(height: 400)
                        Color.orange.frame(height: 400)
                    }
                }
            }
        }
    }
}

struct ChildRoute: Hashable { }

方案2:给ObservableObject的路径添加串行队列保护

如果必须通过ObservableObject统一管理导航逻辑,用串行队列确保对path的读写操作原子执行,避免并发冲突:

class Communicator: ObservableObject {
    private let navigationQueue = DispatchQueue(label: "com.yourapp.navigation.queue")
    private var _path = NavigationPath()
    
    @Published var path: NavigationPath {
        get { navigationQueue.sync { _path } }
        set { navigationQueue.sync { _path = newValue } }
    }

    func navigate() {
        navigationQueue.async { [weak self] in
            guard let self = self else { return }
            self._path.append(ChildRoute())
            // 手动发送更新通知,确保SwiftUI同步视图
            DispatchQueue.main.async {
                self.objectWillChange.send()
            }
        }
    }
}

通过私有串行队列封装_path的读写,避免@Published内部的并发访问问题,同时手动触发视图更新。

方案3:自定义返回按钮,先中断ScrollView惯性滚动

在Child视图中自定义返回按钮,强制停止ScrollView的惯性滚动后再执行导航返回,切断滚动回调与路径修改的并发时机:

// 封装可控制滚动状态的ScrollView
struct ControlledScrollView<Content: View>: View {
    @State private var scrollProxy: ScrollViewProxy?
    let content: () -> Content
    
    var body: some View {
        ScrollView {
            ScrollViewReader { proxy in
                content()
                    .onAppear { scrollProxy = proxy }
            }
        }
        .onDisappear {
            // 强制滚动到顶部,中断惯性滚动
            scrollProxy?.scrollTo(0, anchor: .top, animation: .none)
        }
    }
}

// 在RootView的navigationDestination中使用
.navigationDestination(for: ChildRoute.self) { _ in
    ControlledScrollView {
        LazyVStack {
            Color.indigo.frame(height: 400)
            Color.red.frame(height: 400)
            Color.green.frame(height: 400)
            Color.purple.frame(height: 400)
            Color.orange.frame(height: 400)
        }
    }
    .toolbar {
        ToolbarItem(placement: .navigationBarLeading) {
            Button("Back") {
                communicator.path.removeLast()
            }
        }
    }
}

通过ScrollViewReader获取滚动代理,在视图消失或返回前强制终止惯性滚动,避免SwiftUI在滚动回调中访问导航路径。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 12:08:13