Home > database >  SwiftUI Navigation View goes back in hierarchy
SwiftUI Navigation View goes back in hierarchy

Time:03-04

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")
                }
            }
        }
    }
}

CodePudding user response:

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.

CodePudding user response:

When you tap Pin, your otherModules array is recreated and you do not have the view in Navigation Stack from where you navigated. Thus you are going back automatically, which is desired behaviour. So the solution is Don't destroy your array from where your NavigationLink is created. Make a temporary published array, load Other modules from that array and change the array onAppear like below:

As a workaround which is working in my end:

Add this line in DataManger:

@Published var tempOtherModules:[DataModel] = []

Change your ModulesList like below

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.tempOtherModules) { module in
                        ModulesListCell(module: module)
                    }
                }
            }.onAppear {
                dataStorage.tempOtherModules = dataStorage.otherModules
            }
        }
    }
}
  • Related