Consider the following Realm models:
class Fruit: Object {
#objc dynamic var name = ""
let localizations = List<Localization>()
/**
Returns the localized name of the fruit matching the preferred language of the app
or self.name if the fruit does not have a localization matching the user preferred language codes.
*/
var localizedName: String? {
guard !Locale.isPreferredLanguageDefaultAppLanguage else { return self.name }
let preferredLanguagesCodes = Locale.preferredLanguagesCodes
let localizations = preferredLanguagesCodes.compactMap({ languageCode in Array(self.localizations).filter({ $0.languageCode == languageCode }).first })
return localizations.first?.localizedName ?? self.name
}
}
class Localization: Object {
#objc dynamic var localizedName: String = ""
#objc dynamic var languageCode: String = ""
}
Let's say I have 2 fruits in my database (represented in JSON format for the sake of simplicity):
[
{
"name": "Apple",
"localizations": [
{
"localizedName": "Pomme",
"languageCode": "fr"
}
]
},
{
"name": "Banana",
"localizations": [
{
"localizedName": "Banane",
"languageCode": "fr"
}
]
}
]
Now I want to get all the fruits in my database, and sort them alphabetically by their localizedName.
var localizedNameSortingBlock: ((Fruit, Fruit) -> Bool) = {
guard let localizedNameA = $0.localizedName, let localizedNameB = $1.localizedName else { return false }
return localizedNameA.diacriticInsensitive < localizedNameB.diacriticInsensitive
}
let sortedFruits = Array(Realm().objects(Fruit.self)).sorted(by: localizedNameSortingBlock)
If the first preferred language of my device is "English", I get this:
Apple
Banana
If it's set to "French":
Banane
Pomme
It's quite simple, but this solution has a major inconvenient:
By casting the Results<Fruit> collection into an array, I'm loosing the ability to get live updates via Realm's notification token system.
The problem is, I can't sort using NSPredicate directly on the Results collection, because the localizedName property is a computed property, thus is ignored by Realm.
I thought about writing the localizedName value directly into the name property of the Fruit object, but doing so requires to loop through all fruits and change their name whenever the user change of preferred language. There must be a better way.
So my question is:
Is there a way to retrieve all the fruits in my database, get them sorted by their localizedName, without loosing the ability to receive batch updates from Realm?
Related
Say I have a User class with three properties: name, email, and fruits. What's the most efficient way to determine the differences, and print them out in a dictionary alongside the property name?:
struct User: Equatable {
let name: String
let email: String
let fruits: [Fruit]
}
// Old user
let user = User(name: "Jane", email:"ja#ne.com", fruits: [.banana, .apple])
// Updated user
let updatedUser = User(name: user.name, email: user.email, fruits: [.apple, .peach])
// Looking for help writing a function that can efficiently find the changes + format them into a dictionary for Firebase etc:
let updatedProperties = updatesBetween(old: user, new: updatedUser)
// Output:
//["fruits": ["apple", "peach"]]
Take advantage of the fact that these objects are Equatable and check for their equality right off the bat before proceeding, and then go property by property and look for differences. Keep in mind that arrays care about order so [.apple, .banana] will be different from [.banana, .apple]. If you don't care about order then just sort them both before comparing (this may require additional steps depending on the contents of the array). You can also consider translating the arrays into sets before comparing (if the arrays don't contain duplicates).
func getDifferencesBetween(old: User, new: User) -> [String: Any] {
guard old != new else {
return [:]
}
var differences: [String: Any] = [:]
if old.name != new.name {
differences["name"] = new.name
}
if old.email != new.email {
differences["email"] = new.email
}
if old.fruits != new.fruits {
differences["fruits"] = new.fruits
}
return differences
}
This is how my Realm objects look:
class Restaurant: Object {
#objc dynamic var name: String? = nil
let meals = List<Meal>()
}
class Meal: Object {
#objc dynamic var mealName: String? = nil
let tag = RealmOptional<Int>()
}
I'm trying to fetch all meals that have some tags (I know I can filter all Realm objects of type Meal for specific tags), but the goal is to fetch all Restaurant objects and filter it's Meal child objects based on tag values.
I tried filtering like this:
restaurants = realm.objects(Restaurant.self).filter("meals.#tags IN %#", selectedTags)
but this won't work. Is there a way to filter results based on values in child object list?
To clarify the question, this is an example how filtering should work
for selectedTags = [1, 2, 3]
This is the whole Restaurant model that is saved in Realm.
[Restaurant {
name = "Foo"
meals = [
Meal {
mealName = "Meal 1"
tag = 1
},
Meal {
mealName = "Meal 2"
tag = 2
},
Meal {
mealName = "Meal 7"
tag = 7
}
]
}]
Filtering should return this:
[Restaurant {
name = "Foo"
meals = [
Meal {
mealName = "Meal 1"
tag = 1
},
Meal {
mealName = "Meal 2"
tag = 2
}
]
}]
Here's one possible solution - add a reverse refererence to the restaurant for each meal object
class Restaurant: Object {
#objc dynamic var name: String? = nil
let meals = List<Meal>()
}
class Meal: Object {
#objc dynamic var mealName: String? = nil
let tag = RealmOptional<Int>()
#objc dynamic var restaurant: Restaurant? //Add this
}
then query the meals for that restaurant with the tags you want.
let results = realm.objects(Meal.self).filter("restaurant.name == %# AND tag IN %#", "Foo", [1,2])
LinkingObjects could also be leveraged but it depends on what kind of queries will be needed and what the relationships are between Restaurants and Meals - I am assuming 1-Many in this case.
if you want ALL restaurants, then LinkingObjects is the way to go.
Edit:
Thought of another solution. This will work without adding a reference or an inverse relationship and will return an array of restaurants that have meals with the selected tags.
let selectedTags = [1,2]
let results = realm.objects(Restaurant.self).filter( {
for meal in $0.meals {
if let thisTag = meal.tag.value { //optional so safely unwrap it
if selectedTags.contains(thisTag) {
return true //the tag for this meal was in the list, return true
}
} else {
return false //tag was nil so return false
}
}
return false
})
In short, you cannot do what you are asking. Not within a Realm query (and therefore benefit from update notifications if that is important) at least. No doubt you can make some kind of structure containing what you want though via non-Realm filtering.
To better answer, let's first consider what you're trying to produce as a query result. As you say, your attempt above won't work. But you're trying to filter Restaurants by having some Meals matching some criteria; this is probably achievable, but your resulting query on Restaurant type would then produce a list of Restaurants. Each restaurant would still have a natural property of all its Meals, and would require the same filter applied again to the meals.
It makes sense though to add a function (if you need the search criteria to be dynamic, use a computed property if the filter is always the same tags) to the Restaurant class that produces a view of its Meals matching your criteria.
e.g.
extension Restaurant
{
var importantMeals : Results<Meal>
{
return meals.filter(...)
}
}
So I think there are two options.
Iterate through all Restaurant objects, and add it to a data structure of your own (Set or array) if its importantMeals property is not empty. Then use the same property to produce the meal list when needed. Or you could use a non-Realm filter to produce that query for you. E.g. realm.objects(Restaurant.self).compactMap {$0}.filter { !$0.importantMeals.isEmpty }
Alternatively, filter all Meals according to your criteria (realm.objects(Meal.self).filter(...)). You could then add a LinkingObjects property to your Meal class to make the Set of Restaurants with relevant Meals.
The correct approach will depend on how you want to use the results, but I'd suggest approach 1 will see you right. Note that you might want to sort the results produced by queries before using if order is of any importance to you (e.g. for displaying in UITableView) as there is no guarantee that the order of objects will be the same for each query performed.
I would like to return the native JSON String to store in Realm, since Realm can't store collections of native objects.
Example JSON:
{ "root": { "id":1, "name":"name", "array":["a", "b", "c"] }}
func mapping(map:Map) {
id <- map["id]
name <- map["name"]
array <- map["array"].JSONString // array = "[\"a\", \"b\", \"c\"]"
}
Is this possible?
Same question on github https://github.com/Hearst-DD/ObjectMapper/issues/730
If it helps, Realm can indeed store collections of sub-objects.
class StringObject: Object {
dynamic var string = ""
}
class Root: Object {
dynamic var id = 0
dynamic var name = ""
let array = List<StringObject>()
}
Beyond that, if I understand ObjectMapper correctly, by the time map["array"] is already passed in mapping, it's already been converted from JSON to a Swift object. In which case, you'd need to reserialize it back into JSON yourself.
Given an NSTableView that has an array of structures as its datasource. A user can click on any column heading to sort by that column. The column identifiers match the property names of the properties within the structure.
Given a structure
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
}
and an array of structures
var myArray = [MyStructure]()
The goal is that when a column heading is clicked, use that column's identifier to sort the array of structures by that column identifier/property
With an array of dictionaries, it was easy...
self.myArrayOfDictionaries.sortInPlace {
(dictOne, dictTwo) -> Bool in
let d1 = dictOne[colIdentifier]! as String;
let d2 = dictTwo[colIdentifier]! as String;
return d1 < d2 //or return d1 > d2 for reverse sort
}
The question is how to access the properties of the Structure dynamically, something like
let struct = myArray[10] as! MyStructure //get the 10th structure in the array
let value = struct["col0data"] as! String //get the value of the col0data property
If there is a better way, suggestions would be appreciated.
I should also note that the structure may have 50 properties so this is an effort to reduce the amount of code needed to sort the array by any one of those properties.
edit:
One solution is to change the structure to a class derived from NSObject. Then the properties could be accessed via .valueForKey("some key"). However, I am trying to keep this Swifty.
Maybe I have a solution to your problem. The advantage of this code over your solution is here you don't need to add a subscript method to your struct to create an hardcoded String-Property-Value map via code.
Here's my extension
extension _ArrayType {
func sortedBy(propertyName propertyName: String) -> [Self.Generator.Element] {
let mirrors = self.map { Mirror(reflecting: $0) }
let propertyValues = mirrors.map { $0.children.filter { $0.label == propertyName }.first?.value }
let castedValues = propertyValues.map { $0 as? String }
let sortedArray = zip(self, castedValues).sort { (left, right) -> Bool in
return left.1 < right.1
}.map { $0.0 }
return sortedArray
}
}
Usage
struct Animal {
var name: String
var type: String
}
let animals = [
Animal(name: "Jerry", type: "Mouse"),
Animal(name: "Tom", type: "Cat"),
Animal(name: "Sylvester", type: "Cat")
]
animals.sortedBy(propertyName: "name")
// [{name "Jerry", type "Mouse"}, {name "Sylvester", type "Cat"}, {name "Tom", type "Cat"}]
animals.sortedBy(propertyName: "type")
// [{name "Tom", type "Cat"}, {name "Sylvester", type "Cat"}, {name "Jerry", type "Mouse"}]
Limitations
The worst limitation of this solutions is that it works only for String properties. It can be change to work with any types of property by it must be at compile time. Right now I have not a solution to make it work with any king of property type without changing the code.
I already asked help for the core of the problem here.
I would definitely recommend simply embedding your dictionary into your struct. A dictionary is a much more suitable data structure for 50 key-value pairs than 50 properties – and you've said that this would be an acceptable solution.
Embedding the dictionary in your struct will give you the best of both worlds – you can easily encapsulate logic & you have have easy lookup of the values for each column ID.
You can now simply sort your array of structures like this:
struct MyStructure {
var dict = [String:String]()
init(col0Data:String, col1Data:String) {
dict["col0data"] = col0Data
dict["col1data"] = col1Data
}
}
var myArray = [MyStructure(col0Data: "foo", col1Data: "bar"), MyStructure(col0Data: "bar", col1Data: "foo")]
var column = "col0data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // [MyStructure(dict: ["col0data": "bar", "col1data": "foo"]), MyStructure(dict: ["col0data": "foo", "col1data": "bar"])]
column = "col1data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // MyStructure(dict: ["col0data": "foo", "col1data": "bar"])], [MyStructure(dict: ["col0data": "bar", "col1data": "foo"])
Here's an answer (but not the best answer); use subscripts to return the correct property, and set which property you are sorting by within the array.sort:
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
subscript(key: String) -> String? { //the key will be the col identifier
get {
if key == "col0data" {
return col0data
} else if key == "col1data" {
return col1data
}
return nil
}
}
}
And then here's how the sort works:
let identifier = the column identifier string,say col0data in this case
myArray.sortInPlace ({
let my0 = $0[identifier]! //the identifier from the table col header
let my1 = $1[identifier]!
return my0 < my1
})
If you do not know what types the values of MyStructure can be you will have a hard time comparing them to sort them. If you had a function that can compare all types you can have in MyStructure then something like this should work
struct OtherTypeNotComparable {
}
struct MyStructure {
var col0data = "cat" //name matches the column identifier
var col1data: OtherTypeNotComparable
}
let structures = [MyStructure(), MyStructure()]
let sortBy = "col1data"
func yourCompare(a: Any, b: Any) -> Bool {
return true
}
var expanded : [[(String, Any, MyStructure)]]
= structures.map { s in Mirror(reflecting: s).children.map { ($0!, $1, s) } }
expanded.sortInPlace { (a, b) -> Bool in
let aMatch = a.filter { $0.0 == sortBy }.first!.1
let bMatch = b.filter { $0.0 == sortBy }.first!.1
return yourCompare(aMatch, b: bMatch)
}
source: https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Mirror_Structure/index.html
Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.