Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI Navigation View goes back in hierarchy

I have a problem with Navigation View hierarchy.

All screens in my app use the same ViewModel.

When a screen inside navigation link updates the ViewModel (here it is called DataManager), the navigation view automatically goes back to the first screen, as if the "Back" button was pressed.

Here's what it looks like

I tried to shrink my code as much as I could

struct DataModel: Identifiable, Codable {
  var name: String
  
  var isPinned: Bool = false
  var id: String = UUID().uuidString
}
class DataManager: ObservableObject {
    @Published private(set) var allModules: [DataModel]
    
    var pinnedModules: [DataModel] {
        allModules.filter { $0.isPinned }
    }
    
    var otherModules: [DataModel] {
        allModules.filter { !$0.isPinned }
    }
    
    func pinModule(id: String) {
        if let moduleIndex = allModules.firstIndex(where: { $0.id == id }) {
            allModules[moduleIndex].isPinned = true
        }
    }
    
    func unpinModule(id: String) {
        if let moduleIndex = allModules.firstIndex(where: { $0.id == id }) {
            allModules[moduleIndex].isPinned = false
        }
    }
    
    static let instance = DataManager()
    
    fileprivate init() {
        allModules =
        [DataModel(name: "One"),
         DataModel(name: "Two"),
         DataModel(name: "Three"),
         DataModel(name: "Four"),
         DataModel(name: "Five")]
    }
}

struct ModulesList: View {
    @StateObject private var dataStorage = DataManager.instance
    
    
    var body: some View {
        NavigationView {
            List {
                Section("Pinned") {
                    ForEach(dataStorage.pinnedModules) { module in
                        ModulesListCell(module: module)
                    }
                }
                
                Section("Other") {
                    ForEach(dataStorage.otherModules) { module in
                        ModulesListCell(module: module)
                    }
                }
            }
        }
    }
    
    fileprivate struct ModulesListCell: View {
        let module: DataModel
        
        var body: some View {
            NavigationLink {
                SingleModuleScreen(module: module)
            } label: {
                Text(module.name)
            }
        }
    }
}
struct SingleModuleScreen: View {
    @State var module: DataModel
    @StateObject var dataStorage = DataManager.instance
    
    var body: some View {
        HStack {
            VStack(alignment: .leading) {
                Text(module.name)
                    .font(.title)
                
                Button {
                    dataStorage.pinModule(id: module.id)
                } label: {
                    Text("Pin")
                }
            }
        }
    }
}

like image 762
boring_ape Avatar asked Sep 11 '25 23:09

boring_ape


1 Answers

I can guess because when your dataStorage changed, the ModulesList will be redrawn, that cause all current ModulesListCell removed from memory.

Your cells are NavigationLink and when it destroyed, the navigation stack doesn't keep the screen that's being linked.

I would recommend to watch this wwdc https://developer.apple.com/videos/play/wwdc2021/10022/ and you will know how to manage your view identity properly when your data's changed.

like image 60
Quang Hà Avatar answered Sep 14 '25 22:09

Quang Hà