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

SwiftUI问题:列表更新时NavigationView详情页自动返回栈

问题原因

当列表重新排序后,SwiftUI的List会重新渲染列表项,原NavigationLink对应的列表项在新排序中位置发生变化,系统会误判该导航链接对应的视图已被移除,从而自动弹出导航栈。另外,IdAndScoreItem作为引用类型,@State数组的更新方式会触发不必要的视图刷新,进一步加剧这个问题。

解决方案

方案1:将IdAndScoreItem改为结构体(推荐,iOS 16+)

结构体是值类型,@State可以更精准地跟踪数据变化,避免引用类型带来的意外问题,同时让列表刷新更可控,不会导致NavigationLink关联失效。

修改后的完整代码:

import SwiftUI

struct IdAndScoreItem: Identifiable {
    let id: Int
    var score: Int
}

@main
struct CrazyBackStackProblemApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationStack {
                ListView()
            }
        }
    }
}

struct ListView: View {
    @State var items = (1...50).map { IdAndScoreItem(id: $0, score: 0) }
    
    func addScoreAndSort(item: IdAndScoreItem) {
        items = items.map { currentItem in
            guard currentItem.id == item.id else { return currentItem }
            var updatedItem = currentItem
            updatedItem.score += 1
            return updatedItem
        }
        .sorted { $0.score > $1.score }
    }
    
    var body: some View {
        List(items) { item in
            NavigationLink {
                ScoreClickerView(onClick: { addScoreAndSort(item: item) })
            } label: {
                Text("id: \(item.id) score:\(item.score)")
            }
        }
    }
}

struct ScoreClickerView: View {
    var onClick: () -> Void
    var body: some View {
        Text("tap me to increase the score")
            .onTapGesture {
                onClick()
            }
    }
}

方案2:手动控制导航状态(兼容iOS 15及以下)

如果需要兼容iOS 15,可使用NavigationView结合NavigationLink(isActive:)手动控制导航激活状态,避免列表排序变化导致导航栈自动弹出。

修改后的完整代码:

import SwiftUI

struct IdAndScoreItem: Identifiable {
    let id: Int
    var score: Int
}

@main
struct CrazyBackStackProblemApp: App {
    var body: some Scene {
        WindowGroup {
            NavigationView {
                ListView()
            }
            .navigationViewStyle(.stack)
        }
    }
}

struct ListView: View {
    @State var items = (1...50).map { IdAndScoreItem(id: $0, score: 0) }
    @State private var selectedItemId: Int? = nil
    
    func addScoreAndSort(itemId: Int) {
        items = items.map { currentItem in
            guard currentItem.id == itemId else { return currentItem }
            var updatedItem = currentItem
            updatedItem.score += 1
            return updatedItem
        }
        .sorted { $0.score > $1.score }
    }
    
    var body: some View {
        List(items) { item in
            NavigationLink(
                destination: ScoreClickerView {
                    addScoreAndSort(itemId: item.id)
                },
                isActive: Binding(
                    get: { selectedItemId == item.id },
                    set: { isActive in
                        if isActive {
                            selectedItemId = item.id
                        } else {
                            selectedItemId = nil
                        }
                    }
                )
            ) {
                Text("id: \(item.id) score:\(item.score)")
            }
        }
    }
}

struct ScoreClickerView: View {
    var onClick: () -> Void
    var body: some View {
        Text("tap me to increase the score")
            .onTapGesture {
                onClick()
            }
    }
}
关键说明
  • 结构体替代类:值类型的结构体让@State能准确跟踪每个item的变化,避免引用类型带来的意外数据共享问题,列表刷新更稳定。
  • NavigationStack替代NavigationView:iOS 16+推出的NavigationStack比旧的NavigationView更稳定,对列表项变化的处理更友好。
  • 手动控制导航状态:通过isActive绑定管理导航激活状态,即使列表排序变化,只要selectedItemId保持正确,导航栈就不会意外弹出。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.24 19:24:20