How to handle adding and removing items from NavigationSplitView

Hello, I'm new to app development and have a question that I have been searching all over the place to find an answer to. I have followed Apple's own guides and don't know how to move around the strange issue I am experiencing.

I have a project using NavigationSplitView and have the code setup so that it navigates through an array of items. The two issues I am experiencing that I hope to find some help with are:

  1. I would like to be able to have my add button, instead of presenting a sheet, just navigate to a new item within the NavigationSplitView. This would be more natural for the type of data application I have created. At this time I create the item with a .sheet modifier and then the item is created and added to the array. However, I then have to tap on the newly created item to get it to be selected. Is this how SwiftUI should be working?
  2. Whenever an item is deleted from the list, and it was the last item selected or the last item in the array, the NavigationSplitView will continue showing that remaining item. The only way to get it to show an empty content screen is to force close the application and then reopen it.

Since this is my first time posting, I'm not sure what information would be helpful, but would be happy to provide anything which could be of assistance. :-)

Hi @erfinb ,

  1. I would like to be able to have my add button, instead of presenting a sheet, just navigate to a new item within the NavigationSplitView.

After appending the object in your add button, programmatically set the selection variable to be the last item in the array. (example below)

.

  1. Whenever an item is deleted from the list, and it was the last item selected or the last item in the array, the NavigationSplitView will continue showing that remaining item.

What you need to do here is use the onDelete modifier. You first need to check for the first item that you're deleting and then check if that item is equal to the current selection. If so, set the selection to nil. Then, remove the item from the array.

Here is a full code snippet showing both:

import SwiftUI

struct ContentView: View {
    @State private var items: [Int] = [1, 2, 3, 4, 5]
    @State private var selection: Int?
    
    var body: some View {
        NavigationSplitView {
            List(selection: $selection) {
                ForEach($items, id: \.self) { $item in
                    Text(item.description)
                }
                .onDelete { itemsToDelete in
                    if let itemToDelete = itemsToDelete.first {
                        if items[itemToDelete] == selection {
                            selection = nil
                        }
                    }
                    items.remove(atOffsets: deleted)
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("add") {
                        items.append(6)
                        selection = items.last
                    }
                }
            }
        } detail: {
            if let selection {
                Text(selection.description)
            }
            else {
                Text("none")
            }
            
        }
            
    }
}

@Vision Pro Engineer

How would you select the last item that is being stored with SwiftData?


@Environment(\.modelContext) private var modelContex
```t
    @Query private var applicants: [Applicant]
    @State private var newApplicant: Applicant?
    @State private var selection: Applicant?
///
if !applicants.isEmpty {
                List (selection: $selection) {
                    ForEach(applicants) { applicant in
                        NavigationLink {
                            ApplicantView(applicant: applicant)
///
private func addApplicant() {
        withAnimation {
            let newItem = Applicant()
            modelContext.insert(newItem)
            newApplicant = newItem
            selection = applicants.last
        }
    }
'''

I have it setup this way but the behavior has not changed. :-) Thank you again for your help!

@erfinb , when you click the add button in your navigation split view, it runs addApplicant(), right?

I'd expect a new applicant object to be created & added to the end of the applicants array. On the line before you do selection = applicants.last, can you do print(applicants.last) to ensure that it's actually there? Than we can check if the selection is being set to the correct item. I expect that value to be the Applicant you just created, so let me know if it's not.

If it is the Applicant that you just created, can you give me some more information about how the rest of your app is working? Can you show me how your NavigationSplitView is set up?

Best, Sydney

@Vision Pro Engineer you are all over helping me! :-) I'm sorry if I'm not the best at providing the requested information, still very new to programming. I added the print statement, and It prints "ApplicantProcessor.Applicant" whenever adding. I would be happy to show how the NavigationSplitView is setup. Here is that file.

struct FilteredApplicantListView: View {
    @State private var searchText = ""
    
    var body: some View {
        NavigationSplitView {
            ApplicantListView(applicantFilter: searchText)
                .searchable(text: $searchText, prompt: "Enter Name, Email, or Phone Number")
                .autocorrectionDisabled(true)
        } detail: { }
    }
}

Here is my app file

struct ApplicantProcessorApp: App {
    var sharedModelContainer: ModelContainer = {
        let schema = Schema([
            Applicant.self,
        ])
        let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

        do {
            return try ModelContainer(for: schema, configurations: [modelConfiguration])
        } catch {
            fatalError("Could not create ModelContainer: \(error)")
        }
    }()

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(sharedModelContainer)
    }
}

Here is what an Applicant looks like

import Foundation
import SwiftData

@Model
final class Applicant {
    var name = ""
    var email = ""
    var phoneNumber = ""
    var applicationDate = Date.now
    var expirationDate: Date {
        return Calendar.current.date(byAdding: .day, value: 90, to: applicationDate)!
    }
    
    private var _applicationStatus: ApplicationStatus?
    var applicationStatus: ApplicationStatus {
        get {
            return _applicationStatus ?? .defaultState
        }
        set {
            _applicationStatus = newValue
        }
    }
    
    private var _applicantType: ApplicantType?
    var applicantType: ApplicantType {
        get {
            return _applicantType ?? .standard
        }
        set {
            _applicantType = newValue
        }
    }
    
    private var _additionalFinancialRequirements: FinancialStatuses?
    var additionalFinancialRequirements: FinancialStatuses {
        get {
            return _additionalFinancialRequirements ?? .defaultState
        }
        set {
            _additionalFinancialRequirements = newValue
        }
    }
    
    var coSignerApplicants = ""
    
    var coSigners = ""
    
    var applicantAppFolioURL = ""
    
    var property = ""
    
    var notes = ""
    var requestedItems = ""
    
    var photoID = false
    var incomeDocumentation = false
    var employmentRequirement = false
    var studentDocumentation = false
    
    var ssnVerification = false
    var backgroundCheck = false
    var evictionCheck = false
    var stateCheck = false
    
    var noPastDueBalances = false
    var noGeneralCollections = false
    var dtiApprovable = false
    var creditScoreInRange = false
    var noCosignerOrDoubleDepositRequired = false
    var ableToScore = false
    var creditScore = 0
    
    var additionalApplicant = false
    var additionalApplicantNames = ""
    var additionalApplicantAppFolioLink = ""
    
    private var _petType: PetType?
    var petType: PetType {
        get {
            return _petType ?? .defaultState
        }
        set {
            _petType = newValue
        }
    }

    var petTypeCompatitable = false
    
    var petScreeningComplete = false
    
    var oldPetTypeCheck = false
    
    var declineReasons = ""
    
    init(name: String = "", email: String = "", phoneNumber: String = "", applicationDate: Date = Date.now, _applicationStatus: ApplicationStatus? = .defaultState, _applicantType: ApplicantType? = .standard, _additionalFinancialRequirements: FinancialStatuses? = .defaultState, coSignerApplicants: String = "", coSigners: String = "", applicantAppFolioURL: String = "", property: String = "", notes: String = "", requestedItems: String = "", photoID: Bool = false, incomeDocumentation: Bool = false, employmentRequirement: Bool = false, studentDocumentation: Bool = false, ssnVerification: Bool = false, backgroundCheck: Bool = false, evictionCheck: Bool = false, stateCheck: Bool = false, noPastDueBalances: Bool = false, noGeneralCollections: Bool = false, dtiApprovable: Bool = false, creditScoreInRange: Bool = false, noCosignerOrDoubleDepositRequired: Bool = false, ableToScore: Bool = false, creditScore: Int = 0, additionalApplicant: Bool = false, additionalApplicantNames: String = "", additionalApplicantAppFolioLink: String = "", _petType: PetType? = .defaultState, petTypeCompatitable: Bool = false, petScreeningComplete: Bool = false, oldPetTypeCheck: Bool = false, declineReasons: String = "") {
        self.name = name
        self.email = email
        self.phoneNumber = phoneNumber
        self.applicationDate = applicationDate
        self._applicationStatus = _applicationStatus
        self._applicantType = _applicantType
        self._additionalFinancialRequirements = _additionalFinancialRequirements
        self.coSignerApplicants = coSignerApplicants
        self.coSigners = coSigners
        self.applicantAppFolioURL = applicantAppFolioURL
        self.property = property
        self.notes = notes
        self.requestedItems = requestedItems
        self.photoID = photoID
        self.incomeDocumentation = incomeDocumentation
        self.employmentRequirement = employmentRequirement
        self.studentDocumentation = studentDocumentation
        self.ssnVerification = ssnVerification
        self.backgroundCheck = backgroundCheck
        self.evictionCheck = evictionCheck
        self.stateCheck = stateCheck
        self.noPastDueBalances = noPastDueBalances
        self.noGeneralCollections = noGeneralCollections
        self.dtiApprovable = dtiApprovable
        self.creditScoreInRange = creditScoreInRange
        self.noCosignerOrDoubleDepositRequired = noCosignerOrDoubleDepositRequired
        self.ableToScore = ableToScore
        self.creditScore = creditScore
        self.additionalApplicant = additionalApplicant
        self.additionalApplicantNames = additionalApplicantNames
        self.additionalApplicantAppFolioLink = additionalApplicantAppFolioLink
        self._petType = _petType
        self.petTypeCompatitable = petTypeCompatitable
        self.petScreeningComplete = petScreeningComplete
        self.oldPetTypeCheck = oldPetTypeCheck
        self.declineReasons = declineReasons
    }
    
    static let sampleData = [
        Applicant(name: "John Doe", email: "johndoe@icloud.com", phoneNumber: "123-456-8897", _applicationStatus: .defaultState)
    ]
}

Hi @erfinb ,

Everything you've shown looks fine, I think the best bet would be to put the project into a GitHub repo and share that link here so I can take a look for myself. If it's a really large project, try to create a new project that still reproduces the issue and upload that one.

Thanks,

Sydney

Hello @Vision Pro Engineer I would be happy to share a link to my GitHub, but have it setup as a private repo at the moment. Is there a way to share that link with you directly?

How to handle adding and removing items from NavigationSplitView
 
 
Q