2022-12-25

A coredata computed property used to sort an array of object is not updated. Working when I go from view1 to view2, but not from view 3 to view2

I am still quite new at SwiftUI and I am having trouble to understand why the array that I have created in my model “GameSession”, which is sorted according to a computed variable defined in my second model “GamePlayer”, is not “always” correctly updated.

I have simplified my code to make it more readable, in order to maximize the chances of someone helping me!

Description of my code:

In my App, I have 2 entities

1.     GameSession :which has Attributes "date", "place", "nbplayer" + a relationship called "players" with " GamePlayer "

2.     GamePlayer: which has Attributes "name","scorelevel1","scorelevel2","scorelevel3" + a relationship with "GameSession"

the relationship is "one to many": One GameSession  can have many GamePlayer

 

And basically, I have 3 views:

View 1: Just one navigation link which brings you to View2

View2: Display a list of “Game” played (on which you can click to go to “view3”). For each one, I want to be able to display the date of the game, and the names of the players who played the game, sorted according to a variable “total_score”

View3: You can visualize the game on which you clicked in View2. And you see the name of the players who played the game, and you can modify the score of each player for each one of the 3 levels. And a variable “total_score” sums for each player the score of level 1 to 3

 My problem:

My issue, is that when I go from View3 to View2, the list of the players won’t be correctly sorted according to the “total_score” variable.

But If I go from View1 to View2, the list of the player is correctly sorted… 

I don’t understand what I am doing wrong. 

Here is my code:

My Model for GameSession is like that:. You will notice that I have created in my model a variable which is an array of “GamePlayer” sorted according to the computed variable “total_score” of the GamePlayer entity

import Foundation
import CoreData
 
 
extension GameSession {
 
    @nonobjc public class func fetchRequest() -> NSFetchRequest<GameSession> {
        return NSFetchRequest<GameSession>(entityName: "GameSession")
    }
 
    @NSManaged public var date: Date?
    @NSManaged public var nbplayer: Int32
    @NSManaged public var place: String?
    @NSManaged public var players: NSSet?
    @NSManaged public var photo: NSData?
 
    // pour gerer le fait que "date" ne doit pas etre vide
    public var wrappedDate: Date {
        date ?? Date()
    }
    public var wrappedNbplayer: Int32 {
        nbplayer
    }
    
    // pour gerer le fait que "lieu" ne doit pas etre vide
    public var wrappedPlace: String {
        place ?? "Endroit inconnu"    }
 
    
    // pour gerer le fait que "photo" ne doit pas etre vide
    public var wrappedPhoto: NSData {
        photo ?? NSData()
    }
    
    }
    //
    public var playersArrayRang: [GamePlayer] {
        let playersSet = players as? Set< GamePlayer > ?? []
 
        // On retourne un array sorted en fonction du score total
        return playersSet.sorted {
            $0.totalscore < $1.totalscore
        }
    }

And my model for “GamePlayer” is like that:

extension GamePlayer {
 
    @nonobjc public class func fetchRequest() -> NSFetchRequest< GamePlayer > {
        return NSFetchRequest< GamePlayer >(entityName: " GamePlayer ")
    }
 
    @NSManaged public var name: String?
    @NSManaged public var scorelevel1: Int64
    @NSManaged public var scorelevel2: Int64
    @NSManaged public var scorelevel3: Int64
    @NSManaged public var sessiongame: GameSession?
    
    public var wrappedName: String {
        name ?? "player unknown"
    }
 
    public var total_score: Int64 {
        scorelevel1 + scorelevel2 + scorelevel3
    }
 
}

View 1: GameUIView

struct GameUIView: View {
    var body: some View {
            NavigationLink(destination: GameListePartieUIView ()) {
                Text(" Mes parties ")
            }
    }
}

View 2: GameListePartieUIView

import SwiftUI
import Foundation
 
struct GameListePartieUIView: View {
    @Environment(\.managedObjectContext) var moc
    @FetchRequest(entity: GameSession.entity(), sortDescriptors: [
        NSSortDescriptor(keyPath: \GameSession.date, ascending: false)
    ]) var gamesessions: FetchedResults<GameSession>
    
    @State var showingAddPartie = false
    
    var body: some View {
        
//        VStack {
            List {
                ForEach(gamesessions, id: \.date) { session in
                    
                    HStack{
 
                        NavigationLink (destination: DetailPartieSelecUIView2(session: session)){
                            HStack{
                                Text(" \(session.wrappedDate, formatter: itemFormatter) ")
                                    .frame(width: 120, height: 50, alignment: .top)
                                    .font(.system(size: 12, weight: .light, design: .serif))
 
                                VStack(alignment: .leading){
                                    ForEach(Array(session.playersArrayRang.enumerated()), id: \.offset) { index, player in
                                        if index == 0 {
                                            Text("\(index+1) - \(player.wrappedName)")
                                                .frame(alignment: .leading)
                                                .lineLimit(1)
                                                .font(.system(size: 12, weight: .light, design: .serif))
//                                                .foregroundColor(Color(red: 0.246, green: 0.605, blue: 0))
                                                .foregroundColor(Color("Colorjb1"))
                                                .bold()
                                        } else {
                                            Text("\(index+1) - \(player.wrappedName)")
                                                .frame(alignment: .leading)
                                                .lineLimit(1)
                                                .font(.system(size: 12, weight: .light, design: .serif))
                                        }
 
                                    }
                                }
                                
                            }
                            
                        }
                        
                        
                        
                    }
                    
                }
                .onDelete(perform: deleteSessions)
                .padding(1)
            }
 
            .navigationBarItems(trailing: Button("Ajouter") {
                self.showingAddPartie.toggle()
            })
            .navigationBarTitle("Parties", displayMode: .inline)
            .sheet(isPresented: $showingAddPartie) {
                AddPartieRamiUIView().environment(\.managedObjectContext, self.moc)
            }
            .frame( maxWidth: .infinity)
            .edgesIgnoringSafeArea(.all)
            .listStyle(GroupedListStyle()) // or PlainListStyle()
            .padding(1)
    
    }
    
    
    // --------
    private func deleteSessions(at offsets: IndexSet) {
        for index in offsets {
            let session = ramisessions[index]
            moc.delete(session)
        }
        
        do {
            try moc.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
    }
    
}

View 3: DetailPartieSelecUIView2

struct DetailPartieSelecUIView2: View {
    @Environment(\.managedObjectContext) var moc
    
    @FetchRequest
    private var players: FetchedResults<GamePlayer>
    
    private var session_actuelle: GameSession
    
    init(session: GameSession) {
        let predicate = NSPredicate(format: "sessionramy = %@", session)
        let sortDescriptors = [SortDescriptor(\GamePlayer.name)] // need something to sort by.
        _players = FetchRequest(sortDescriptors: sortDescriptors, predicate: predicate)
        session_actuelle = session
    }
    
    let formatter: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .decimal
        return formatter
    }()
 
    
    var body: some View {
        
        VStack {
            ScrollView(.vertical){
 
                Text("\(session_actuelle.wrappedLieu) - \(session_actuelle.wrappedDate, formatter: itemFormatter)")
                
                ScrollView([.horizontal, .vertical]) {
                    HStack(alignment:.center){
                        // 1er element de mon HStack
                        VStack {
                                Text(" ")
                                Divider()
                                Text("Level 1")
                                Text("Level 2")
                                Text("Level 3")
                                Divider()
                                Text("Total")
                            }
                        // 2eme element de mon HStack
                        ForEach(players, id: \.wrappedName) { player in
                            PlayerView(player: player)
                        }
                    } // HStack
                } //scrollView
 
                // bouton pour sauvegarder les scores de la partie
                Button("Save scores") {
                        do {
                            try self.moc.save()
                        } catch {
                            print("Whoops! \(error.localizedDescription)")
                        }
                    }
 
            }
        }
    }
}
 
struct PlayerView: View {
    @ObservedObject var player:GamePlayer
 
    @Environment(\.managedObjectContext) var moc
    
    @State private var numberFormatter: NumberFormatter = {
        var nf = NumberFormatter()
        nf.numberStyle = .decimal
        return nf
    }()
    
    var body: some View {
        
        VStack {
            
 
                Text(player.wrappedName)
                Divider()
                TextField("Score", value: $player.scorelevel1, formatter:NumberFormatter())
                TextField("Score", value: $player.scorelevel2, formatter:NumberFormatter())
                TextField("Score", value: $player.scorelevel3, formatter:NumberFormatter())
                Divider()
                Text(String(player.scoretotal))
                
            }
    }
}
 
extension Binding {
    func toUnwrapped<T>(defaultValue: T) -> Binding<T> where Value == Optional<T>  {
        Binding<T>(get: { self.wrappedValue ?? defaultValue }, set: { self.wrappedValue = $0 })
    }
}
 

When I am in my “View3” and I change values in the TextField (of PlayerView), player.scoretotal is updated accordingly.

Then I click on my button "Save scores", in order to save my “moc” (Core Data)

But when I click on “back” (on the top left corner of the view) and that I go back to the previous view (View2), the right part of my list, where I am supposed to sort players name according to their total_score, is not updated…

What is weird, is that, if I go “back” to my “View1”, and that I click on the navigation link which open my “View2” (DetailPartieSelecUIView2), Then the names of the players are sorted correctly…

Thanks a lot for reading my long post. Hopefully somebody can help me. Best regards!

I tried everything...



No comments:

Post a Comment