sort array of multiple inheritance objects by date - ios

I'm having an array array: [AnyObject] which contain a number of different inheritance objects for instance Dog or Cat. I would like to sort these by date how can i do this?
The classes could for instance look like this
class Pet {
var: NSDate = NSDate()
}
class Dog: Pet {
}
class Cat: Pet {
}
So far i've created this class, which is suppose to handle the sorting and return a new array with the objects sorted
class FavoriteSorter {
func sortOrganizationsByDistanceFromLocation(orgArray:[AnyObject]) -> [AnyObject] {
let sortedArray = orgArray.sort {
}
return sortedArray
}
}

Try this out:
class Pet {
var myDate = NSDate()
var title = String()
}
class Dog: Pet {
}
class Cat: Pet {
}
var dog1 = Dog()
dog1.myDate = NSDate()
dog1.title = "Dog 1"
var dog2 = Dog()
dog2.myDate = NSDate(timeIntervalSinceNow:NSTimeInterval(10))
dog2.title = "Dog 2"
var cat1 = Cat()
cat1.myDate = NSDate(timeIntervalSinceNow:NSTimeInterval(20))
cat1.title = "Cat 1"
var cat2 = Cat()
cat2.myDate = NSDate(timeIntervalSinceNow:NSTimeInterval(15))
cat2.title = "Cat 2"
var arrayOfObjs = [cat1, cat2, dog1, dog2]
// Sorting array
func sortOrganizationsByDistanceFromLocation(array:[AnyObject]) -> [AnyObject] {
return array.sort{ ($0.0 as! Pet).myDate.compare(($0.1 as! Pet).myDate) == NSComparisonResult.OrderedAscending }
}
let sortedArray = sortOrganizationsByDistanceFromLocation(arrayOfObjs) as! [Pet]
print(sortedArray[0].title); // Prints Dog 1
print(sortedArray[1].title); // Prints Dog 2
print(sortedArray[2].title); // Prints Cat 2
print(sortedArray[3].title); // Prints Cat 1

class FavoriteSorter {
func sortOrganizationsByDistanceFromLocation(array:[AnyObject]) -> [AnyObject] {
return array.sort{ ($0.0 as! Pet).date.compare(($0.1 as! Pet).date) == NSComparisonResult.OrderedAscending }
}
}
As mentioned by vacawama it would be better to pass [Pet] instead of [AnyObject] to avoid crashing your app in case you pass another object type:
class FavoriteSorter {
func sortOrganizationsByDistanceFromLocation(array:[Pet]) -> [Pet] {
return array.sort{ $0.date.compare($1.date) == NSComparisonResult.OrderedDescending }
}
}
class Pet {
var date = NSDate()
}
class Dog: Pet { }
class Cat: Pet { }
let dog = Dog()
let cat = Cat()
let date01 = NSDate()
let date02 = NSDate().dateByAddingTimeInterval(3600)
dog.date = date01
cat.date = date02
let pets = [dog,cat]
pets.description // "[Dog, Cat]"
let sortedPets = FavoriteSorter().sortOrganizationsByDistanceFromLocation(pets)
sortedPets.description // "[Cat, Dog]"

Related

Is it possible to use dynamic variable for a class member variable

I have a class like
class Person {
var address:String
var number:String
var houseNo:String
var licenceNo:String
....
}
let jone = Person()
jone.number = "123456"
So in this i need to initialize the variable of person class one by one. And i have approx 30 variables in person class.
Is not there s simple way to do this ?
like I have all the keys coming from backend like "number = 123456". Is not there a way that i run a for loop and use something like.
for key in keys {
john."\(key)" = dict[key]
}
Is not there a way to shorten this lengthy procedure ?
You can try out this code
extension NSObject{
// fetch all class varible
func property() -> Mirror.Children {
return Mirror(reflecting: self).children
}
func propertyList() {
for (name, value) in property() {
guard let name = name else { continue }
print("\(name): \(type(of: value)) = '\(value)'")
}
}
}
Your class, set value like below code it's helpfull
class Person: NSObject {
var address:String = ""
var number:String = ""
var houseNo:String = ""
var licenceNo:String = ""
init(with response: [String: AnyObject]) {
for child in self.property() {
if let key = child.label, let value = response[key] {
self.setValue(value, forKey: key)
}
}
}
}
person.propertyList()
// Display all class property list
address: String = ''
number: String = ''
houseNo: String = ''
licenceNo: String = ''
Why don't use a Person method for load the backend response into a new Person object?
Somethind like that:
let jone = Person()
jone.loadDataFrom(response)
Or use a Person static method
let jone = Person.loadDataFrom(response)
static func loadDataFrom(response:Object) -> Person {
let p = Person()
...
set response data
...
return p
}

object list always print nil in swift

In my object Dish xxx.Dish, I want to access the Choice class price and name to display but I failed. dish data load from web API and I tested data loaded success full and put the data to the object dish and it return the object list to viewcontroller to load tableview.
Output of printed console
Optional([xxx.Dish, xxx.Dish])
and in the dish class before append optionList?.append(_obj)
xxx.DishOption
Anyone helps me how can I do that .. I am new to swift and is it right way to implement? Please suggest me?
class Dish {
let dishId : String
var optionList : [DishOption]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let dishId = resposne["dishId"] as? String else {
return nil
}
self.dishId = dishId
if let objs = resposne["options"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = DishOption(fromAPIResponse: obj){
optionList?.append(_obj)
}
}
}
}
class DishOption {
let optionId : String
var choiceList : [Choice]?
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let optionId = resposne["optionId"] as? String else {
return nil
}
self.optionId = optionId
if let objs = resposne["choices"] as? [[String: AnyObject]]{
for obj in objs {
if let _obj = Choice(fromAPIResponse: obj){
choiceList?.append(_obj)
}
}
}
}
}
class Choice{
let choiceId : String
let name : String
let price : String
init?(fromAPIResponse resposne : Dictionary<String,AnyObject>) {
guard let choiceId = resposne["choiceId"] as? String ,
let name = resposne["name"] as? String,
let price = resposne["price"] as? String else {
return nil
}
self.choiceId = choiceId
self.name = name
self.price = price
}
}
UPDATE:
var dishMenuList = [Dish]()
guard let objs = json["menu_list"] as? [[String : AnyObject]] else {
return
}
for obj in objs {
if let _obj = Dish(fromAPIResponse: obj){
print(_obj.optionList) //always print nil
if let options = _obj.optionList {
for data in options {
print(data.displayAsButton)
}
}
dishMenuList.append(_obj)
}
}
From what I can see, you are never initializing both the optionList and choiceList arrays. It would be better to initialize them as empty arrays:
class Dish {
let dishId : String
var optionList = [DishOption]()
...
optionList.append(_obj)
This is the reason that you cannot see any options. Since the optionList is still nil, the line optionList?.append(_obj) does not execute.

Generating an array of keyed arrays: More efficient way?

I want to sort an array of performers so that they are grouped by the first character of their first name. So for example 'A' in the following output, is for a collection of performers who's first name starts with 'A'.
[
"A"[Performer,Performer,Performer,Performer]
"B"[Performer,Performer,Performer]
"C"[Performer,Performer,Performer]
"D"[Performer,Performer,Performer]
"F"[Performer,Performer,Performer]
"M"[Performer,Performer,Performer]
... etc
]
I have achieved it but I'm hoping there is a more trivial way to do it. The following is how I achieved it.
class Performer {
let firstName: String
let lastName: String
let dateOfBirth: NSDate
init(firstName: String, lastName: String, dateOfBirth: NSDate) {
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
}
}
private var keyedPerformers: [[String: [Performer]]]?
init() {
super.init()
let performers = generatePerformers()
let sortedPerformers = performers.sort { $0.0.firstName < $0.1.firstName }
keyedPerformers = generateKeyedPerformers(sortedPerformers)
}
//'sortedPerformers' param must be sorted alphabetically by first name
private func generateKeyedPerformers(sortedPerformers: [Performer]) -> [[String: [Performer]]] {
var collectionKeyedPerformers = [[String: [Performer]]]()
var keyedPerformers = [String: [Performer]]()
var lastLetter: String?
var collection = [Performer]()
for performer in sortedPerformers {
let letter = String(performer.firstName.characters.first!)
if lastLetter == nil {
lastLetter = letter
}
if letter == lastLetter {
collection.append(performer)
} else {
keyedPerformers[lastLetter!] = collection
collectionKeyedPerformers.append(keyedPerformers)
keyedPerformers = [String: [Performer]]()
collection = [Performer]()
}
lastLetter = letter
}
return collectionKeyedPerformers.sort { $0.0.keys.first! < $0.1.keys.first! }
}
First of all, since you have an (sorted) array of section index titles it's not necessary to use an array of dictionaries. The most efficient way is to retrieve the array of Performers from the dictionary by key.
Add a lazy instantiated variable initialLetter in the Performer class. I added also the description property to get a more descriptive string representation.
class Performer : CustomStringConvertible{
let firstName: String
let lastName: String
let dateOfBirth: NSDate
init(firstName: String, lastName: String, dateOfBirth: NSDate) {
self.firstName = firstName
self.lastName = lastName
self.dateOfBirth = dateOfBirth
}
lazy var initialLetter : String = {
return self.firstName.substringToIndex(self.firstName.startIndex.successor())
}()
var description : String {
return "\(firstName) \(lastName)"
}
To create the performers use an array rather than a simple string. The "missing" letters are already omitted.
private let createIndexTitles = ["A","B","C","D","E","F","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
The function to create the dummy instances pre-sorts the arrays of performers by initialLetter and then by lastName
private func generatePerformers() -> [Performer] {
var performers = [Performer]()
for i in 0...99 {
let x = i % createIndexTitles.count
let letter = createIndexTitles[x]
performers.append(Performer(firstName: "\(letter)", lastName: "Doe", dateOfBirth: NSDate()))
}
return performers.sort({ (p1, p2) -> Bool in
if p1.initialLetter < p2.initialLetter { return true }
return p1.lastName < p2.lastName
})
}
Now create an empty array of strings for the section title indexes
private var sectionIndexTitles = [String]()
The function to create the keyed performers follows a simple algorithm : If the key for initialLetter doesn't exist, create it. Then append the performer to the array. At the end assign the sorted keys of the dictionary to sectionIndexTitles, the elements of the keyed arrays are already sorted.
private func generateKeyedPerformers(sortedPerformers: [Performer]) -> [String: [Performer]] {
var keyedPerformers = [String: [Performer]]()
for performer in sortedPerformers {
let letter = performer.initialLetter
var keyedPerformer = keyedPerformers[letter]
if keyedPerformer == nil {
keyedPerformer = [Performer]()
}
keyedPerformer!.append(performer)
keyedPerformers[letter] = keyedPerformer!
}
sectionIndexTitles = Array(keyedPerformers.keys).sort { $0 < $1 }
return keyedPerformers
}
Now test it
let performers = generatePerformers()
let keyedPerformers = generateKeyedPerformers(performers)
print(sectionIndexTitles , keyedPerformers)
In the (presumably) table view use sectionIndexTitles as the section array and get the performer arrays by key respectively.

RealmSwift: How to create To-One Relationships properly?

Let's assume I have:
class Dog: Object {
dynamic var race = ""
dynamic var name = ""
override static func primaryKey() -> String? {
return "race"
}
}
class Person: Object {
dynamic var name = ""
dynamic var address = ""
dynamic var dog: Dog?
override static func primaryKey() -> String? {
return "name"
}
}
First I create a Dog and save it:
let dog = Dog()
dog.race = "Dalmatian"
try! realm.write {
realm.add(dog, update: true)
}
Now I create a Person in a different class. The docs are quite a bit unclear about this scenario. Do I need to save changes for the Dog first before creating the relationship?:
let person = Person()
person.name = "Jim"
// retrieve dog from realm:
if let dog = realm.objectForPrimaryKey(Dog.self, key: "Dalmatian") {
dog.name = "Rex" // Owner gives dog a new name
// Question:
// Saving changes to Rex: is this step neccessary?
try! realm.write {
realm.add(dog, update: true)
}
person.dog = dog
}
try! realm.write {
realm.add(person, update: true)
}
No, and it will cause a crash
if let dog = realm.objectForPrimaryKey(Dog.self, key: "Dalmatian") {
dog.name = "Rex" // Owner gives dog a new name
person.dog = dog
}
if you want update the dog's name, write like this:
if let dog = realm.objectForPrimaryKey(Dog.self, key: "Dalmatian") {
try! realm.write({
dog.name = "Rex"
})
person.dog = dog
}
see more: Realm.io/updating-objects
You can setup a whole object graph as unmanaged objects and persist them all by one call. So you don't need to persist the Dog first and retrieve it again to be able to use it in a relationship.
let dog = Dog()
dog.race = "Dalmatian"
let person = Person()
person.name = "Jim"
person.dog = dog
try! realm.write {
realm.add(person, update: true)
}

Realm append data to a type List<t>

I'm trying to go through data and save it in my model, however whatever i do i keep getting the following error: Can't mutate a persisted array outside of a write transaction.. What am i doing wrong? i'm appending each match to the league however it does not seem to work?
realm model
class League: Object {
dynamic var id: Int = 0
dynamic var name: String? = ""
var matches = List<Match>()
override class func primaryKey() -> String {
return "id"
}
}
class Match: Object {
dynamic var matchId: Int = 0
dynamic var date: NSDate?
dynamic var homeName: String? = ""
dynamic var awayName: String? = ""
dynamic var awayScore: Int = 0
dynamic var homeScore: Int = 0
dynamic var leagueName: String? = ""
dynamic var homeLogo: NSData?
dynamic var awayLogo: NSData?
}
Code
for (_, item) in result {
if let leagueId = item["league"].int,
let leagueName = item["name"].string,
let allMatches = item["matches"].array {
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if let matchId = match["matchId"].int,
let tournament = match["tournament"].string,
let homeTeam = match["homeName"].string,
let awayTeam = match["awayName"].string,
let homeScore = match["homeScore"].int,
let awayScore = match["awayScore"].int,
let homeLogo = match["homeLogo"].string,
let awayLogo = match["awayLogo"].string,
let date = match["date"].string {
if let awayLogoUrl = NSURL(string: awayLogo),
let homeLogoUrl = NSURL(string: homeLogo) {
if let awayLogoData = NSData(contentsOfURL: awayLogoUrl),
let homeLogoData = NSData(contentsOfURL: homeLogoUrl) {
let matchObject = Match()
matchObject.matchId = matchId
matchObject.leagueName = tournament
matchObject.homeName = homeTeam
matchObject.awayName = awayTeam
matchObject.homeScore = homeScore
matchObject.awayScore = awayScore
matchObject.homeLogo = homeLogoData
matchObject.awayLogo = awayLogoData
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
formatter.timeZone = NSTimeZone(abbreviation: "CET")
matchObject.date = formatter.dateFromString(date)!
league.matches.append(matchObject)
}
}
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
}
}
}
}
Simplifying your code to show only the general structure helps to reveal the issue:
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if … {
let matchObject = Match()
…
league.matches.append(matchObject)
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
}
This is the sequence of events: You start by creating a standalone, unpersisted League instance, league. For each match, you create a standalone, unpersisted Match instance and append it to league.matches. You then create a write transaction, and save league to the Realm. From this point, league is no longer standalone, and may only be modified within a write transaction. On the next iteration of the loop you create another Match instance and attempt to append it to league.matches. This throws since league is persisted and we're not in a write transaction.
One solution here would be to restructure the code so you only save league to the Realm once, like so:
let league = League()
league.name = leagueName
league.id = leagueId
for match in allMatches {
if … {
let matchObject = Match()
…
league.matches.append(matchObject)
}
}
print(league)
try! realm.write {
realm.add(league, update: true)
}
The post above is wrong, you can't mutate a List of a realm model outside of a write block.
the correct way would be:
try! realm.write {
league.matches.append(matchObject)
realm.add(league, update: true)
}
At least this is the usual way with Realm Version 0.98

Resources