swiftuinavigationsplitview

Preselection of list in NavigationSplitView with SceneStorage


I've got a list in sidebar of NavigationSplitView. The view in detail should change by selecting an item. For restoration of view I'm using SceneStorage like this:

import SwiftUI

enum ListType: String {
   case all = "all"
   case frequently = "frequently"
}

struct MyListItem: Identifiable, Hashable {
   let type: ListType
   let label: String
   let id = UUID()
}

struct SelectionMode: View {
   
   @State private var listItems = [
      MyListItem(type: .all, label: "alle Unterrichte"),
      MyListItem(type: .frequently, label: "am häufigsten")
   ]

   @SceneStorage("ListType") private var selectedType: ListType?

   var body: some View {
    
      NavigationSplitView {
        List(listItems, id: \.self.type, selection: $selectedType) { type in
            NavigationLink("\(type.label)", value: type)
        }
      } detail: {
        NavigationStack {
            SelectedList(viewType: selectedType?.rawValue ?? "all")
        }
    }
}

This works well, with one exception: The selected item is not highlighted in the list. First, “all” should be highlighted or the selected item (e.g. “frequently”) after the restoration. How can I achieve this?


Solution

  • Try this approach using a @State private var selectedItem: MyListItem? to do the List selection, as it matches the List display MyListItem type set with .tag(item).

    
    struct ContentView: View {
        var body: some View {
            SelectionMode()
        }
    }
    
    enum ListType: String {
        case all = "all"
        case frequently = "frequently"
    }
    
    struct MyListItem: Identifiable, Hashable {
       let type: ListType
       let label: String
       let id = UUID()
    }
    
    struct SelectionMode: View {
        
        @State private var listItems = [
            MyListItem(type: .all, label: "alle Unterrichte"),
            MyListItem(type: .frequently, label: "am häufigsten")
        ]
        
        // only for scenes (screens), will be lost when App is closed
     //   @SceneStorage("ListType") private var selectedType: ListType?
        
        // for the whole App, for my testing, keeps the storage after App is closed
        @AppStorage("ListType") private var selectedType: ListType?
        
        @State private var selectedItem: MyListItem?  // <--- here
        
        var body: some View {
            NavigationSplitView {
                List(listItems, selection: $selectedItem) { item in
                    Text(item.label).tag(item) // <--- here
                }
            } detail: {
                NavigationStack {
                    if let selectedItem = selectedItem {
                        SelectedList(viewType: selectedItem.type.rawValue)
                    } else {
                        Text("Select an item").font(.title)
                    }
                }
            }
            .onAppear {
                if selectedType == nil {
                    selectedType = .all
                }
                if let index = listItems.firstIndex(where: {$0.type == selectedType!}) {
                    selectedItem = listItems[index] // <--- here
                }
            }
            .onChange(of: selectedItem) {
                selectedType = selectedItem?.type  // <--- here
            }
        }
    }
    
    struct SelectedList: View {
        let viewType: String
        
        var body: some View {
            Text("Selected type: \(viewType)").font(.title)
        }
    }