Remove duplicated in a Struct Array - swift

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)

Related

How to get Realm Objects by ID only?

I set up a Realm model (in Swift) with a children relationship:
import Foundation
import RealmSwift
class MyObject1: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childrenIDs = RealmSwift.List<String>()
}
I added the relationship as a RealmSwift.List of Strings, because I intent to create other model classes, each with an ID, that can be added as children. In other words, the children might not be of a single type/class. For example:
import Foundation
import RealmSwift
class MyObject2: RealmSwift.Object, RealmSwift.ObjectKeyIdentifiable {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var title: String
}
Now at some point, I have to fetch all the children by their ID, for example to show them in a list. But I do not know how. I know the realm.objects(Class.self).filter but this expects a single class type. So my question is, how can I fetch objects from a Realm only by their ID (ie without their class/type)?
As mentioned in a comment, ObjectId's are generic and have no correlation to the object class they are tied to. So you can't do exactly what you want.
But... there are options.
TL;DR
create another object with properties to hold the id and the object type, defined by an enum or
Use AnyRealmValue to store the objects themselves
Long answers:
Long answer #1
Create a class to store the id and type. Suppose we have a database that stores wine grape types; some are white and some are red but you want to store them all in the same list. Let me set up an example
enum GrapeTypesEnum: String, PersistableEnum {
case white
case red
}
class Grape: Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var grape_name = ""
#Persisted var grape_type: GrapeTypesEnum
}
class GrapeObjects: Object { //contains the grape id string & grape type
#Persisted var child_id = ""
#Persisted var child_type: GrapeTypesEnum
}
so then the model would be
class MyObject1: RealmSwift.Object {
#Persisted(primaryKey: true) var _id: RealmSwift.ObjectId
#Persisted var childGrapes = RealmSwift.List<GrapeObjects>()
}
You could then create a couple of grapes
let whiteGrape = Grape()
whiteGrape.name = "Chenin Blanc"
whiteGrape.grape_type = .white
let redGrape = Grape()
redGrape.name = "Cabernet Franc"
redGrape.grape_type = .red
let grape0 = GrapeObjects()
grape0.grape_id = whiteGrape._id.stringValue()
grape0.grape_type = whiteGrape.grape_type
let grape1 = GrapeObjects()
grape1.grape_id = redGrape._id.stringValue()
grape1.grape_type = redGrape.grape_type
then finally store those objects in your class. Those can then be sorted, filtered and you will know which ID goes with what type of object
let anObject = MyObject1()
anObject.childGrapes.append(objectsIn: [grape0, grape1])
Long answer #2
Another option is to not store the objectID string but store the actual objects by leveraging AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
let red = WhiteGrape()
let white = RedGrape()
let obj0: AnyRealmValue = .object(red) //both of these are objects,
let obj1: AnyRealmValue = .object(white) // even through they are different objects
let m = MyClass()
m.myGrapeList.append(obj0)
m.myGrapeList.append(obj1)
Then you can take action based on the the objects type
for grape in m.myGrapeList {
if let white = grape.object(WhiteGrape.self) {
print("is a white grape")
} else if let red = grape.object(RedGrape.self) {
print("is a red grape")
}
}

Swift - How to Change Multi-Level JSON Struct Single Element Value on Condition

I have multi-level Struct that converts complex JSON data to a Struct. What I am struggling is to change the dataStruct.group.point.presentValue element value on condition.
var dataStruct : DataStruct = load("jsonfile.json")
struct DataStruct : Codable {
let name: String
var group: [groupData]
}
struct groupData: Codable, Hashable {
let id, name : String
var point : [pointInfo]
}
struct pointInfo : Codable, Hashable {
let id : String
let address : address
let name : String
var presentValue : String
}
struct address: Codable, Hashable {
let index: String
let type: String
}
I have tried the following map function, but the compiler complains that the Group in ForEach is 'let' constant.
Basically the function is supposed to compare address.index field in the Struct to the passed pointNo variable, and once it has been found (unique), point.presentValue is changed to the new value.
What is the correct way to achieve this?
func updatePresentValue(pointNo : String) {
dataStruct.group.forEach { Group in
Group.point = Group.point.map { point -> pointInfo in
var p = point
if point.address.index == pointNo {
p.presentValue = "New value"
return p
}
else { return p }
}
}
}
Basically there are two ways.
Extract the objects by assigning them to variables, modify them and reassign them to their position in dataStruct.
Enumerate the arrays and modify the objects in place.
This is an example of the second way
func updatePresentValue(pointNo : String) {
for (groupIndex, group) in dataStruct.group.enumerated() {
for (pointIndex, point) in group.point.enumerated() {
if point.address.index == pointNo {
dataStruct.group[groupIndex].point[pointIndex].presentValue = "New value"
}
}
}
}
It gets more complicated when dealing with multilevel structures but here is one way to do it where we first enumerate over group so we get both the object and the index of the object for each iteration so we can use this index when updating the group array. The inner struct is updated using a mutable copy of point
for (index, group) in dataStruct.group.enumerated() {
if group.point.contains(where: { $0.address.index == pointNo }) {
var copy = group
copy.point = group.point.reduce(into: []) {
if $1.address.index == pointNo {
var pointCopy = $1
pointCopy.presentValue = "new value"
$0.append(pointCopy)
} else {
$0.append($1)
}
}
dataStruct.group[index] = copy
}
}

Swift : How to merge two Realm Results in on in 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")
}

Observing a #Published var from another Object

I am trying to get one object to listen to changes in the property of another object. I have it working as shown below, but I would prefer the observing object knew nothing of the Model, just the property.
class Model : ObservableObject{
#Published var items: [Int] = []
}
class ObjectUsingItems{
var itemObserver: AnyCancellable?
var items: [Int] = []
func observeItems(model: Model){
itemObserver = model.$items
.sink{ newItems in
self.items = newItems
print("New Items")
}
}
}
At the moment I begin observing the model.items as follows - which works:
let model = Model()
let itemUser = ObjectUsingItems()
itemUser.observeItems(model: model)
model.items.append(1) // itemUser sees changes
Unfortunately I can’t seem to figure out just what is required as the parameter to the observeItems method so that it works without knowing anything about the Model - like this:
class ObjectUsingItems{
var itemObserver: AnyCancellable?
var items: [Int] = []
func observeItems(propertyToObserve: WhatGoesHere?){
itemObserver = propertyToObserve
.sink{ newItems in
// etc.
}
}
}
And then call it like so:
itemUser.observeItems(XXX: model.$items)
Can anyone explain what I need to do? Thanks!
You can just accept a publisher as a parameter, if you don't care where the value comes from.
In your very specific case, it could be:
func observeItems(propertyToObserve: Published<[Int]>.Publisher) {
itemObserver = propertyToObserve
.sink { self.items = $0 }
}
But this might be too restrictive - why only this specific publisher? In principle, you shouldn't care what the publisher is - all you care about is the output value and error type. You can make it generic to any publisher, so long as its Output is [Int] and Failure is Never (like that of the #Published one):
func observeItems<P: Publisher>(propertyToObserve: P)
where P.Output == [Int], P.Failure == Never {
itemObserver = propertyToObserve
.sink { self.items = $0 }
}
The usage would be something like this:
let model = Model()
let itemUser = ObjectUsingItems()
itemUser.observeItems(propertyToObserve: model.$items)

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.