-2

I'm working on an iOS project in swift 4. I have an array of dictionarylike

var myArray = [[String: AnyObject]]()

The dictionary contains, name, age created_at(date in string), nationality etc.. And I need to sort the myArray with the ascending order of 'created_at' key which is string format of date. How can I do that? Please help me. I checked all solutions, but i didnt get the sorted array.

my array is like,

(
    {
        name: X
        age: 26
        created_at: "1991-10-10 18:29:05"
        nationality: Indian
    },
    {
        name: Y
        age: 30
        created_at: "2000-05-10 18:29:05"
        nationality: Indian
    },
)
Hilaj S L
  • 1,936
  • 2
  • 19
  • 31
  • Not sure why not search before asking. Because this question is so common and already asked on SO. – TheTiger Jul 30 '19 at 07:21
  • @TheTiger however, it is also not logical to close it as a duplicate with *another* programming language, the question here is about Swift but not Python. I think we should find the right question for marking it as a duplicate. – Ahmad F Jul 30 '19 at 08:50
  • I agree. But still this is very common question for Swift too. Even I answered for the same requirement. – TheTiger Jul 30 '19 at 08:56
  • Now do you think its already asked [here](https://stackoverflow.com/questions/43556728/sorting-of-array-of-dictionary-with-date-swift-3) and [here](https://stackoverflow.com/questions/53928563/how-to-sort-an-array-of-nsdictionary-using-datetime-key-swift-4/) and a duplicate question? – TheTiger Jul 30 '19 at 09:31

6 Answers6

4

You can use this:

let sortedArray = (array as NSArray).sortedArray(using: [NSSortDescriptor(key: "created_at", ascending: true)]) as! [[String:AnyObject]]

just wrap it up with an if let so that you won't force cast.

But I would suggest that you make a model for this dictionary especially because you're gonna be using it with multiple data repetitively.

It is easier to sort an array of objects (something like what i posted below):

EDIT: Sort dates by Date and not String. Code below assumes that your createdAt is already converted to Date objects.

array.sorted(by: { ($0.createdAt).compare($1.createdAt) == .orderedAscending })
Ace Rivera
  • 620
  • 4
  • 14
4

I would suggest to create a model for these but of course you can do it like this as well

array.sort { (firstItem, secondItem) -> Bool in
    let dateFormatter = DateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

    if let dateAString = firstItem["created_at"] as? String,
        let dateBString = secondItem["created_at"] as? String,
        let dateA = dateFormatter.date(from: dateAString),
        let dateB = dateFormatter.date(from: dateBString){
        return dateA.compare(dateB) == .orderedAscending
    }
    return false
}
TheTiger
  • 13,264
  • 3
  • 57
  • 82
valentinv
  • 139
  • 5
0

You can use NSSortDescriptor

let sortedArray = (myArray as NSArray).sortedArray(using: [NSSortDescriptor(key: "created_at", ascending: true)]) as! [[String:AnyObject]]

working example

let dateDICT = [["name" : "a", "date": "1991-10-10 18:29:05"],["name" : "b", "date": "2000-05-10 18:29:05"],["name" : "c", "date": "2000-05-10 18:29:04"]]
print(dateDICT)

Before sorting

[["name": "a", "date": "1991-10-10 18:29:05"], ["name": "b", "date": "2000-05-10 18:29:05"], ["name": "c", "date": "2000-05-10 18:29:04"]]

Here the third element date is less than one second of the second element.

Sorting code

let sortedArray = (dateDICT as NSArray).sortedArray(using: [NSSortDescriptor(key: "date", ascending: true)]) as! [[String:AnyObject]]
print(sortedArray)

Result After sorting

[["name": a, "date": 1991-10-10 18:29:05], ["name": c, "date": 2000-05-10 18:29:04], ["name": b, "date": 2000-05-10 18:29:05]]
Vinu Jacob
  • 393
  • 2
  • 15
0

In Your case the key created_at: "1991-10-10 18:29:05" is your date string so you have to convert your date string into a Date object and get the time interval using Date().timeIntervalSince1970 method. It will return a Double and you can compare your date using this Double value.

Please refer to the below code snippet for more convenience.

//Model For your JSON
struct MyModel:Codable {
    var age:Int?
    var name:String?
    var nationality:String?
    var created_at:String?
}

//MY Array
var dataSource = [MyModel]()


//Helper method to convert the date string into timeInterval
func getMilisecFrom(dateString:String) -> Double{
    let formator = DateFormatter()
    formator.dateFormat = "yyyy-MM-dd HH:mm:ss"
    guard let date = formator.date(from: dateString)
        else {
            return Date().timeIntervalSince1970
    }
    return date.timeIntervalSince1970
}


// method to short the array
func ShortByDate(){
    let shortedData = dataSource.sorted(by: {(getMilisecFrom(dateString: $0.created_at ?? "")) > (getMilisecFrom(dateString: $1.created_at ?? ""))})
    dataSource = shortedData
}
Mohindra Bhati
  • 146
  • 1
  • 8
0

In your case, to get a proper result, you sort based on Date equality logic but not strings. In order to do that, you should get Date from the strings first, by knowing the desired format for the given dates (which is yyyy-MM-dd HH:mm:ss in your case) thus sort it based on that. Example:

let myArray: [[String: AnyObject]] = [["name": "X" as AnyObject, "age": 26 as AnyObject, "created_at": "1991-10-10 18:29:05" as AnyObject],
                                      ["name": "Y" as AnyObject, "age": 30 as AnyObject, "created_at": "2000-05-10 18:29:05" as AnyObject]]

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"

let sorted = myArray.sorted { first, second -> Bool in
    guard let firstCreatedAt = first["created_at"] as? String,
          let firstCreatedAtDate = formatter.date(from: firstCreatedAt),
          let secondCreatedAt = second["created_at"] as? String,
          let secondCreatedAtDate = formatter.date(from: secondCreatedAt) else {
        return false
    }

    return firstCreatedAtDate < secondCreatedAtDate
}

print(sorted)
// [["name": X, "age": 26, "created_at": 1991-10-10 18:29:05], ["created_at": 2000-05-10 18:29:05, "name": Y, "age": 30]]

Since Date conforms to Comparable protocol, you could simply compare between two dates by using > or < operators.

Note: Date Strings comparison will also work. So you can just replace return firstCreatedAtDate < secondCreatedAtDate with return firstCreatedAt < secondCreatedAt.

TheTiger
  • 13,264
  • 3
  • 57
  • 82
Ahmad F
  • 30,560
  • 17
  • 97
  • 143
  • The given date format can be sorted perfectly well as a string so no conversion is needed. – Joakim Danielson Jul 30 '19 at 08:20
  • @JoakimDanielson do you think that it is the good practice for it? Shall we relay on string sorting logic to sort dates? In addition to if the date format has been changed for some reason, it might return incorrect results; Even if we considered that changing the format date is not our problem here so it works fine, I'd say it is still not a proper approach to follow. – Ahmad F Jul 30 '19 at 08:58
  • Yes I think it is a good practice to not do extra work when the circumstances allows it. And your code would fail equally bad if the format changed, wouldn't it? – Joakim Danielson Jul 30 '19 at 09:05
0

You use the sort function of apple. We just only need to compare the value of created_at field. Because when we compare string rely on ASCII to compare.

let result = myArray.sorted(by: { (object1, object2) in bool
    guard let temp1 =  object1["created_at"] as? String,
        let temp2 = object2["created_at"] as? String else { return false }
        return temp1 < temp2
})
TheTiger
  • 13,264
  • 3
  • 57
  • 82