SwiftUI: Trying to set my CoreData model for decoded JSON - swift

I have a small app that doesn't have persistence. I have JSON file of 100+ countries as part of the app:
Countries.json (only included 1 for brevity)
[
{
"id": 0,
"display_name": "Algeria",
"searchable_names": [
"algeria"
],
"image": "algeria",
"latitude": 36.753889,
"longitude": 3.058889,
"favorite": false,
"extended": false
}
]
and my country model looks like this:
Country.swift
import Foundation
struct Country: Codable, Identifiable {
var id: Int
let display_name: String
let searchable_names: [String]
let image: String
let latitude: Double
let longitude: Double
var favorite: Bool
var extended: Bool
}
and I'd decode the JSON like this:
#State var countries: [Country] = Bundle.main.decode("Countries.json")
Now, I am wanting to add a way to persist data so that if a user togglers favorite, that country will stay marked as a favorite on subsequent app visits.
I am having trouble in how to translate this over to use CoreData. I tried making a CoreData model called Country and added each property in but I feel like that is not correct. I basically need a way to add this decoded JSON to CoreData so that I could manipulate a single countries favorite value. I am bit lost at this point in how to accomplish this with a CoreData model.

Related

SWIFT "3 levels array"

I have a little problem with using arrays and declaring it in swift 5 on OSX:
I know an [[String]] is an array of arrays like [["team1", "2 goals"], ["team2", "0 goals"]]. Well it's easy for my, but let me explain my case:
I have an array of "Systems", which is like -> ["nes", "Xbox" , "ps2"]
every "System" have "Games" and every "game" in "Games" have an array of properties like "name", "thumbnail", "video", etc...
I need to create an array like ->
["xbox" , [["game1",array of properties],["game2", array of properties]]]
I tried to define an [[[String]]]
var allMyGames = [[[String]]]()
but I don´t know how to myGames.append this...
I have an array named myGames = [[String]]() which contains data like ["game1", "property1", "property2" ,"property3"] ["game2", "property1", "property2" ,"property3"]
and another array with the systems mySystems = [String]() my contains data like ["nes", "xbox", "ps2"]
Any idea?
thanks in advance
You would be much better modelling your problem using structs. Here is a start for you to show how you might start modelling your problem.
OK, let's start by creating a game with a name, thumbnail image and video.
struct Game {
let name: String
let thumbnailURL: URL
let videoURL: URL
}
Now we can create a system...
struct System {
let name: String
let games: [Game]
}
And now you can create your array of systems...
let systems = [
System(
name: "Xbox",
games: [
Game(name: "Halo", thumbnailURL: ... videoURL: ...),
]
),
System(
name: "NES",
games: [
Game(name: "Super Mario Bros", thumbnailURL: ... videoURL: ...),
]
)
]
As you can see you end up with a much easier way to model the different systems and games etc...

Vapor 4: how to include siblings including extra properties from the pivot table?

I am struggling a lot with how to return a model that contains a many-to-many relationship via a pivot table that contains extra fields. Basically, I want to return the full pivot table with the extra fields, but I can't figure how to do this.
Let's consider the following 3 models: Course, User, and the pivot between them, a Student. The Student model contains the extra field progress.
final class Course: Model, Content {
static let schema = "courses"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
init() { }
}
final class Student: Model {
static let schema = "students"
#ID(key: .id)
var id: UUID?
#Parent(key: "course_id")
var course: Course
#Parent(key: "user_id")
var user: User
#Field(key: "progress")
var progress: Int
init() { }
}
final class User: Model, Content {
static let schema = "users"
#ID(key: .id)
var id: UUID?
#Field(key: "name")
var name: String
#Field(key: "private")
var somePrivateField: String
init() { }
}
I have a route like this, which returns an array of courses:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.all()
.get()
}
The resulting JSON looks something like this:
[
{
"id": 1,
"name": "Course 1"
}
]
How can I also include the array of students, so that the end result is something like this?
[
{
"id": 1,
"name": "Course 1",
"students": [
{
"user": {
"id": 1,
"name": "User 1"
},
"progress": 0
},
{
"user": {
"id": 2,
"name": "User 2"
},
"progress": 100
},
]
]
I can add the users to the Course model like this:
#Siblings(through: Student.self, from: \.$course, to: \.$user)
public var users: [User]
And then change my route like this:
func list(req: Request) throws -> EventLoopFuture<[Course]> {
return Course
.query(on: req.db)
.with(\.$user)
.all()
.get()
}
But that only adds the user info to the result, NOT the extra properties on the pivot table (namely, progress). It kinda seems to me that even though pivot tables can have extra properties and the docs even specifically point that out, there are no good ways of actually dealing with this scenario since #Siblings don't point to the pivot at all.
Bonus question: I'd want the User model be mapped to a PublicUser model, so that private/internal fields are not part of the JSON result. See this question for what I mean. I want to do that same thing, but with the Student pivot's User model. Complicated, I know 😬
I encountered the same issue about accessing additional fields in the pivot table, but there is a fairly tidy way of accomplishing this. In addition to your siblings relationship, define a #Children relation from Course to Student. Then, in your query, do a nested with.
Put this in your Course model:
#Children(for:\.$course) var students: [Student]
Query:
let query = Course.query(on: req.db).with(\.$students){ $0.with(\.$user) }.all()
The first with gets the additional fields of the pivot table and then the nested with get the User model.
You can query and use the extra properties on the pivot table directly using the pivots property on the Sibling relation which eagerly loads the pivot table objects through the sibling relation for easy access.
Eg:
Course.query(on: db)
.with(\.$user)
.with(\.$user.$pivots).all().map { course in
// you can now access the loaded students using:
let students = course.$user.pivots
...
}

Access Data Object by ID

I have the following structs that defined according to the SwiftUI example in Apple.
struct Cat: Hashable, Codable, Identifiable {
let id: Int
let name: String
let audio: [Int]? // list of audio of cat sound
}
struct CatAudio: Hashable, Codable, Identifiable {
let id: Int
let filename: String
}
I then would like to access the audio and then deliver in the view.
I have json data like this:
[
{
"id": 55,
"name": "meow",
"audio": [6,5]
},
{
"id": 3,
"name": "meowmeow",
"audio": [2]
}
]
AudioData.json
[
{
"id": 5,
"filename": "5.wav"
},
{
"id": 2,
"filename": "2.wav"
},
{
"id": 6,
"filename": "6.wav"
}
]
The json files loaded successfully.
#Published var cats: [Cat] = load("CatData.json")
#Published var catAudios: [CatAudio] = load("CatAudio.json")
I then tried to get an audio object from my environment model data:
#EnvironmentObject var modelData: ModelData
and then I want to get an the corresponding audio object of the cat. but I failed to do so as I do not know how to use the "Id" to get it.
Example:
Assume that I got the cat object from my model:
let cat = modelData.cats[0]
I then want to get its audio data according to the id stored in the audio list of it
let catAudio = modelData.catAudios[cat.audio[0]!] // exception here
I found that it is because the array order may not be consistent with the "Id". I want to make use of the "Id" instead of the Array order to get the item.
How can I do it?
========
I have tried to write a function to get the list of CatAudio of a cat.
I have also make audio non-optional.
func getAudio(cat: Cat) -> [CatAudio] {
return cat.audio.compactMap{id in catAudio.filter { $0.id==id } }
}
But it complains and said that cannot convert the value of type [CatAudio] to closure result type 'CatAudio'
I got confused with that.
You need to use high-order functions to match the id values in the audio array with the elements in the CatAudio array
Assuming the 2 arrays and a selected Cat object
var cats: [Cat] = ...
var catAudios: [CatAudio] = ...
let cat = cats[0]
To select one audio for a one id
if let firstId = cat.audio?.first {
let audio = catAudios.first { $0.id == firstId}
}
To get an array of all CatAudio for the cat object
if let array = cat.audio {
let values = array.compactMap { id in catAudios.filter { $0.id == id }}
}
The code would be simpler if the audio array wasn't optional, any reason for it to be declared optional?

How to nest data in Firebase Realtime database (Swift)

My database in Firebase currently looks like this:
locations: {
person: ""
location: ""
}
How do I nest the data so that it looks something like this:
locations: {
person: "" {
location: ""
}
}
A structure where locations is the parent, person is the child, and location is the child of person.
I know the relationships, I just am not too sure of the syntax.
Here is the code I currently have, written in Swift:
let locRef = locationRef.childByAutoId()
let locItem = [
"location": getLocation(),
"person": senderId
]
locRef.setValue(locItem)
Thanks.
let locRef = locationRef.childByAutoId()
let locItem = [
senderId : [
"location": getLocationID()
]
]
SenderID will be the key for the person, so you can find the Person by its ID... And the ID is holding a nested location key
Do you only need the logic? Or do you need working code example?

Adding additional items to dictionary

Would like to add John together with Peter in this combination:
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = "John"
println(myData0)
Println gives only John back instead Peter AND John. Is there a way to add John without overwriting Peter?
If you want to keep a string value of dictionary, you can do like that
var myData0: [String: String] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = myData0["DrinksMilk"]! + ", John"
println(myData0)
If not, you can change the type of dictionary value to AnyObject like that :
var myData0: [String: AnyObject] = ["Item": "Milk", "Description": "Milk is white", "DrinksMilk": "Peter"]
myData0["DrinksMilk"] = [myData0["DrinksMilk"]!, "John"]
println(myData0)
It appears that you are attempting to encode a data type into a dictionary. A better approach, not knowing anything more about your specific problem, is to define a class for this:
class Drink {
var name:String
var desc:String
var drinkers:[Person]
// ...
func addDrinker (person:Person) {
drinkers.append(person)
}
}
You've declared your dictionary to hold values of type String but, as you describe, now you want the dictionary values to be String or Array. The common type of these type value types is Any (it can be AnyObject if you've imported Foundation; but that is a Swift anomaly). You will then need to 'fight' the type system to coerce the "DrinksMilk" field as a modifiable array and to add "John".
// First extract `"DrinksMilk"` and force it to a mutating array:
var drinkers:[String] = myData0["DrinksMilk"]! as [String]
// Add the new drinker
drinkers.append("John")
// Reassign back into data
myData0["DrinksMilk"] = drinkers