Realm - return array list - ios

I have a realm object called mood and I'm trying to return all values in the field activities as a [String] list where mood = great
My Realm object looks like:
Results<Mood> <0x7fce164851a0> (
[0] Mood {
mood = Great;
comment = test;
activities = Friends;
time = 8:06 PM;
date = 4/10/18;
symptom = down;
},
[1] Mood {
mood = Sad;
comment = ;
activities = Date;
time = 6:22 PM;
date = 4/14/18;
symptom = up;
},
[2] Mood {
mood = Sad;
comment = ;
activities = Date;
time = 7:40 PM;
date = 4/14/18;
symptom = Night;
},
[3] Mood {
mood = Sad;
comment = ;
activities = Date;
time = 7:41 PM;
date = 4/14/18;
symptom = Dry;
},
[4] Mood {
mood = Awful;
comment = this is an example data for the app!;
activities = Date;
time = 2:17 PM;
date = 4/15/18;
symptom = Lost;
}
)
and I want to return a list like
["down"]
because thats the activity where mood = great etc
I have tried:
func getGreatMoodActivity() -> [String]{
let greaMoodActivity = Set(realm.objects(Mood.self).filter("mood = 'Great'") as! [String])
return getGreatMoodActivity()
}
But this crashes with this error
Could not cast value of type 'RealmSwift.Results'
(0x10f520810) to 'NSArray' (0x10ce35008).
Is there a way to fix this?

The filter operation will keep filter the Mood objects matching your given criteria, so its return value will be of type Results<Mood> which you obviously can't convert in coercion to Array<String>.
It seems that you are actually trying to map the Mood objects matching your filter criteria to their symptom property, which is of type String. To achieve your goals, you need to call map after filtering the Mood objects.
Moreover, your return value is completely flawed, you want to return the array stored in the variable greatMoodActivity and not call your function recursively.
func getGreatMoodActivity() -> [String]{
let greatMoodActivity = realm.objects(Mood.self).filter("mood = 'Great'").map({$0.symptom})
return Array(greatMoodActivity)
}

Related

How to find sum for each category in an array?

I have an array that contains multiple items. This is a Realm array. It contains data in the following structure:
Results<Entry> <0x7ff04840e1a0> (
[0] Entry {
name = Salary;
amount = 40000;
date = 2020-03-18 16:00:00 +0000;
category = Main Incomes;
entryType = Income;
},
[1] Entry {
name = Diff;
amount = -500;
date = 2020-04-18 16:00:00 +0000;
category = Misc;
entryType = Expense;
},
[2] Entry {
name = Cat Food;
amount = -399;
date = 2020-04-18 16:00:00 +0000;
category = Animals;
entryType = Expense;
},
[3] Entry {
name = Fish Food;
amount = -599;
date = 2020-04-18 16:00:00 +0000;
category = Animals;
entryType = Expense;
}
)
What I am trying to achieve is to make another array that will 'pivot' totals for each category. So it can work as a pivot table in Excel.
The desired output is an array that will contain totals for each category:
[0] X-Array {
category = Main Incomes;
amount = XXXX;
},
[1] X-Array {
category = Animals;
amount = XXXX;
And so on...
I'm fairly new to this fancy one-liners like .map and .reduce and other Swift's array management sugar, so would very much appreciate the advice!
Thank you!
P.S. I plan to do the same thing with total expenses and incomes in order to calculate closing balance.
I think use Dictionary is easier to do categorize and later you can convert it to array or anything you want
var dict:[String:Double] = [:]
list.forEach {
if let current = dict[$0.category]{
dict[$0.category] = current + $0.amount
}else{
dict[$0.category] = $0.amount
}
}
print(dict)
I think the question is asking how to get the sum for each category, Income, Expense etc. If so, here's how it's done using the .sum function on the results.
let totalIncome = realm.objects(AccountClass.self)
.filter("entryType == 'Income'")
.sum(ofProperty: "amount") as Int
let totalExpense = realm.objects(AccountClass.self)
.filter("entryType == 'Expense'")
.sum(ofProperty: "amount") as Int
print("Total Income: \(totalIncome)")
print("Total Expense: \(totalExpense)")
and the output for your example data is
Total Income: 40000
Total Expense: 1498
If you want a pivot table, you could just add the amounts to an array - if you need the labels, use a tuple to store them in an array like this
let i = ("Income", totalIncome)
let e = ("Expense", totalExpense)
let tupleArray = [i, e]
for account in tupleArray {
print(account.0, account.1)
}
Keep in mind that a Results object is not an array but has some array-like functions. Results are live updating so as the underlying data in Realm changes, the Results change along with it.

Realm queries to extract data

I have 2 Realm Models:
class CourseModel: Object {
dynamic var coursename = ""
dynamic var par3field = 0
dynamic var par4field = 0
dynamic var par5field = 0
let scoreModels: List<ScoresModel> = List<ScoresModel>()
override internal static func primaryKey() -> String? { return "coursename" }
}
class ScoresModel: Object {
dynamic var dateplayed = ""
var courseModel: CourseModel? {
return linkingObjects(CourseModel.self, forProperty: "scoreModels").first
}
}
The app user will first add a new course for which I use CourseModel. As the user plays a course they enter scores for that course, for which I use ScoresModel, hence the primary key 'coursename'.
I query the CourseModel with
let realm = try Realm()
let results = realm.objects(CourseModel)
return results
and it produces the following result
Results<CourseModel> (
[0] CourseModel {
coursename = First Course;
par3field = 4;
par4field = 10;
par5field = 4;
scoreModels = RLMArray <0x797a36d0> (
[0] ScoresModel {
dateplayed = Apr 5, 2016; },
[1] ScoresModel {
dateplayed = Mar 3, 2016; }
);
},
[1] CourseModel {
coursename = Second Course;
par3field = 4;
par4field = 10;
par5field = 4;
scoreModels = RLMArray <0x7a046f40> (
[0] ScoresModel {
dateplayed = Apr 5, 2016; }
);
}
)
The ScoresModel produces a similar result but without the CourseModel data.
The ScoresModel has a lot of data in it, I only showed 'dateplayed' here to keep it short.
My question is this; when I've extracted the data from Realm how can I access a particular field to work with that data, i.e. how do I get the par5field data to do calculations with it, and also the 2nd question how do I get to the scoreModels data, for example 'dateplayed' to list the dates in a table for example?
When you perform a query against Realm, the results are returned in a Results object that behaves exactly like an array. So you need to iterate through each object to access the properties you want for each one.
To answer your first question, to access the par5field property (From just the first object):
let firstObject? = results.first
let par5field = firstObject.par5field
// Do calculations with it
For your second question, scoreModels is just a standard array object, so you can just insert the values it into a table view as you would a standard Array object.
If you wanted to list ALL of the ScoreModel objects, regardless of which CourseModel objects they belong to, you can perform a Realm query to get them directly.
let realm = try! Realm()
let results = realm.objects(ScoreModel)
return results

Sort value in dictionary that resides within an array

I have an Array: var messageArray = [AnyObject]() and in that Array there is a single tuple that contains Dictionaries with 10 key/value paires (9 of them not important for the sort): var messageDetailDict = [String: AnyObject]()
Getting and setting those values all work correctly, however now I want to sort the Array by 1 of the values (not keys) of the Dictionary.
Example -> The Array has a tuple containing several Dictionaries:
The key in the Dictionary (which is the first element in the Array) is: 'ReceivedAt' which has a value of 21-03-2015
The key in the Dictionary (which is the second element in the Array) is: 'ReceivedAt' which has a value of 20-03-2015
The key in the Dictionary (which is the third element in the Array) is: 'ReceivedAt' which has a value of 15-03-2015
Now the Array should be sorted so that the values of 'ReceivedAt' will be sorted from earliest date, to the last date.
Hope this makes sense, but it's a bit difficult to explain. Thanks!
EDIT >>>>>
This is the println(messageArray) output:
[(
{
ConversationId = "94cc96b5-d063-41a0-ae03-6d1a868836fb";
Data = "Hello World";
Id = "eeb5ac08-209f-4ef0-894a-72e77f01b80b";
NeedsPush = 0;
ReceivedAt = "/Date(1439920899537)/";
SendAt = "/Date(1436620515000)/";
Status = 0;
},
{
ConversationId = "94cc96b5-d063-41a0-ae03-6d1a868836fb";
Data = "Hello World";
Id = "86b8766d-e4b2-4ef6-9112-ba9193048d9d";
NeedsPush = 0;
ReceivedAt = "/Date(1439921562909)/";
SendAt = "/Date(1436620515000)/";
Status = 0;
}
)]
And the received date is converted to a string with the following method (I do think however this is not important, as it is a time interval, and therefore OK to sort):
func getTimeStampFromAPIValue(dateTimeReceived: String) -> String {
let newStartIndex = advance(dateTimeReceived.startIndex, 6)
let newEndIndex = advance(dateTimeReceived.endIndex, -2)
let substring = dateTimeReceived.substringWithRange(newStartIndex..<newEndIndex) // ell
let receivedAtValueInInteger = (substring as NSString).doubleValue
let receivedAtValueInDate = NSDate(timeIntervalSince1970:receivedAtValueInInteger/1000)
//format date
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd-MM-yy hh:mm"
var dateString = dateFormatter.stringFromDate(receivedAtValueInDate)
return dateString
}
Since the values of ReceivedAt are timestamps as strings you could apply the following algorithm:
var sortedArray = messageArray.sorted { (dict1, dict2) in
// Get the ReceivedAt value as strings
if let date1String = dict1["ReceivedAt"] as? String,
let date2String = dict2["ReceivedAt"] as? String {
// Compare the date strings to find the earlier of the two
return date1String.compare(date2String) == .OrderedAscending
}
// Couldn't parse the date, make an assumption about the order
return true
}
Try this, change OrderedAscending with OrderedDescending if need in inverse order
messageArray.sortInPlace {
($0["ReceivedAt"] as! NSDate).compare($1["ReceivedAt"] as! NSDate) == .OrderedAscending
}

One to many relations Parse

I've looked all over but I can't find an answer to this question.
I am saving a Podcasts and its related episodes to Parse but the following code only saves 1 episode and the podcast (I suppose every entry found in the for loop resets currentP and only the last value found gets saved).
let currentP = PFObject(className: self.podcastClass)
currentP["user"] = PFUser.currentUser()
currentP["name"] = name
currentP["artist"] = artist
currentP["summary"] = summary
currentP["feedURL"] = feedURL
currentP["artworkURL"] = artworkURL
currentP["artwork"] = artwork
currentP["date"] = date
let episodesToParse = PFObject(className: self.episodesClass)
for episode in episodes {
episodesToParse["showDate"] = episode.date
episodesToParse["title"] = episode.title
episodesToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodesToParse["showNotes"] = episode.summary
episodesToParse["localPath"] = ""
episodesToParse["isDownloaded"] = "no"
episodesToParse["parent"] = currentP
}
episodesToParse.saveInBackground()
If I use something like episodesToParse.addObject(episode.date, forKey: "showDate") then the following error is returned:
[Error]: invalid type for key showDate, expected date, but got array (Code: 111, Version: 1.8.1)
I'm not sure how to proceed. What I want is currentP to be saved as it is and all its episodes to be saved in a different class with a relationship to its parent (Podcast). I found tons of ways to do this if you're adding one episode at a time but not a whole bunch of them (I would like to be able to save 500 instance of episodesToParseat once.
Thanks for your help.
Your problem is, that you save the episodesToParse after the loop. You have to move the episodesToParse.saveInBackground() inside the loop so that everytime the loop sets the properties of the episode the episode gets updated:
for episode in episodes {
episodesToParse["showDate"] = episode.date
episodesToParse["title"] = episode.title
episodesToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodesToParse["showNotes"] = episode.summary
episodesToParse["localPath"] = ""
episodesToParse["isDownloaded"] = "no"
episodesToParse["parent"] = currentP
//Inside
episodesToParse.saveInBackground()
}
Or you could use PFObject.saveAllInBackground to save all objects:
var episodesToSave[PFObject] = []
for episode in episodes {
var episodeToParse
episodeToParse["showDate"] = episode.date
episodeToParse["title"] = episode.title
episodeToParse["downloadURL"] = episode.enclosures[0].valueForKey("url") as? String
episodeToParse["showNotes"] = episode.summary
episodeToParse["localPath"] = ""
episodeToParse["isDownloaded"] = "no"
episodeToParse["parent"] = currentP
//Add to episode-array
episodesToSave.append(episodesToParse)
}
//Save all objects in the array
PFObject.saveAllInBackground(episodesToSave)

Searching Large Array Performance in Swift

I have a a JSON file of all the airports in the world that I am trying to search in a function but it is very slow and am trying to improve its performance. There are 9500 entries in this JSON file( I would have used an web API but couldnt find a free one so I am using this JSON file). My array is like like:
var data = [Dictionary<String, AnyObject>]
Here is what one of the dictionaries looks like:
[DisplayText: YYZ, airportObject: {
0 = 193;
Altitude = 569;
City = Toronto;
Country = Canada;
DST = A;
IATA = YYZ;
ICAO = CYYZ;
Latitude = "43.677223";
Longitude = "-79.630556";
Name = "Lester B Pearson Intl";
TZ = "America/Toronto";
UTC = "-5";
}, DisplaySubText: Lester B Pearson Intl]
The goal is to have a TextField autocomplete with the name of the airport as the user types in the field. I wrote a function to filter these entries with the user's input. It works, however it is very slow and takes about 1 second per letter typed, and the cpu goes to 50% when typing.
Here is the function
func applyFilterWithSearchQuery(filter : String) -> [Dictionary<String, AnyObject>]
{
//let predicate = NSPredicate(format: "DisplayText BEGINSWITH[cd] \(filter)")
var lower = (filter as NSString).lowercaseString
var filteredData = data.filter({
if let match : AnyObject = $0["DisplayText"]{
//println("LCS = \(filter.lowercaseString)")
return (match as NSString).lowercaseString.hasPrefix((filter as NSString).lowercaseString)
}
else{
return false
}
})
return filteredData
}
How can I improve the performance of this function?
I ended up taking all the comments into account and came up with this.
I changed the logic so it only only runs applyFilterWithSearchQuery after 2 characters are typed instead of 1.
Cached the results after each result so only the sublist was searched
Changed the function to use NSPredicate as per below:
func applyFilterWithSearchQuery(filter : String) -> [Dictionary<String, AnyObject>]
{
let predicate = NSPredicate(format: "DisplayText BEGINSWITH[cd] %#", filter)
let filteredData = (self.data as NSArray).filteredArrayUsingPredicate(predicate!)
return filteredData as [Dictionary<String, AnyObject>]
}
Overall CPU time dropped down to 1% . Thanks to all those that had suggestions

Resources