changeset 15:f1967f8cc67b

first iteration of core data
author Dennis C. M. <dennis@denniscm.com>
date Wed, 19 Oct 2022 10:04:17 +0200
parents 136928bae534
children 1011e56b7832
files GeoQuiz.xcodeproj/project.pbxproj GeoQuiz/Components/GameAlertsModifier.swift GeoQuiz/GeoQuiz.xcdatamodeld/GeoQuiz.xcdatamodel/contents GeoQuiz/GeoQuizApp.swift GeoQuiz/GuessTheCapitalView.swift GeoQuiz/GuessTheCountryView.swift GeoQuiz/GuessTheFlagView.swift GeoQuiz/GuessThePopulationView.swift GeoQuiz/Logic/CityGameClass.swift GeoQuiz/Logic/CountryGameClass.swift GeoQuiz/Logic/DataController.swift GeoQuiz/Logic/GameProtocol+Extension.swift GeoQuiz/Logic/GameTypeEnum.swift GeoQuiz/Logic/PlayedGame+CoreDataClass.swift GeoQuiz/Logic/PlayedGame+CoreDataProperties.swift GeoQuiz/ProfileModalView.swift
diffstat 16 files changed, 157 insertions(+), 35 deletions(-) [+]
line wrap: on
line diff
--- a/GeoQuiz.xcodeproj/project.pbxproj	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz.xcodeproj/project.pbxproj	Wed Oct 19 10:04:17 2022 +0200
@@ -45,6 +45,9 @@
 		95C6456E28FE8C04000CD570 /* UserImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6456D28FE8C04000CD570 /* UserImageHelper.swift */; };
 		95C6457228FFC4DC000CD570 /* ProfileEditModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */; };
 		95C6457428FFC8E0000CD570 /* DataController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457328FFC8E0000CD570 /* DataController.swift */; };
+		95C6457728FFC934000CD570 /* GeoQuiz.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 95C6457528FFC934000CD570 /* GeoQuiz.xcdatamodeld */; };
+		95C6459A28FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */; };
+		95C6459B28FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */; };
 		95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */; };
 		95CC404928F98503001F74E1 /* GameTypeEnum.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95CC404828F98503001F74E1 /* GameTypeEnum.swift */; };
 		95FA409A28D9876B00129B60 /* GuessTheFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 95FA409928D9876B00129B60 /* GuessTheFlagView.swift */; };
@@ -90,6 +93,9 @@
 		95C6456D28FE8C04000CD570 /* UserImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserImageHelper.swift; sourceTree = "<group>"; };
 		95C6457128FFC4DC000CD570 /* ProfileEditModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileEditModalView.swift; sourceTree = "<group>"; };
 		95C6457328FFC8E0000CD570 /* DataController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataController.swift; sourceTree = "<group>"; };
+		95C6457628FFC934000CD570 /* GeoQuiz.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = GeoQuiz.xcdatamodel; sourceTree = "<group>"; };
+		95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataClass.swift"; sourceTree = "<group>"; };
+		95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PlayedGame+CoreDataProperties.swift"; sourceTree = "<group>"; };
 		95CA294F28F6BB4500CE0B7A /* ActivityAlertHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityAlertHelper.swift; sourceTree = "<group>"; };
 		95CC404828F98503001F74E1 /* GameTypeEnum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GameTypeEnum.swift; sourceTree = "<group>"; };
 		95E6188428DDDB5C003359ED /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
@@ -125,6 +131,8 @@
 				95CC404828F98503001F74E1 /* GameTypeEnum.swift */,
 				95AE8D5628C8750E0067F219 /* LoadFunc.swift */,
 				95C6457328FFC8E0000CD570 /* DataController.swift */,
+				95C6459828FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift */,
+				95C6459928FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift */,
 			);
 			path = Logic;
 			sourceTree = "<group>";
@@ -170,6 +178,7 @@
 			children = (
 				95E6188428DDDB5C003359ED /* Info.plist */,
 				9539829628C51EDF00B70973 /* Assets.xcassets */,
+				95C6457528FFC934000CD570 /* GeoQuiz.xcdatamodeld */,
 				9539829228C51EDE00B70973 /* GeoQuizApp.swift */,
 				95C4315528C64A8C00212131 /* ContentView.swift */,
 				95FA409928D9876B00129B60 /* GuessTheFlagView.swift */,
@@ -297,6 +306,7 @@
 			files = (
 				955A65A928D7815E00CEEC6D /* HapticsClass.swift in Sources */,
 				95BC392D28EC42570049AB49 /* CityMapHelper.swift in Sources */,
+				95C6459A28FFE5A3000CD570 /* PlayedGame+CoreDataClass.swift in Sources */,
 				952E41E928DC521200198643 /* GameAlertsModifier.swift in Sources */,
 				95197EFD28F339AE00FE67E9 /* StoreKitRCClass.swift in Sources */,
 				9509A8E228E5A3D700CFCDBA /* GuessThePopulationView.swift in Sources */,
@@ -320,9 +330,11 @@
 				95CA295028F6BB4500CE0B7A /* ActivityAlertHelper.swift in Sources */,
 				955A658128D703EB00CEEC6D /* GameToolbarHelper.swift in Sources */,
 				95AE8D5728C8750E0067F219 /* LoadFunc.swift in Sources */,
+				95C6457728FFC934000CD570 /* GeoQuiz.xcdatamodeld in Sources */,
 				9590359528E098FF00B24560 /* ProfileModalView.swift in Sources */,
 				955950BB28F15FF2001BDEE8 /* FormatterExtension.swift in Sources */,
 				95C6456C28FE87E4000CD570 /* UserDataModel.swift in Sources */,
+				95C6459B28FFE5A3000CD570 /* PlayedGame+CoreDataProperties.swift in Sources */,
 				951D197328D485E000671FAD /* ColorExtension.swift in Sources */,
 				95C6457228FFC4DC000CD570 /* ProfileEditModalView.swift in Sources */,
 				95C430F928D0A8E500480D23 /* GradientExtension.swift in Sources */,
@@ -558,6 +570,19 @@
 			productName = RevenueCat;
 		};
 /* End XCSwiftPackageProductDependency section */
+
+/* Begin XCVersionGroup section */
+		95C6457528FFC934000CD570 /* GeoQuiz.xcdatamodeld */ = {
+			isa = XCVersionGroup;
+			children = (
+				95C6457628FFC934000CD570 /* GeoQuiz.xcdatamodel */,
+			);
+			currentVersion = 95C6457628FFC934000CD570 /* GeoQuiz.xcdatamodel */;
+			path = GeoQuiz.xcdatamodeld;
+			sourceTree = "<group>";
+			versionGroupType = wrapper.xcdatamodel;
+		};
+/* End XCVersionGroup section */
 	};
 	rootObject = 9539828728C51EDE00B70973 /* Project object */;
 }
--- a/GeoQuiz/Components/GameAlertsModifier.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/Components/GameAlertsModifier.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -6,9 +6,14 @@
 //
 
 import SwiftUI
+import CoreData
 
 struct GameAlertsModifier<T: Game>: ViewModifier {
     @ObservedObject var game: T
+    
+    var gameType: GameType
+    var moc: NSManagedObjectContext
+    
     @Environment(\.dismiss) var dismiss
     
     func body(content: Content) -> some View {
@@ -22,25 +27,12 @@
             } message: {
                 Text(game.alertMessage)
             }
-        
-            .alert(game.alertTitle, isPresented: $game.showingGameOverAlert) {
-                Button("Try again") {
-                    game.reset {
-                        game.selector()
-                    }
-                }
-                Button("Exit", role: .cancel) { dismiss()}
-            } message: {
-                Text(game.alertMessage)
-            }
             
             .alert(game.alertTitle, isPresented: $game.showingEndGameAlert) {
-                Button("Play again") {
-                    game.reset() {
-                        game.selector()
-                    }
+                Button("Exit", role: .cancel) {
+                    game.save(gameType, with: moc)
+                    dismiss()
                 }
-                Button("Exit", role: .cancel) { dismiss() }
             } message: {
                 Text(game.alertMessage)
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/GeoQuiz.xcdatamodeld/GeoQuiz.xcdatamodel/contents	Wed Oct 19 10:04:17 2022 +0200
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="21279" systemVersion="21G115" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
+    <entity name="PlayedGame" representedClassName="PlayedGame" syncable="YES">
+        <attribute name="correctAnswers" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]"/>
+        <attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
+        <attribute name="id" attributeType="UUID" usesScalarValueType="NO"/>
+        <attribute name="score" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="type" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
+        <attribute name="wrongAnswers" optional="YES" attributeType="Transformable" valueTransformerName="NSSecureUnarchiveFromData" customClassName="[String]"/>
+        <uniquenessConstraints>
+            <uniquenessConstraint>
+                <constraint value="id"/>
+            </uniquenessConstraint>
+        </uniquenessConstraints>
+    </entity>
+</model>
\ No newline at end of file
--- a/GeoQuiz/GeoQuizApp.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/GeoQuizApp.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -10,6 +10,7 @@
 
 @main
 struct GeoQuizApp: App {
+    @StateObject private var dataController = DataController()
     
     init() {
         Purchases.configure(withAPIKey: "appl_BymTxroeoaWiXAraaFjcPlHlqbf")
@@ -18,6 +19,7 @@
     var body: some Scene {
         WindowGroup {
             ContentView()
+                .environment(\.managedObjectContext, dataController.container.viewContext)
         }
     }
 }
--- a/GeoQuiz/GuessTheCapitalView.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/GuessTheCapitalView.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -10,6 +10,8 @@
 struct GuessTheCapitalView: View {
     @StateObject var game = CountryGame()
     
+    @Environment(\.managedObjectContext) var moc
+    
     var body: some View {
         ZStack {
             LinearGradient(gradient: .secondary, startPoint: .top, endPoint: .bottom)
@@ -62,7 +64,7 @@
             }
         }
         .navigationBarHidden(true)
-        .modifier(GameAlertsModifier(game: game))
+        .modifier(GameAlertsModifier(game: game, gameType: .guessTheCapital, moc: moc))
     }
 }
 
--- a/GeoQuiz/GuessTheCountryView.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/GuessTheCountryView.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -10,6 +10,8 @@
 struct GuessTheCountryView: View {
     @StateObject var game = CityGame()
     
+    @Environment(\.managedObjectContext) var moc
+    
     var body: some View {
         ZStack {
             LinearGradient(gradient: .tertiary, startPoint: .top, endPoint: .bottom)
@@ -60,7 +62,7 @@
             }
         }
         .navigationBarHidden(true)
-        .modifier(GameAlertsModifier(game: game))
+        .modifier(GameAlertsModifier(game: game, gameType: .guessTheCountry, moc: moc))
     }
 }
 
--- a/GeoQuiz/GuessTheFlagView.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/GuessTheFlagView.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -10,6 +10,8 @@
 struct GuessTheFlagView: View {
     @StateObject var game = CountryGame()
     
+    @Environment(\.managedObjectContext) var moc
+    
     var body: some View {
         ZStack {
             LinearGradient(gradient: .main, startPoint: .top, endPoint: .bottom)
@@ -62,7 +64,7 @@
             }
         }
         .navigationBarHidden(true)
-        .modifier(GameAlertsModifier(game: game))
+        .modifier(GameAlertsModifier(game: game, gameType: .guessTheFlag, moc: moc))
     }
 }
 
--- a/GeoQuiz/GuessThePopulationView.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/GuessThePopulationView.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -10,6 +10,8 @@
 struct GuessThePopulationView: View {
     @StateObject var game = CountryGame()
     
+    @Environment(\.managedObjectContext) var moc
+    
     var body: some View {
         ZStack {
             LinearGradient(gradient: .quaternary, startPoint: .top, endPoint: .bottom)
@@ -63,7 +65,7 @@
             }
         }
         .navigationBarHidden(true)
-        .modifier(GameAlertsModifier(game: game))
+        .modifier(GameAlertsModifier(game: game, gameType: .guessThePopulation, moc: moc))
     }
 }
 
--- a/GeoQuiz/Logic/CityGameClass.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/Logic/CityGameClass.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -32,7 +32,6 @@
     // Alerts
     @Published var alertTitle = String()
     @Published var alertMessage = String()
-    @Published var showingGameOverAlert = false
     @Published var showingEndGameAlert = false
     @Published var showingWrongAnswerAlert = false
     @Published var showingExitGameAlert = false
--- a/GeoQuiz/Logic/CountryGameClass.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/Logic/CountryGameClass.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -32,7 +32,6 @@
     // Alerts
     @Published var alertTitle = String()
     @Published var alertMessage = String()
-    @Published var showingGameOverAlert = false
     @Published var showingEndGameAlert = false
     @Published var showingWrongAnswerAlert = false
     @Published var showingExitGameAlert = false
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Logic/DataController.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -0,0 +1,21 @@
+//
+//  DataController.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 19/10/22.
+//
+
+import CoreData
+import Foundation
+
+class DataController: ObservableObject {
+    let container = NSPersistentContainer(name: "GeoQuiz")
+    
+    init() {
+        container.loadPersistentStores { description, error in
+            if let error = error {
+                print("Core Data failed to load: \(error.localizedDescription)")
+            }
+        }
+    }
+}
--- a/GeoQuiz/Logic/GameProtocol+Extension.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/Logic/GameProtocol+Extension.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -8,6 +8,7 @@
 import Foundation
 import SwiftUI
 import AVFAudio
+import CoreData
 
 protocol Game: ObservableObject {
     
@@ -29,7 +30,6 @@
     // Alerts
     var alertTitle: String { get set }
     var alertMessage: String { get set }
-    var showingGameOverAlert: Bool { get set }
     var showingEndGameAlert: Bool { get set }
     var showingWrongAnswerAlert: Bool { get set }
     var showingExitGameAlert: Bool { get set }
@@ -91,7 +91,7 @@
             if userLives == 0 {
                 alertTitle = "🤕 Game over 🤕"
                 alertMessage = "Get up and try again."
-                showingGameOverAlert = true
+                showingEndGameAlert = true
             } else {
                 alertTitle = "🔴 Wrong 🔴"
                 alertMessage = "You have \(userLives) lives left."
@@ -107,14 +107,20 @@
         }
     }
     
-    func reset(selector: () -> Void) {
-        dataAsked = [String: T]()
-        userScore = 0
-        userLives = 3
-        correctAnswers = [String: T]()
-        wrongAnswers = [String: T]()
-        askQuestion {
-            selector()
+    func save(_ gameType: GameType, with moc: NSManagedObjectContext) {
+        let playedGame = PlayedGame(context: moc)
+
+        playedGame.id = UUID()
+        playedGame.type = gameType
+        playedGame.date = Date()
+        playedGame.score = Int32(userScore)
+        playedGame.correctAnswers = Array(correctAnswers.keys)
+        playedGame.wrongAnswers = Array(wrongAnswers.keys)
+        
+        do {
+            try moc.save()
+        } catch {
+            print("Couldn't save object to CoreData: \(error)")
         }
     }
     
--- a/GeoQuiz/Logic/GameTypeEnum.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/Logic/GameTypeEnum.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -7,6 +7,7 @@
 
 import Foundation
 
-enum GameType {
+@objc
+public enum GameType: Int16 {
     case guessTheFlag, guessTheCapital, guessTheCountry, guessThePopulation
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Logic/PlayedGame+CoreDataClass.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -0,0 +1,15 @@
+//
+//  PlayedGame+CoreDataClass.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 19/10/22.
+//
+//
+
+import Foundation
+import CoreData
+
+@objc(PlayedGame)
+public class PlayedGame: NSManagedObject {
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/GeoQuiz/Logic/PlayedGame+CoreDataProperties.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -0,0 +1,30 @@
+//
+//  PlayedGame+CoreDataProperties.swift
+//  GeoQuiz
+//
+//  Created by Dennis Concepción Martín on 19/10/22.
+//
+//
+
+import Foundation
+import CoreData
+
+
+extension PlayedGame {
+
+    @nonobjc public class func fetchRequest() -> NSFetchRequest<PlayedGame> {
+        return NSFetchRequest<PlayedGame>(entityName: "PlayedGame")
+    }
+
+    @NSManaged public var id: UUID
+    @NSManaged public var type: GameType
+    @NSManaged public var score: Int32
+    @NSManaged public var date: Date
+    @NSManaged public var correctAnswers: [String]?
+    @NSManaged public var wrongAnswers: [String]?
+
+}
+
+extension PlayedGame : Identifiable {
+
+}
--- a/GeoQuiz/ProfileModalView.swift	Wed Oct 19 07:56:33 2022 +0200
+++ b/GeoQuiz/ProfileModalView.swift	Wed Oct 19 10:04:17 2022 +0200
@@ -13,6 +13,11 @@
     @ObservedObject var storeKitRC: StoreKitRC
     
     @Environment(\.dismiss) var dismiss
+    @Environment(\.managedObjectContext) var moc
+    
+    @FetchRequest(sortDescriptors: [
+        SortDescriptor(\.date),
+    ]) var playedGames: FetchedResults<PlayedGame>
     
     @State private var showingEditModalView = false
     
@@ -63,8 +68,11 @@
                 }
                 
                 Section {
-                    ForEach(1..<10) { _ in
-                        Text("Hello")
+                    ForEach(playedGames) { playedGame in
+                        HStack {
+                            Text("\(playedGame.id)")
+                            Text("\(playedGame.date)")
+                        }
                     }
                 } header: {
                     Text("Recent games")