5

I have a SwiftUI app with a basic List/Detail structure. A new item is created from a modal sheet. When I create a new item and save it I want THAT list item to be selected. As it is, if no item is selected before an add, no item is selected after an add. If an item is selected before an add, that same item is selected after the add.

I'll include code for the ContentView, but this is really the simplest example of List/Detail.

struct ContentView: View {

    @ObservedObject var resortStore = ResortStore()
    @State private var addNewResort = false
    @State private var coverDeletedDetail = false

    @Environment(\.presentationMode) var presentationMode

    var body: some View {

        List {
            ForEach(resortStore.resorts) { resort in
                NavigationLink(destination: ResortView(resort: resort)) {

                    HStack(spacing: 20) {
                        Image("FlatheadLake1")
                        //bunch of modifiers

                        VStack(alignment: .leading, spacing: 10) {
                        //the cell contents
                        }
                    }
                }
            }
            .onDelete { indexSet in
                self.removeItems(at: [indexSet.first!])
                self.coverDeletedDetail.toggle()
            }

            if UIDevice.current.userInterfaceIdiom == .pad {
                NavigationLink(destination: WelcomeView(), isActive: self.$coverDeletedDetail) {
                    Text("")
                }
            }
        }//list
        .onAppear(perform: self.selectARow)
        .navigationBarTitle("Resorts")
        .navigationBarItems(leading:
        //buttons

    }//body

    func removeItems(at offsets: IndexSet) {
        resortStore.resorts.remove(atOffsets: offsets)
    }

    func selectARow() {
    //nothing that I have tried works here
        print("selectARow")
    }
}//struct

And again - the add item modal is extremely basic:

struct AddNewResort: View {
//bunch of properties

    var body: some View {
        VStack {
            Text("Add a Resort")
            VStack {
                TextField("Enter a name", text: $resortName)
                //the rest of the fields
            }
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding(EdgeInsets(top: 20, leading: 30, bottom: 20, trailing: 30))

            Button(action: {
                let newResort = Resort(id: UUID(), name: self.resortName, country: self.resortCountry, description: self.resortDescription, imageCredit: "Credit", price: Int(self.resortPriceString) ?? 0, size: Int(self.resortSizeString) ?? 0, snowDepth: 20, elevation: 3000, runs: 40, facilities: ["bar", "garage"])
                self.resortStore.resorts.append(newResort)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("Save Trip")
            }
            .padding(.trailing, 20)

        }
    }
}

To show the issue - The list with a selection:

enter image description here

The list after a new item created showing the previous selection:

enter image description here

Any guidance would be appreciated. Xcode 11.4

3
  • I need this too. And the ability to scroll to the top. Commented Apr 6, 2020 at 3:52
  • David Reich: To scroll to the top without animation you can add an .id() modifier to your list to rebuild it. user2698617: I don't see enough details in the code you provided to help you. You can use a List with a selection binding or you handle selection manually at your own. Commented Apr 6, 2020 at 6:12
  • Yes, you have probably rephrased the question more succinctly - I have not been able to figure out how to use Optional and Set instances directly for selection since SelectionManager is deprecated. If you could point me in the right direction I would appreciate it. Commented Apr 8, 2020 at 5:06

1 Answer 1

5

I tried to reconstitute your code as closely as could so that it builds. Here is what I have in the end. We have a list of resorts and when a new resort is saved in the AddNewResort sheet, if we are currently in split view (horizontalSizeClass is regular), we will select the new resort, otherwise just dismiss the sheet.

import SwiftUI

class ResortStore: ObservableObject {
    @Published var resorts = [Resort(id: UUID(), name: "Resort 1")]
}

struct ContentView: View {
    @ObservedObject var resortStore = ResortStore()
    @State private var addingNewResort = false

    @State var selectedResortId: UUID? = nil

    var navigationLink: NavigationLink<EmptyView, ResortView>? {
        guard let selectedResortId = selectedResortId,
            let selectedResort = resortStore.resorts.first(where: {$0.id == selectedResortId}) else {
                return nil
        }

        return NavigationLink(
            destination: ResortView(resort: selectedResort),
            tag:  selectedResortId,
            selection: $selectedResortId
        ) {
            EmptyView()
        }
    }

    var body: some View {

        NavigationView {
            ZStack {
                navigationLink
                List {
                    ForEach(resortStore.resorts, id: \.self.id) { resort in
                        Button(action: {
                            self.selectedResortId = resort.id
                        }) {
                            Text(resort.name)
                        }
                        .listRowBackground(self.selectedResortId == resort.id ? Color.gray : Color(UIColor.systemBackground))
                    }

                }
            }
            .navigationBarTitle("Resorts")
            .navigationBarItems(trailing: Button("Add Resort") {
                self.addingNewResort = true
            })
                .sheet(isPresented: $addingNewResort) {
                    AddNewResort(selectedResortId: self.$selectedResortId)
                        .environmentObject(self.resortStore)
            }

            WelcomeView()
        }

    }

}

struct ResortView: View {
    let resort: Resort

    var body: some View {
        Text("Resort View for resort name: \(resort.name).")
    }
}


struct AddNewResort: View {
//bunch of properties

    @Binding var selectedResortId: UUID?

    @State var resortName = ""

    @Environment(\.presentationMode) var presentationMode
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @EnvironmentObject var resortStore: ResortStore

    var body: some View {
        VStack {
            Text("Add a Resort")
            VStack {
                TextField("Enter a name", text: $resortName)
                //the rest of the fields
            }
            .textFieldStyle(RoundedBorderTextFieldStyle())
            .padding(EdgeInsets(top: 20, leading: 30, bottom: 20, trailing: 30))

            Button(action: {
                let newResort = Resort(id: UUID(), name: self.resortName)
                self.resortStore.resorts.append(newResort)
                self.presentationMode.wrappedValue.dismiss()

                if self.horizontalSizeClass == .regular {
                    self.selectedResortId = newResort.id
                }

            }) {
                Text("Save Trip")
            }
            .padding(.trailing, 20)

        }
    }
}

struct WelcomeView: View {
    var body: some View {
        Text("Welcome View")
    }
}

struct Resort {
    var id: UUID
    var name: String
}
  1. We need to keep track of the selectedResortId
  2. We create an invisible NavigationLink that will programmatically navigate to the selected resort
  3. We make our list row a Button, so that the user can select a resort by tapping on the row

I started writing a series of articles about navigation in SwiftUI List view, there are a lot of points to consider while implementing programmatic navigation. Here is the one that describes this solution that I'm suggesting: SwiftUI Navigation in List View: Programmatic Navigation. This solution works at the moment on iOS 13.4.1. SwiftUI is changing rapidly, so we have to keep on checking.

And here is my previous article that explains why a more simple solution of adding a NavigationLink to each List row has some problems at the moment SwiftUI Navigation in List View: Exploring Available Options

Let me know if you have questions, I'd be happy to help where I can.

Sign up to request clarification or add additional context in comments.

4 Comments

I'll have to study this a bit. At first glance, it does not work. ALL of the action happens in the left pane - both master list and detail view. The split window never populates. When adding a new Resort it is indeed selected, but again it is the detail view that is placed in the left pane where the list should be.
@user2698617 could you share your code? something that builds? the split view can indeed behave weird sometimes, it can happen if something is preselected before the view appears for example
Natalia, I believe your code does indeed work. I had setup an AppRoot directory to include the Welcome screen for iPads. Once I pointed the SceneDelegate directly to ContentView I believe it now works. If you care to look, I added the code to GitHub under branch Natalia. github.com/JohnGrauel/SimpleMasterDetailNoTabsSwiftUI
Links are broken.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.