Get associated object value from filter query using Realm swift - 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.

Related

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.

Filter Realm Results array returns elements that should be filtered

Any ideas why this filter is not working correctly ?
for item in activeItems {
print("item.product: \(item.product), \(item.spaceRequired)")
}
returns
item.product: nil, 40.0
Filtering where product is nil
let f1 = activeItems.filter{$0.product != nil}
print("f1: \(f1)")
print("f1.count: \(f1.count)")
returns a count of ZERO but the array still appears to contain an item
f1: LazyFilterSequence<Results<AssortmentItem>>(_base: Results<AssortmentItem> <0x109ce2c90> (
[0] AssortmentItem {
...
f1.count: 0
And then filtering and mapping just spaceRequired
let f11 = f1.filter{$0.product!.isProduct == true}.map({$0.spaceRequired})
print("f11: \(f11)")
returns the same array with a single item
f11: LazyMapSequence<LazyFilterSequence<Results<AssortmentItem>>, Double>(_base: Swift.LazyFilterSequence<RealmSwift.Results<Merchandise_Manager.AssortmentItem>>(_base: Results<AssortmentItem> <0x109ce2c90> (
[0] AssortmentItem {
And then trying to reduce crashes
let w = f11.reduce(0,+)
This seems to fix the problem
let width = Array(activeItems.filter{$0.product != nil}).filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
Is this a bug in Swift 5 or in Realm ?
EDIT: It looks like this is a bug in Realm's handling of things.
To be a bit cleared below is a more complete set of the Realm objects.
import Foundation
import RealmSwift
let activeDate: NSDate = Date() as NSDate
let defaultWidth: Double = 40.0
class MyObject: Object {
#objc dynamic var number: Int = 0
#objc dynamic var name: String?
let items = List<ChildObject>()
}
extension MyObject {
var activeItems: Results<ChildObject> {
let activeDate = activeDate // Some globally defined value
let active = items.filter("startDate <= %# && (endDate >= %# || endDate == nil)", activeDate, activeDate).sorted(byKeyPath: "number")
return active
}
/// Works Correctly
var totalWidth: Double {
let width = Array(activeItems.filter{$0.product != nil}).filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
let width2 = Array(activeItems.filter{$0.product == nil}.map({$0.spaceRequired})).reduce(0,+)
return width+width2
}
/// Crashes
var totalWidth: Double {
let width = activeItems.filter{$0.product != nil}.filter{$0.product!.isProduct == true}.map({$0.spaceRequired}).reduce(0,+)
let width2 = activeItems.filter{$0.product == nil}.map({$0.spaceRequired}).reduce(0,+)
return width+width2
}
}
class ChildObject: Object {
#objc dynamic var parent: MyObject?
#objc dynamic var number: Int = 0
#objc dynamic var product: Product?
#objc dynamic var name: String?
#objc dynamic var spaceRequired: Double = 40.0
#objc dynamic var startDate: NSDate?
#objc dynamic var endDate: NSDate?
}
extension ChildObject {
var spaceRequired: Double {
if let p = product {
return p.width
} else {
return defaultWidth
}
}
}
class Product: Object {
#objc dynamic var isProduct: Bool = false
#objc dynamic var width: Double = 30.0
}
There's a couple of issues at work here but the main problem is that Realm Results are live updating; while you can filter data using the Swifty
let f1 = activeItems.filter{$0.product != nil}
it's going to give intermittent results since Realm doesn't know which items are filtered or not as .filter { is not a Realm function and Realm won't know what to update within the results.
You should generally use the built in Realm filtering mechanism
let results = realm.objects(ItemClass.self).filter("product != nil")
Those results will be live updating - if an object leaves the filter parameter, the results follow that. If an object matches the filter the results are updated as well.
I believe this Github issue #2138 provides some more light on the issue.
If you absolutely need static data, then I would suggest extending the Results class to return an array; like this
extension Results {
func toArray() -> [Element] {
return compactMap { $0 }
}
}
Keeping in mind this will use more memory as Realm objects are lazy loaded and and array isn't.
EDIT:
There's some additonal information in the question so I crafted up a simple example trying to replicate the issue. There's a HouseClass object which contains a List of RoomClass objects and then the HouseClass is extended to return the total width of all of the rooms in its list.
class RoomClass: Object {
#objc dynamic var room_name = ""
#objc dynamic var width = 0
#objc dynamic var length = 0
#objc dynamic var belongs_to_house: HouseClass!
}
class HouseClass: Object {
#objc dynamic var house_id = NSUUID().uuidString
#objc dynamic var house_name = ""
let rooms = List<RoomClass>()
override static func primaryKey() -> String? {
return "house_id"
}
}
extension HouseClass {
var totalWidth: Int {
let width = Array(rooms).map {$0.width}.reduce(0,+)
return width
}
var anotherTotalWidth: Int {
let width = rooms.map {$0.width}.reduce(0,+)
return width
}
}
and then the code to get all of the houses and output their room widths based on two different functions (see the HouseClass extension)
let houseResults = realm.objects(HouseClass.self)
for house in houseResults {
let w0 = house.totalWidth
print(w0)
let w1 = house.anotherTotalWidth
print(w1)
}
I added 100 houses each with three rooms and ran the above code several times without crash.
Count of f1 is 0 so map is not worked.
You can optimize your width calculation as following
let width = activeItems
.filter { $0.product?.isProduct ?? false }
.map { $0.spaceRequired }
.reduce(0,+)

Invalid value when query on Int in realm

I have a realm declaration like that :
#objc dynamic var roomId = UUID().uuidString
#objc dynamic var roomName = ""
#objc dynamic var roomType = ""
#objc dynamic var floor = 1
#objc dynamic var placeId : String?
I am trying to get a query of all rooms for a specific floor in a specific place from realm DB with this function :
static func getAllRoomNamesAndTypesForQuery (placeName: String? ,room : String? , floor : Int?) -> [[String]] {
var result : [[String]] = [[]]
if placeName != nil , floor != nil {
let placeId = Places.specificPlaceQueries(placeName: placeName)[0] as! String
let allRooms = Users.realm.objects(Rooms.self).filter("placeId == '\(placeId)' AND floor == '\(floor!)'")
var roomNames = [""]
var roomTypes = [""]
for number in 0..<allRooms.count {
roomNames.append(allRooms[number].roomName)
roomTypes.append(allRooms[number].roomType)
}
let sortedRoomNames = roomNames.sorted()
let sortedRoomTypes = roomTypes.sorted()
result = [sortedRoomNames , sortedRoomTypes]
}
return result
}
but it keeps showing me the following strange error
Expected object of type int for property 'floor' on object of type
'Rooms', but received: 1
I dunno how it rejects 1 as Int ?? anyone know where is the problem here??
You shouldn't be using String interpolation when creating NSPredicates, since even though it is supported, it is really easy to mess up the predicate format. Simply use %# for substituting variable values into the predicate.
let allRooms = Users.realm.objects(Rooms.self).filter("placeId == %# AND floor == %#",placeId, floor)
Some further improvements to your code: don't use nil check, then force unwrapping, use optional binding when working with Optionals.
if let placeName = placeName , let floor = floor {
Also don't add an initial value to Arrays when creating them, instead of var roomNames = [""] and var roomTypes = [""], do
var roomNames = [String]()
var roomTypes = [String]()
Can you try
let allRooms = Users.realm.objects(Rooms.self).filter {
$0.placeId == placeId
&& $0.floor == floor
}
First :
set this
var roomNames = [""];
var roomTypes = [""];
to this
var roomNames = [String]()
var roomTypes = [String]()

How to change Realm singleton attribute value

I have a Realm object:
class TransactionDB: Object {
dynamic var transactionID : Int = -1
dynamic var registrationPlate : String = ""
dynamic var locationID : Int = 0
dynamic var time : String = ""
dynamic var subscription : String = ""
dynamic var startTime : NSDate = NSDate()
dynamic var endTime : NSDate = NSDate()
dynamic var status : Int = -2
dynamic var requestType : Int = -1
var extensions : List<ExtensionDB> = List<ExtensionDB>()
dynamic var price : Double = 0
dynamic var currency : String = ""
private dynamic var test : Int = 10
override static func primaryKey() -> String? {
return "transactionID"
}
class var sharedInstance : TransactionDB {
struct Singleton {
static let instance = TransactionDB()
}
return Singleton.instance
}
static func saveOrUpdate {
// ......
}
and a singleton version for it. So I have one object over many controllers when screens change.
A few days back I was using some older Objective-C version of Realm but now I changed to the Swift-only version 1.0.2 and I'm trying to fix all the problems.
So now it came to part that when I try to call stuff like:
TransactionDB.sharedInstance.time = ""
I get an exception. However, after I do the following, it works:
let realm = try! Realm()
try! realm.write {
TransactionDB.sharedInstance.time = ""
}
So am I creating the singleton wrong or is this just the way it has to be done? Because, for me, it is a little annoying that I would always have to use a try block when I want to change the value of some attribute.
Take a look at the first line of the Realm docs for the write section.
It states:
All changes to an object (addition, modification and deletion) must be done within a write transaction.
So yea, it's just how you have to do it.

Swift: Realm error at init "NULL is not supported as an RLMObject property". But I don't have a NULL

It seems most people with this error are trying to create null strings. I just have three properties
dynamic var babyEvent: Int
dynamic var eventDate: NSDate
dynamic var timeSpent: Int
which are initialized in init() to
override init()
{
self.babyEvent = BabyWet
self.eventDate = NSDate()
self.timeSpent = 5
super.init()
}
but by the time super.init() is called I get
'(null)' is not supported as an RLMObject property.
There are two Ints and one NSDate, all of which are valid Realm property types. So why am I getting this error?
Realm doesn't support Swift enum's with no raw value. But adding a raw type to the BabyEvent enum and assigning the raw value to your realm objects works:
enum BabyEvent: Int {
case BabyWet, case BabyDry
}
class MyRealmObject: RLMObject {
dynamic var babyEvent = BabyEvent.BabyWet.rawValue
dynamic var eventDate = NSDate()
dynamic var timeSpent = 0
}
In Swift Enums have a specific type. So while you think you are passing an Int for BabyWet you are actually passing something of that specific type.
It seems that you have an Enum for the BabyEvent, so you should really have a look at the rawValue property:
self.babyEvent = BabyWet.rawValue
As I wrote, you can just set the starting values in the model definitions:
class TestClass: RLMObject {
dynamic var babyEvent: Int = 1
dynamic var eventDate: NSDate = NSDate()
dynamic var timeSpent: Int = 5
}
but this also works for me:
class TestClass: RLMObject {
dynamic var babyEvent: Int
dynamic var eventDate: NSDate
dynamic var timeSpent: Int
override init() {
babyEvent = 1
eventDate = NSDate()
timeSpent = 5
super.init()
}
}
In both cases I simply use
let realm = RLMRealm.defaultRealm()
var myTestObject = TestClass()
realm.beginWriteTransaction()
realm.addObject(myTestObject)
realm.commitWriteTransaction()
to create and add the object to the Realm.