Swift : How to merge two Realm Results in on in swift - swift

I am querying realm for two different keywords then I want to merge those two realm result into one so I loop through its.
I have this Object descendent class:
class Item: Object {
#objc dynamic var _id: ObjectId? = nil
#objc dynamic var _partitionKey: String = ""
#objc dynamic var productBrand: String? = nil
override static func primaryKey() -> String? {
return "_id"
}
}
I'm fetching the realms
class SearchResult : ObservableObject {
#Published var storeitems: Results<Item>
storeitems = realm.objects(Item.self).sorted(byKeyPath: "_id")
let p1 = self.storeitems.filter("productDescription CONTAINS '"Cheese"'")
let p2 = self.storeitems.filter("productDescription CONTAINS '"Blue"'")
}
I am trying to merge p1 and p2 to get something like
p3 = p1 + p2
How can this be implemented with realm result?

You can adjust your filter to get what you want in one go:
let items = storeitems.filter { item in
item.productDescription.contains("Cheese") || item.productDescription.contains("Blue")
}

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,+)

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.

Remove duplicated in a Struct Array

I am filtering an array that can have a value where there are multiple Models of the same name, only they have different model numbers.
Variables
var modelArray = [model]()
Struct
struct model {
var modelName = String();
var modelNumber = String();
var manufacturer = String();
var phiTypeCode = String();
var phiTypeDesc = String();
}
Filter
var filteredArray = self.modelArray.filter { $0.manufacturer.range(of: manufacturerVar, options: .caseInsensitive) != nil }
This produces the correct filtered Array, only due to the possibility of similar models with different model numbers, I am trying to remove duplicates from filteredArray.
Fairly new to swift I don't have a great deal of experience making the struct hashable to be able to use the suggested solutions.
Hopefully this is more clear
First off, I tried making a sample in my PlayGround.
Conform your model model to the protocal Equatable, like so:
struct Car: Equatable {
var modelName = String()
var manufacturer = String()
init(modelName: String, manufacturer: String) {
self.modelName = modelName
self.manufacturer = manufacturer
}
static func == (lhs: Car, rhs: Car) -> Bool {
return lhs.modelName == rhs.modelName
}
}
In the code above, we're assuming that the modelName is the primary key of your model.
Now make a function that enumerates your data source and returns a new data source after checking the element one by one, like so:
// returns unique array
func unique(cars: [Car]) -> [Car] {
var uniqueCars = [Car]()
for car in cars {
if !uniqueCars.contains(car) {
uniqueCars.append(car)
}
}
return uniqueCars
}
Finally, you now have the function to generate a new unique data source.
Example:
// Variable
var cars = [Car]()
// Adding data models to data source
let car1 = Car(modelName: "Kia Picanto", manufacturer: "Kia")
let car2 = Car(modelName: "Honda XX", manufacturer: "Honda")
let car3 = Car(modelName: "Honda XX", manufacturer: "Honda")
cars.append(car1)
cars.append(car2)
cars.append(car3)
// Now contains only two elements.
let uniqueCars = unique(cars: cars)

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.