How to save and load GKGameModelPlayer from Realm in Swift? - 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.

Related

sink value is Void with publisher

Consider the below Observable Object.
class User: ObservableObject {
#Published var age: Int
#Published var name: String {
didSet {
objectWillChange.send()
}
}
init(name: String, age: Int) {
self.name = name
self.age = age
}
}
The below code prints blank value or Void block. Any reason why? If we change Integer value age it should simply print that value.
let userJohnCancellable = userJohn.objectWillChange.sink { val in
print("Changed Value \(val)")
}
userJohn.age = 21
userJohn.age = 39
We can try to print the values in the closure using userJohn.age. But why does val not return a Integer value in this case.
Also what would be the best way to handle sink changes for age and name, both, one is String other is Int.
When you look in the documentation for ObservableObject you will find thatobjectWillChange is ObservableObjectPublisher
/// A publisher that emits before the object has changed.
public var objectWillChange: ObservableObjectPublisher { get }
which in turn is defined as having an output of type Void:
final public class ObservableObjectPublisher : Publisher {
/// The kind of values published by this publisher.
public typealias Output = Void
/// The kind of errors this publisher might publish.
///
/// Use `Never` if this `Publisher` does not publish errors.
public typealias Failure = Never
}
There is no need to send objectWillChange from didSet - each time any of the #Published values changes objectWillChange will emit a value.
If you want to get notified when a particular property marked as #Published changes and receive the new value you have to subscribe to that particular property:
let userJohn = User(name: "Johnny", age: 17)
let objectWillChangeCancellable = userJohn
.objectWillChange
.sink {
print("object will change")
}
let ageCancellable = userJohn
.$age
.sink { age in
print("new value of age is \(age)")
}
let nameCancellable = userJohn
.$name
.sink { name in
print("new value of name is \(name)")
}
This will get printed:
new value of age is 17
new value of name is Johnny
if you add:
userJohn.name = "John"
you will see the following printed:
object will change
new value of name is John
if you add:
userJohn.age = 21
you will see the following printed:
object will change
new value of age is 21
You seem to be confused about ObservableObject. It is for use with SwiftUI. But your code is not SwiftUI code, so you don't need ObservableObject and you really can't use it in any meaningful way. If the goal is to be able to subscribe to the properties of User so as to be notified when one of them changes, then it suffices to make those properties Published:
class User {
#Published var age: Int
#Published var name: String
init(age: Int, name: String) {
self.age = age; self.name = name
}
}
Here's an example of using it; I will assume we have a user property of a UIViewController:
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
var user = User(age: 20, name: "Bill")
override func viewDidLoad() {
super.viewDidLoad()
user.$age.sink {print("age:", $0)}.store(in: &cancellables)
user.$name.sink {print("name:", $0)}.store(in: &cancellables)
}
}
If this view controller's user has its age or name changed, you'll see the print output in the console.
If the question is how to handle both changes in a single pipeline, they have different types, as you observe, so you'd need to define a union so that both types can come down the same pipeline:
class User {
#Published var age: Int
#Published var name: String
enum Union {
case age(Int)
case name(String)
}
var unionPublisher: AnyPublisher<Union, Never>?
init(age: Int, name: String) {
self.age = age; self.name = name
let ageToUnion = $age.map { Union.age($0) }
let nameToUnion = $name.map { Union.name($0) }
unionPublisher = ageToUnion.merge(with: nameToUnion).eraseToAnyPublisher()
}
}
And again, here's an example of using it:
class ViewController: UIViewController {
var cancellables = Set<AnyCancellable>()
var user = User(age: 20, name: "Bill")
override func viewDidLoad() {
super.viewDidLoad()
user.unionPublisher?.sink { union in
switch union {
case .age(let age): print ("age", age)
case .name(let name): print ("name", name)
}
}.store(in: &cancellables)
}
}
Again, change the user property's name or age and you'll get an appropriate message in the console.

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.

Get associated object value from filter query using Realm swift

So Im pretty new to realm and i feel my question is very basic but i cant find the answer to it.
Basically I'm trying to query Realm for all playerName associated with a specific TeamID (ie. TeamID is not the primary key), however Im having trouble finding the solution. I keep getting a Value of type 'Results<playerInfoTable>' has no member 'playerName' error.
Below is my Realm class:
`class playerInfoTable: Object {
#objc dynamic var playerID: Int = 0
#objc dynamic var playerName: String = ""
#objc dynamic var jerseyNum: Int = 0
#objc dynamic var TeamID: String = ""
#objc dynamic var goalCount: Int = 0
#objc dynamic var shotCount: Int = 0
override class func primaryKey() -> String {
return "playerID"
}
}`
And the Code I'm using the query Realm:
let mainPlayerFilter = NSPredicate(format: "teamID == %#", "1")
let mainPlayerStrings = realm.objects(playerInfoTable.self).filter(mainPlayerFilter)
let mainPlayerTeamName = mainPlayerStrings.playerName
Solution!
let mainPlayerFilter = NSPredicate(format: "TeamID == %#", String(homeTeam!))
let mainPlayerStrings = realm.objects(playerInfoTable.self).filter(mainPlayerFilter)
let mainPlayerTeamName = mainPlayerStrings.value(forKeyPath: "playerName") as! [String]
I suppose you get the error when you declare the mainPlayerTeamName constant. Try the following:
let mainPlayerTeamName = mainPlayerStrings.first?.playerName
And I noticed that in your playerInfoTable class you declare "teamID" as "TeamID", while in your predicate you refer to it as "teamID". Decide which one you want because NSPredicate is case sensitive by default.

Realm query nested object

Hello everyone I'm having difficulties archiving one thing with a query of nested object. I have two realm object Championship and Game.
class Championship: Object {
dynamic var id: Int = 0
dynamic var name: String = ""
let games = List<Game>
override static func primaryKey() -> String? {
return "id"
}
}
class Game: Object {
dynamic var id: Int = 0
dynamic var homeTeamName: String = ""
dynamic var awayTeamName: String = ""
dynamic var status: String = "" //"inprogress", "finished", "scheduled"
override static func primaryKey() -> String? {
return "id"
}
}
And basically I want to retrieve all championships that have games with status "inprogress", so what I'm doing to archive that is:
let realm = try! Realm()
realm.objects(Championship.self).filter("ANY games.status = 'inprogress'")
What that query is doing is giving me all championships that have at least one game with that status, but is also giving me all the the games from that championship, but actually I just want the games with "inprogress" status.
There any way for doing that?
You could take two approaches here. If you want all games with inprogress status, you could write this:
let inProgressGames = realm.objects(Championship.self)
.flatMap { $0.games }
.filter { $0.status == "inprogress" }
This will return you all games in progress inside [Game] array. flatMap is used to combine all games from all championships and filter is used to filter all games with inProgress status.
If you want championships where every game is inprogress you could write:
let inProgressChampionships = realm.objects(Championship.self).filter {
let inProgressGames = $0.games.filter { $0.status == "inprogress"}
return $0.games.count == inProgressGames.count
}
This will return array of Championship where each game is inprogress.
Other that that, I would recommend using enum for game status instead of hard-coded strings.

Do Realm writes cascade?

I'm currently using Realm Swift 1.0.1.
Say you have a Realm Object, that has another Realm Object as a property, like so:
import RealmSwift
class Car: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var model = ""
}
class Garage: Object {
dynamic var id = 0
override static func primaryKey() -> String? {
return "id"
}
dynamic var carStored: Car?
}
If you then create new Car and Garage objects, with the Car being a property of the Garage... but only write the new Garage to the Realm, like so...
let teslaCar = Car()
teslaCar.id = 1
teslaCar.model = "Tesla"
let myGarage = Garage()
myGarage.id = 1
myGarage.carStored = teslaCar
let realm = try! Realm()
try! realm.write {
realm.add(myGarage, update: true)
}
... will the write cascade, and also save the teslaCar to the Realm as well as myGarage?
The Realm Swift write docs: https://realm.io/docs/swift/latest/#writes
I just tested it in one of the Realm sample apps to be absolutely sure. Yes, if you set an object as a linked object of another Realm object, both will be added to Realm in the next write transaction.