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

SwiftUI NavigationStack重复打开同一视图问题求助

SwiftUI NavigationStack 导航时视图重复实例化问题

我是iOS开发新手,在用SwiftUI开发时遇到了列表导航的问题,一直没能解决。

核心问题

  • 首次点击PostListView中的列表项时,PostDetailsView会针对同一帖子被实例化两次,破坏了业务逻辑;再次点击列表项时,PostDetailsView仅被实例化一次,功能正常。
  • 如果使用带destination和label的NavigationLink,功能可以正常运行,但我需要从PostDetailsView导航到嵌套的PostDetailsView(当博客中存在其他帖子链接时),这必须借助path和NavigationStack实现编程式导航才能完成。

原始实现代码

struct BlogNavigationGraph: View {
   
    @State private var path = [PostUiModel]()

    var body: some View {
        NavigationStack(path: $path) {
            PostListView(viewModel: /* pass view model here */)
                .navigationDestination(for: PostUiModel.self) { post in
                    PostDetailsView(post: post, viewModel: /* pass view model here */)
                }
        }
    }
}

struct PostListView: View {

    @ObservedObject var viewModel: PostListViewModel

    var body: some View {
        Group {
            switch viewModel.state {
                case .loading: LoadingView()
                case .error: ErrorView()
                case .hasPosts(let data): PostList(data)
            }
        }.onAppear { viewModel.onViewDidAppear() }
    }

}

struct PostList: View {
    let data: [PostUiModel]

    var body: some View {
        List(data, id: \.id) { item in
            PostItem(post: item)
        }
    }
}

struct PostItem: View {
    
    let post: PostUiModel

    var body: some View {
        NavigationLink(value: post) {
            PostItemContent(post: post)
        }
    }

}

struct PostItemContent: View {
 
    var body: some View { 
        // complex content here
    }

}

简化复现代码

为了复现问题,我写了一个带硬编码数据的简化示例:

import SwiftUI

struct UiModel: Identifiable, Hashable {
    let id: Int
    let title: String
}

struct TestView: View {
    
    @State private var path = [UiModel]()
    
    var body: some View {
        
        NavigationStack(path: $path) {
            TestList()
                .navigationDestination(for: UiModel.self) { item in
                    TestItemDetails(viewModel: TestItemDetailsViewModel(),
                                    item: item) { target in
                        path.append(target)
                    }
                }
        }
    }
}

struct TestList: View {
    
    let data = [
        UiModel(id: 1, title: "Title 1"),
        UiModel(id: 2, title: "Title 2"),
        UiModel(id: 3, title: "Title 3")
    ]
    
    var body: some View {
        List(data) { item in
            TestItem(item: item)
        }
    }
}

struct TestItem: View {
    
    let item: UiModel
    
    
    init(item: UiModel) {
        self.item = item
    }
    
    var body: some View {
        NavigationLink(value: item) {
            TestItemContent(item: item)
        }
    }
    
}

struct TestItemContent: View {
    
    let item: UiModel
    
    init(item: UiModel) {
        self.item = item
    }
    
    var body: some View {
        Text(item.title)
    }
    
}

struct TestItemDetails: View {
    
    @ObservedObject var viewModel: TestItemDetailsViewModel
    
    let item: UiModel
    let onRedirect: (UiModel) -> Void
    
    var body: some View {
        VStack {
            Text("Details for: {\(item.id), \(item.title)}")
            
            switch viewModel.state {
            case .normal: Text("Normal")
            case .error: Text("Error")
            }
            
            Button("Go to next") {
                onRedirect(
                    UiModel(
                        id: 5,
                        title: "Redirected item title"
                    )
                )
            }
        }.onAppear { viewModel.onViewDidAppear() }
    }
}

enum TestItemDetailsState {
    case normal
    case error
}

class TestItemDetailsViewModel: ObservableObject {
    
    @Published var state: TestItemDetailsState = .normal
    
    func onViewDidAppear() {
        state = .error
    }
    
}

struct TestView_Previews: PreviewProvider {
    static var previews: some View {
        TestView()
    }
}

复现的现象

在这个示例中:

  • TestItem.init会针对每个项被调用两次,TestItemContent.init针对每个项被调用一次
  • 首次点击列表项时,TestItemDetails.init会被调用两次;再次点击则仅调用一次,功能正常
  • 当TestItemDetails.onAppear触发时,TestItemDetailsViewModel.onViewDidAppear会被调用,但状态变更无法传递到TestItemDetails视图
  • 若不使用.navigationDestination修饰符,功能可正常运行,但无法实现从当前详情视图推入另一个TestItemDetails视图的需求

有没有人能帮我分析下哪里出错了?


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 12:15:27