SwiftUI: DoubleColumn: Pre-select view from Master?

Hello. There's very little information regarding DoubleColumnNavigationViewStyle and the documentation just say "No overview avaiable".


When using code below. It loads the detail-view on the right side without a navigationbar-title and it's not selected in the master-view on the side. If I select the view from the master-view, then it shows correctly. Is there a workaround to fix this? My idea was to maybe force a selection somehow.



Issue: Loads view, tab not selected, no title.

https://i.imgur.com/cl9dNGV.png


Fixed when selecting tab:

https://i.imgur.com/NJ0VjQ1.png


Apple Settings-app selects tab by default, mine don't.

https://i.imgur.com/FcaNYav.png



Same issue happens on my real iPad Pro as well in the simulator.


Deployment target: 13.2

iPadOS: 13.3


//
//  SettingsSwitcherView.swift
//
//  Created by Philip Andersen on 2020-01-28.
//  Copyright © 2020 Philip Andersen. All rights reserved.
//

import SwiftUI

struct SettingsSwitcherView: View {
    
    var body: some View {
        
        NavigationView {
            
            List {
                
                Section {
                    NavigationLink(destination: SettingsGeneralView()) {
                        Image(systemName: "gear")
                            .padding(7)
                        Text("General")
                    }
                    .padding(.vertical, 7)
                }
                
                Section(footer: Text("Version: \(BuildInfo.longVersionBuild())")) {
                    NavigationLink(destination: SettingsHelpView()) {
                        Image(systemName: "questionmark.circle")
                            .padding(7)
                        Text("Help")
                    }
                    .padding(.vertical, 7)
                    
                    NavigationLink(destination: SettingsContactView()) {
                        Image(systemName: "envelope")
                            .padding(7)
                        Text("Contact us")
                    }
                    .padding(.vertical, 7)
                    
                    NavigationLink(destination: SettingsAboutView()) {
                        Image(systemName: "info.circle")
                            .padding(7)
                        Text("About this app")
                    }
                    .padding(.vertical, 7)
                    
                }
                
            }
            .navigationBarTitle("Settings", displayMode: .large)
            .listStyle(GroupedListStyle())
            
            // Load default detail-view (loads, but not selected in master-view)
            SettingsGeneralView()
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
        
    }
}


Thanks.

Depending on the number of items in your list (in your example there aren't many) then you can use a state variable to keep track of which item is selected, and tell SwiftUI to display a particular detail view by setting that variable yourself.


For example, you have General, Help, Contact, and About items. That would work as an enum, like so:


enum SettingsPane: Equatable, Identifiable {
    case none
    case general
    case help
    case contact
    case about

    var id: SettingsPane { self }
}


With this in place, you create a state variable:


@State var whichPane: SettingsPane = .none


Now you change the declaration of each of your navigation links. There are three primary initializers, and you're using the first: init(destination:label:). There's also init(destination:isActive:), which takes a binding to a Bool value that will be used to show or hide it. Lastly there's init(destination:tag:selection:label:), which goes even further: it assigns a particular value to this link, and then binds to a variable of the same type. Whenever that variable's value is set to the assigned tag, the link fires and pushes the destination view.


Additionally, the links in all cases will set the appropriate value in any bindings when they're tapped on manually, so you'll be able to keep track of them that way, too.


So, using the enum and property defined above, you could have:


NavigationLink(destination: SettingsGeneralView(), tag: .general, selection: $whichPane) { ... }
NavigationLink(destination: SettingsHelpView(), tag: .help, selection: $whichPane) { ... }
NavigationLink(destination: SettingsContactView(), tag: .contact, selection: $whichPane) { ... }
NavigationLink(destination: SettingsAboutView(), tag: .about, selection: $whichPane) { ... }


It's then up to you record and reassign the selection value somehow. For instance, you might use .onAppear:


List { ... }
.onAppear { self.whichPane = self.globalSettings.currentSettingsPane }


Or you might bind directly to the value in your preferences:


NavigationLink(destination: ..., tag: ..., selection: $globalSettings.currentSettingsPane) { ... }


Another option would be to use the UserDefaults to both vend and record the value. A property wrapper that reads and writes the UserDefaults and vends a working Binding would serve. Something like this:


@propertyWrapper
struct UserDefault<Value> {
    let key: String
    let defaultValue: Value
    
    init(_ key: String, defaultValue: Value) {
        self.key = key
        self.defaultValue = defaultValue
    }
    
    var wrappedValue: Value {
        get { Self.getWrapped(forKey: key, defaultValue: defaultValue) }
        mutating set { Self.setWrapped(value: newValue, forKey: key) }
    }
    
    var projectedValue: Binding<Value> {
        Binding(get: { Self.getWrapped(forKey: self.key, defaultValue: self.defaultValue) },
                set: { Self.setWrapped(value: $0, forKey: self.key) })
    }
    
    static private func getWrapped(forKey key: String, defaultValue: Value) -> Value {
        UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
    }
    static private func setWrapped(value: Value, forKey key: String) {
        UserDefaults.standard.set(value, forKey: key)
    }
}


With this, you need only change the declaration of your state variable:


@UserDefault("my.app.settings.current-pane", defaultValue: .none)
var whichPane: SettingsPane


That should do what you need, and you've a few different options for ways to store & retrieve the value as necessary.

Using your solutions caused a lot of issues with the selection binding.

- "Generic parameter 'C0' could not be inferred"

- "Cannot convert value of type 'Binding<SettingsPane>' to expected argument type 'Binding<_?>'"

However, seeing the tag and selection as arguments for NavigationLink, it was very helpful. Now I get the proper navigation-bar just by using simple tags. So it loads correctly, half way there, it's still not grey-selected in the master-view, but it's functional now. 🙂


struct SettingsSwitcherView: View {

    @State var selection: Int? = 1
        
    var body: some View {
        
        NavigationView {
            
            List {
                                
                Section {
                    NavigationLink(destination: SettingsGeneralView(), tag: 1, selection: $selection) {
                        Image(systemName: "gear")
                            .padding(7)
                        Text("General")
                    }
                    .padding(.vertical, 7)
                }
                
                Section(footer: Text("Version: \(BuildInfo.longVersionBuild())")) {
                    
                    NavigationLink(destination: SettingsHelpView(), tag: 3, selection: $selection) {
                        Image(systemName: "questionmark.circle")
                            .padding(7)
                        Text("Help")
                    }
                    .padding(.vertical, 7)
                    
                    NavigationLink(destination: SettingsContactView(), tag: 4, selection: $selection) {
                        Image(systemName: "envelope")
                            .padding(7)
                        Text("Contact us")
                    }
                    .padding(.vertical, 7)
                    
                    NavigationLink(destination: SettingsAboutView(), tag: 5, selection: $selection) {
                        Image(systemName: "info.circle")
                            .padding(7)
                        Text("About this app")
                    }
                    .padding(.vertical, 7)
                    
                }
                
            }
            .navigationBarTitle("Settings", displayMode: .large)
            .listStyle(GroupedListStyle())
            SettingsGeneralView()
        }
        .navigationViewStyle(DoubleColumnNavigationViewStyle())
        
    }
    
}
Were you ever able to get it to highlight the default selected item?
SwiftUI: DoubleColumn: Pre-select view from Master?
 
 
Q