how to initialise variables in #NSManaged object class as a JSON value? - swift

I want to initialise the variables in the 'Item' class like the variables 'Item2' class
this is the 'Item' class :-
import UIKit
import CoreData
import SwiftyJSON
class Item: NSManagedObject {
#NSManaged var name = String()
#NSManaged var symbol = String()
#NSManaged var checked : Bool = false
#NSManaged var buyPrice = String()
#NSManaged var rank = String()
}
this is 'Item2' class :-
import Foundation
import SwiftyJSON
class Item2 : Codable {
var name = String()
var symbol = String()
var checked : Bool = false
var buyPrice = String()
var rank = String()
init(bitJSON: JSON) {
self.name = bitJSON["name"].stringValue
self.symbol = bitJSON["symbol"].stringValue
self.buyPrice = bitJSON["price_usd"].stringValue
self.rank = bitJSON["rank"].stringValue
}
}

ManagedObject are not normal object. They are an object-oriented way to assess a database. Managed objects use a context to get all their properties. A managedObject must have context - they simply don't work without one.
extension Item {
static func insert(json:JSON, in context:NSManagedObjectContext) -> Item{
let newItem = Item.init(entity: Item.entity(), insertInto:context)
newItem.name = json["name"].stringValue
newItem.symbol = json["symbol"].stringValue
newItem.buyPrice = json["price_usd"].stringValue
newItem.rank = json["rank"].stringValue
}
}
Notice two things about this method: 1) it does not assume that it knows the context - that is a job for a higher level - for some apps it is simply inserted direclty into the viewContext, for other apps a temporary background context is created. and 2) it does not save the context - that is also not it's job - other object may be edited or created at the same time and they should all be save transactionally.
It would be used for example like this:
persistentContainer.performBackgroundTask { (context) in
let newItem = Item.insert(json: json, in: context)
do {
try context.save()
} catch {
// log to you analytic provider
}
}
Hope this helps.

Related

How to get #Published updates in Combine with a Nested ObservableObject Array (Replicate ValueType Behavior)

I'm not really sure how to properly title the question, so hopefully it is made clear here.
the use case is mostly hypothetical at the moment, but if I have an array of published objects where Child is itself an ObservableObject:
#Published var children: [Child]
then, if I update an individual Child's property which is also published, I'd like the publisher to fire.
(if we use value types, it triggers the entire array and functions easily, this is best for most solutions in my experience)
TestCase
import XCTest
import Combine
final class CombineTests: XCTestCase {
final class RefChild: ObservableObject {
#Published var name: String = ""
}
struct ValueChild {
var name: String = ""
}
final class Parent: ObservableObject {
#Published var refChildren: [RefChild] = []
#Published var refNames: String = ""
#Published var valueChildren: [ValueChild] = []
#Published var valueNames: String = ""
init() {
$refChildren.map { $0.map(\.name).joined(separator: ".") } .assign(to: &$refNames)
$valueChildren.map { $0.map(\.name).joined(separator: ".") } .assign(to: &$valueNames)
}
}
func testChildPublish() {
let parent = Parent()
parent.refChildren = .init(repeating: .init(), count: 5)
parent.valueChildren = .init(repeating: .init(), count: 5)
XCTAssertEqual(parent.refNames, "....")
XCTAssertEqual(parent.valueNames, "....")
parent.refChildren[0].name = "changed"
// FAILS
XCTAssertEqual(parent.refNames, "changed....")
parent.valueChildren[0].name = "changed"
// PASSES
XCTAssertEqual(parent.valueNames, "changed....")
}
}

Why not use a struct-based singleton in Swift

Why not use a struct-based singleton?
I created decodable struct-based singleton.
struct Person: Decodable {
static var shared = Person()
private(set) var name: String?
var age: Int?
private init() {
}
mutating func initData(from data: Data) {
if let person = try? JSONDecoder().decode(Person.self, from: data) {
self = person
}
}
}
init from other class like this:
Person.shared.initData(from: data)
and use parameters:
let name = Person.shared.name
Person.shared.name = "MyName" //error
Person.shared.age = 20
Is this the wrong way?
You can't use a struct fora singleton because struct is a value type so when you assign it to a variable you get a copy. This can be easily shown
struct Singleton {
static var shared = Singleton()
var value: Int
private init() {
value = 0
}
}
Singleton.shared.value = 1
var otherSingleton = Singleton.shared
otherSingleton.value = 2
Now if we print the value of both
print(Singleton.shared.value, otherSingleton.value)
we get
1 2
So otherSingleton is clearly a separate instance so now we have 2 singletons :)
But if we simply change the type of Singleton to class that is a reference type and then run the same code the result of the print is
2 2
since it is the same instance we have changed the value property for.

How to save and load GKGameModelPlayer from Realm in Swift?

I am attempting to implement a GKGameModel in my application. In it, it holds variables to a few things, but for the purposes of my question I'm interested in the following two variables:
import GameplayKit
final class GameModel: NSObject, GKGameModel {
var players: [GKGameModelPlayer]?
var activePlayer: GKGameModelPlayer?
}
I do something like this to initialise the game with 3 players (not exact)
let game = GameModel.init()
game.players = [Player(),Player(),Player()] // Create 3 players
guard let firstPlayer = game.players.first else {
return
}
game.activePlayer = firstPlayer
A player class is defined as:
class Player : NSObject, GKGameModelPlayer {
var playerId: Int // GKGameModelPlayer protocol variable
let name: String
var cash: Int = 0
}
In my project I have Realm Entities and the models seperated. So there will be a PlayerEntity and a Player class.
I'm wanting to use RealmSwift to save and load the GKGameModelPlayer data, and more specifically the ability to store/re-store the active player.
I think the key here is the playerId variable; but I am not sure.
But what I'm not sure about is retrieving this information and then re-mapping it into a valid GKGameModelPlayer format
My current idea/theory is that I need to map my model to an entity class and vice-versa.
Ie:
// [REALM] Player entity
class PlayerEntity: Object {
#objc dynamic var id = UUID().uuidString
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
override static func primaryKey() -> String {
return "id"
}
}
And then I extend this class to do some "mapping":
extension PlayerEntity {
// Map model -> entity
convenience init(model: Player) {
self.init()
self.playerId = model.playerId
self.name = model.name
self.cash = model.cash
}
}
extension Player {
// Map entity -> model
convenience init(entity: PlayerEntity) {
let playerId = entity.playerId
let name = entity.name
let cash = entity.cash
self.init(id: playerId, name: name, cash: cash)
}
}
Right now, the playerId is always zero (0) because I'm not really sure how to set it.
I can save a player to realm.
The issue comes from when I try to restore the player, and I want to restore the activePlayer variable in the GameModel
Therefore, my question is:
How would I go about saving and restoring the activePlayer variable so that it continues to comply to GKGameModelPlayer?
I appreciate any assistance on this.
With thanks
While you could use those extensions, sometimes simpler is better. Here's a rough example:
class PlayerEntity: Object {
#objc dynamic var playerId: Int = 0
#objc dynamic var name: String = ""
#objc dynamic var cash: Int = 0
convenience init(withPlayer: PlayerClass) {
self.init()
self.playerId = withPlayer.playerId
self.name = withPlayer.name
self.cash = withPlayer.cash
}
func getPlayer() -> Player {
let p = Player()
p.playerId = self.playerId
p.name = self.name
p.cash = self.cash
return p
}
override static func primaryKey() -> String {
return "playerId"
}
}
to load all the players into an array... this will do it
let playerResults = realm.objects(PlayerEntity.self)
for player in playerResults {
let aPlayer = player.getPlayer()
self.playerArray.append(aPlayer)
}
Notice the removal of
#objc dynamic var id = UUID().uuidString
because it's not really being used to identify the object as a primary key.
The primary key is really
var playerId: Int // GKGameModelPlayer protocol variable
which is fine to use as long as it's unique.

How can I properly map RealmDB Results objects to SwiftUI Lists?

I am trying to display results of a realmdb query in a SwiftUI list but have trouble when deleting database objects.
I am trying to use something like this:
final class DBData: ObservableObject{
let didChange = PassthroughSubject<DBData, Never>()
private var notificationTokens: [NotificationToken] = []
var events = try! Realm().objects(ADMEvent.self)
#Published var events: [ADMEvent] = []
init() {
// Observe changes in the underlying model
self.notificationTokens.append(posts.observe { _ in
self.events = Array(self.posts)
self.didChange.send(self)
})
}
}
Which works if I display items In a list but the moment I use realm.deleteAll() the app crashes because it looks like Swift UI's list implementation is trying to diff the list, accessing the now invalidated realm db objects.
There are like 3 or 4 similar questions on stack overflow but they are all out of date in one way or another, or work but still have this issue when it comes to deletion.
Thanks!
Realm objects are live an autoupdating this is why they crash when you try to hold onto a deleted object. Instead of giving your publish subject the Realm.Object map it to a struct that has only the fields you need to use and use that array to drive SwiftUI.
struct Event: Identifiable {
var id: String
var name: String
var date: Date
}
final class DBData: ObservableObject {
private var notificationTokens: [NotificationToken] = []
var events = try! Realm().objects(ADMEvent.self)
#Published var publishedEvents: [ADMEvent] = []
init() {
// Observe changes in the underlying model
self.notificationTokens.append(posts.observe { _ in
self.publishedEvents = events.map { Event(id: $0.id, name: $0.name, date: $0.date)}
})
}
}
I love this approach! I just want to put this out there because the accepted answer won't compile and has more than one issue:
#Published var publishedEvents: [ADMEvent] = []
should be:
#Published var publishedEvents: [Event] = []
and
self.notificationTokens.append(posts.observe { _ in
should be:
self.notificationTokens.append(events.observe { _ in
so
final class DBData: ObservableObject {
private var notificationTokens: [NotificationToken] = []
var events = try! Realm().objects(ADMEvent.self)
#Published var publishedEvents: [Event] = []
init() {
// Observe changes in the underlying model
self.notificationTokens.append(events.observe { _ in
self.publishedEvents = events.map { Event(id: $0.id, name: $0.name, date: $0.date)}
})
}
}

Swift, Firebase: `setValue` giving error "AnyObject cannot be used with Dictionary Literal"

I am experiencing an error Contextual type AnyObject cannot be used within dictionary literal in that func addPet down there, while trying to populate database with the newPet argument constituents in a dictionary.
import Foundation
import Firebase
struct Pet {
var name:String?
var type:String?
var age:Int?
var feedingList:[String]
var walkingList:[String]
}
struct User {
var currentId:String?
var numberOfPets:Int?
var pets:[Pet]
}
class petBrain {
var reff = FIRDatabaseReference()
var currentUser:User = User(currentId: "",numberOfPets: 0,pets: [])
init(){
self.reff = FIRDatabase.database().reference()
}
func setUserId(cId:String?){
self.currentUser.currentId = cId
}
func addPet(newPet:Pet) {
self.reff.child("pets").childByAutoId().setValue(["name":newPet.name, "type":newPet.type, "age":newPet.age, "fList":newPet.feedingList, "wList":newPet.walkingList])
}
}
I have already done this in other viewController, similarly for users and its working fine in dictionary shape (producing no error)
let em = emailTextField.text!
let us = usernameTextField.text!
...
else {
print("User created! Loging in.")
self.login()
// adding user to DB of users
self.ref.child("users").child(user!.uid).setValue(["email":em, "username":us])
}
What did i do wrong in the pets case? its maybe due to struct, or struct element types? Are those two structs well defined?
? is used if the value can become nil in the future.
! is used if it really shouldn't become nil in the future, but it needs to be nil initially.
See the problem is Swift is a strictly typed language, if you declare a variable ? you are saying that it's of type nil. So a dictionary cant tell what type of a value would it be storing....
var myVar : String? // Look myVar, you and your type is completely nil as of now
var myVar : String! // Look myVar, your value is nil as of now but your type is certainly of String
Just change your code to this:-
struct Pet {
var name:String!
var type:String!
var age:Int!
var feedingList:[String]
var walkingList:[String]
}
struct User {
var currentId:String?
var numberOfPets:Int?
var pets:[Pet]
}
class petBrain {
var reff = FIRDatabaseReference()
var currentUser:User = User(currentId: "",numberOfPets: 0,pets: [])
init(){
self.reff = FIRDatabase.database().reference()
}
func setUserId(cId:String?){
self.currentUser.currentId = cId
}
func addPet(newPet:Pet) {
let dict = ["name":newPet.name, "type":newPet.type, "age":newPet.age, "fList":newPet.feedingList, "wList":newPet.walkingList]
self.reff.child("pets").childByAutoId().setValue(dict)
}
}