Home > Enterprise >  How to sort a list in a detail view of a one to many relationship with Core Data and SwiftUI?
How to sort a list in a detail view of a one to many relationship with Core Data and SwiftUI?

Time:11-09

Meditations has a one to many relationship with stages and I have sorted the Meditations based on an order property, which I change by moving them around when I tap edit and this works. Now I want to do the same with the stages in the detail view of the meditations.

I tried to do a new FetchRequest for the DetailView so that I could use the same method as with meditations, but I couldn't make it specific for the particular meditation (and this might not be the best approach if a new FetchRequest is avoidable I think).

I also tried to sort the list items with .sorted, but then it didn't update right away when the order was changed in edit mode and you had to go back and forth to see the changes.

Is there a way to make one of these approaches work? I am also open to other solutions of course.

This is the view for the meditations:

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
    
    @FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Meditation.order, ascending: true)])
    private var meditations:FetchedResults<Meditation>
    
    @State private var add = false
    
    var body: some View {
        

        NavigationView {
            List {
                         ForEach(meditations){meditation in
                             NavigationLink(destination: {
                                 DetailView(meditation: meditation)
                             }, label: {
                                 Text(meditation.title ?? "")
                                 Text("\(meditation.order)")
                             })
                         }
                         .onDelete(perform: deleteMeditation)
                         .onMove(perform: moveItem)
                     }
                     .navigationTitle("Meditations")
                     .sheet(isPresented: $add){
                         AddView()
                     }
                     .toolbar {
                         ToolbarItem(placement:.navigationBarTrailing){
                             EditButton()
                         }
                         
                                     ToolbarItem(placement:.navigationBarTrailing){
                                         Button(action: {
                                             add.toggle()
                                         }, label: {
                                             Label("Add Meditation",systemImage: "plus")
                                         })
                                         
                                     }
                                 }
            
        }
    }
    
    private func deleteMeditation(at offset:IndexSet){
        withAnimation {
            for index in offset{
                let meditationToDelete = meditations[index]
                do{
                    viewContext.delete(meditationToDelete)
                    try viewContext.save()
                }catch{
                    print("Error while deleting Meditation \(error.localizedDescription)")
                }
            }
        }
        }
    
    
    private func moveItem(at sets:IndexSet,destination:Int) {
        let itemToMove = sets.first!
        print (itemToMove)
        print (destination)
        if itemToMove < destination {
            var startIndex = itemToMove   1
            let endIndex = destination - 1
            print (startIndex)
            print (endIndex)
            var startOrder = meditations[itemToMove].order
            while startIndex <= endIndex {
                meditations[startIndex].order = startOrder
                startOrder = startOrder   1
                startIndex = startIndex   1
            }
            meditations[itemToMove].order = startOrder
        } else if destination < itemToMove {
            var startIndex = destination
            let endIndex = itemToMove - 1
            var startOrder = meditations[destination].order   1
            let newOrder = meditations[destination].order
            while startIndex <= endIndex {
                meditations[startIndex].order = startOrder
                startOrder = startOrder   1
                startIndex = startIndex   1
            }
            meditations[itemToMove].order = newOrder
        }
        do {
            try viewContext.save()
        }
        catch {
            print(error.localizedDescription)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

This is the detailview where the stages are shown:


import SwiftUI

struct DetailView: View {
    let meditation : Meditation
    
    @Environment(\.managedObjectContext) var viewContext
    @Environment(\.dismiss) var dismiss
    
    @State private var addStage = false
    @State private var showingDeleteAlert = false
    @State private var isPresented = false

    var body: some View {
        VStack {
            Text(meditation.wrappedDetails)
            List{
                ForEach(meditation.meditationStage){meditationStage in
                    MeditationStageDetailCell(meditationStage: meditationStage)
                }
                .onMove(perform: moveStage)
                .onDelete(perform: deleteStage)
            }
            Spacer()
            Button ("Begin"){
                isPresented.toggle()
            }
            .fullScreenCover(isPresented: $isPresented) {
                MeditatingView(meditation: meditation)
            }
            Spacer()
        }
        .navigationTitle(meditation.wrappedTitle)
        .navigationBarTitleDisplayMode(.inline)
        .alert("Delete Meditation?", isPresented: $showingDeleteAlert) {
            Button ("Delete", role: .destructive, action: deleteMeditation)
            Button ("Cancel", role: .cancel) {}
        } message: {
            Text("This action can't be undone")
        }
        .toolbar{
            ToolbarItem(placement:.navigationBarTrailing){
                 Button {
                    showingDeleteAlert = true
                } label: {
                    Label ("Delete this Stage", systemImage: "trash")
                }
            }
            ToolbarItem(placement:.navigationBarTrailing){
                EditButton()
            }
            ToolbarItem(placement:.navigationBarTrailing){
                Button(action: {
                    addStage.toggle()
                }, label: {
                    Label("Add Stage",systemImage: "plus")
                })
            }
        }
        .sheet(isPresented: $addStage){
            AddMeditationStageView(meditation: meditation)
        }
    }
    
    private func deleteMeditation() {
        viewContext.delete(meditation)
        try? viewContext.save()
        dismiss()
    }
    
    private func deleteStage(at offset:IndexSet){
            for index in offset{
                let stageToDelete = meditation.meditationStage[index]
                do{
                    viewContext.delete(stageToDelete)
                    meditation.stages -= 1
                    try viewContext.save()
                } catch {
                    print("Error while deleting Meditation \(error.localizedDescription)")
                }
            }
        }
    
    private func moveStage(at sets:IndexSet,destination:Int) {
        let itemToMove = sets.first!
        print (itemToMove)
        print (destination)
        if itemToMove < destination {
            var startIndex = itemToMove   1
            let endIndex = destination - 1
            print (startIndex)
            print (endIndex)
            var startOrder = meditation.meditationStage[itemToMove].order
            print(startOrder)
            while startIndex <= endIndex {
                meditation.meditationStage[startIndex].order = startOrder
                startOrder = startOrder   1
                startIndex = startIndex   1
            }
            meditation.meditationStage[itemToMove].order = startOrder
        } else if destination < itemToMove {
            var startIndex = destination
            let endIndex = itemToMove - 1
            var startOrder = meditation.meditationStage[destination].order   1
            let newOrder = meditation.meditationStage[destination].order
            while startIndex <= endIndex {
                meditation.meditationStage[startIndex].order = startOrder
                startOrder = startOrder   1
                startIndex = startIndex   1
            }
            meditation.meditationStage[itemToMove].order = newOrder
        }
        do {
            try viewContext.save()
        }
        catch {
            print(error.localizedDescription)
        }
    }
}


CodePudding user response:

    @FetchRequest
    private var stages: FetchedResults<MeditationStage>

    init(meditation: Meditation) {
        let sortDescriptors = [SortDescriptor(\MeditationStage.timestamp]
        _stages = FetchRequest(sortDescriptors: sortDescriptors, predicate: NSPredicate(format: "meditation = %@", meditation), animation: .default)
    }
  • Related