Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift 2 iOS - get file list sorted by creation date - more concise solution?

In my code, which works, I am returning a [String]? containing the names of the files (lastPathComponent) stored in /Documents/ - ordered by the date last modified.

I believe that I am probably using too many steps and am looking for advice here re how to reduce the code.

In order to achieve the required result currently - I am creating two intermediate dictionaries: var attributesDictionary: [String : AnyObject]? and var urlDictionary = [NSURL:NSDate](). Looping through the initial [NSURL] I am using two steps - .resourceValuesForKeys initializes attributesDictionary. I then populate urlDictionary so that it contains the URL and the value for the key NSURLContentModificationDateKey.

I feel fairly certain that there should be a way to achieve this result without creating urlDictionary and attributesDictionary and without the need for the loop. Perhaps from urlArray directly. Here is my current code:

EDIT: do{}s were not required as pointed out by Arthur Gevorkyan in the first comment.

func getFileList() -> [String]? {
    let directory = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)[0]
    let properties = [NSURLLocalizedNameKey, NSURLCreationDateKey, NSURLContentModificationDateKey, NSURLLocalizedTypeDescriptionKey]

    // no catch required - contentsOfDirectoryAtURL returns nil if there is an error
    if let urlArray = try? NSFileManager.defaultManager().contentsOfDirectoryAtURL(directory, includingPropertiesForKeys: properties, options:NSDirectoryEnumerationOptions.SkipsHiddenFiles) {
        var attributesDictionary: [String:AnyObject]?
        var dateLastModified: NSDate
        var urlDictionary = [NSURL:NSDate]()

        for URLs in urlArray {
            // no catch required - resourceValuesForKeys returns nil if there is an error
            attributesDictionary = try? URLs.resourceValuesForKeys(properties)
            dateLastModified = attributesDictionary?[NSURLContentModificationDateKey] as! NSDate
            urlDictionary[URLs] = dateLastModified
        }
        // this approach to sort is used because NSDate cannot be directly compared with </>
        return urlDictionary.filter{$0 != nil}.sort{$0.1.compare($1.1) == NSComparisonResult.OrderedDescending }.map{$0.0}.map{$0.lastPathComponent!}
    } else {
        return nil
    }
}
like image 565
simons Avatar asked Oct 09 '15 07:10

simons


4 Answers

A possible solution:

if let urlArray = try? NSFileManager.defaultManager().contentsOfDirectoryAtURL(directory,     includingPropertiesForKeys: properties, options:.SkipsHiddenFiles) {      return urlArray.map { url -> (String, NSTimeInterval) in         var lastModified : AnyObject?         _ = try? url.getResourceValue(&lastModified, forKey: NSURLContentModificationDateKey)         return (url.lastPathComponent!, lastModified?.timeIntervalSinceReferenceDate ?? 0)     }     .sort({ $0.1 > $1.1 }) // sort descending modification dates     .map { $0.0 } // extract file names  } else {     return nil } 

The array of URLs is mapped to an array of (lastPathComponent, lastModificationDate) tuples first, then sorted according to the last modification date, and finally the path name extracted.

The attributesDictionary can be avoided by using getResourceValue(_ : forKey) to retrieve only the last modification date.

Update for Swift 3:

let directory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! if let urlArray = try? FileManager.default.contentsOfDirectory(at: directory,                                                                includingPropertiesForKeys: [.contentModificationDateKey],                                                                options:.skipsHiddenFiles) {      return urlArray.map { url in             (url.lastPathComponent, (try? url.resourceValues(forKeys: [.contentModificationDateKey]))?.contentModificationDate ?? Date.distantPast)         }         .sorted(by: { $0.1 > $1.1 }) // sort descending modification dates         .map { $0.0 } // extract file names  } else {     return nil } 
like image 120
Martin R Avatar answered Sep 21 '22 20:09

Martin R


Swift 5.0. Simple extension based on the previous answers:

extension FileManager {      enum ContentDate {         case created, modified, accessed          var resourceKey: URLResourceKey {             switch self {             case .created: return .creationDateKey             case .modified: return .contentModificationDateKey             case .accessed: return .contentAccessDateKey             }         }     }      func contentsOfDirectory(atURL url: URL, sortedBy: ContentDate, ascending: Bool = true, options: FileManager.DirectoryEnumerationOptions = [.skipsHiddenFiles]) throws -> [String]? {          let key = sortedBy.resourceKey          var files = try contentsOfDirectory(at: url, includingPropertiesForKeys: [key], options: options)          try files.sort {              let values1 = try $0.resourceValues(forKeys: [key])             let values2 = try $1.resourceValues(forKeys: [key])              if let date1 = values1.allValues.first?.value as? Date, let date2 = values2.allValues.first?.value as? Date {                  return date1.compare(date2) == (ascending ? .orderedAscending : .orderedDescending)             }             return true         }         return files.map { $0.lastPathComponent }     } } 
like image 43
Jovan Stankovic Avatar answered Sep 21 '22 20:09

Jovan Stankovic


Swift 5.0 Uses Filter and Sort by ModificationDate

let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
guard let directoryURL = URL(string: paths.path) else {return}
do {
   let contents = try
   FileManager.default.contentsOfDirectory(at: directoryURL, 
          includingPropertiesForKeys:[.contentModificationDateKey], 
          options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants])
       .filter { $0.lastPathComponent.hasSuffix(".swift") }
       .sorted(by: {
           let date0 = try $0.promisedItemResourceValues(forKeys:[.contentModificationDateKey]).contentModificationDate!
           let date1 = try $1.promisedItemResourceValues(forKeys:[.contentModificationDateKey]).contentModificationDate!
           return date0.compare(date1) == .orderedDescending
        })
  
    // Print results    
    for item in contents {
        guard let t = try? item.promisedItemResourceValues(forKeys:[.contentModificationDateKey]).contentModificationDate 
            else {return}
        print ("\(t)   \(item.lastPathComponent)")
    }
} catch {
    print (error)
}
like image 25
Richard Legault Avatar answered Sep 19 '22 20:09

Richard Legault


Swift 3 Code With Complete Solution: Based on @ingconti's answer. This method return list of item names from provided URL path.

func filesSortedList(atPath: URL) -> [String]? {

    var fileNames = [String]()
    let keys = [URLResourceKey.contentModificationDateKey]

    guard let fullPaths = try? FileManager.default.contentsOfDirectory(at: atPath, includingPropertiesForKeys:keys, options: FileManager.DirectoryEnumerationOptions.skipsHiddenFiles) else {
        return [""]
    }

    let orderedFullPaths = fullPaths.sorted(by: { (url1: URL, url2: URL) -> Bool in
        do {
            let values1 = try url1.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])
            let values2 = try url2.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])

            if let date1 = values1.creationDate, let date2 = values2.creationDate {
                //if let date1 = values1.contentModificationDate, let date2 = values2.contentModificationDate {
                return date1.compare(date2) == ComparisonResult.orderedDescending
            }
        } catch _{

        }
        return true
    })

    for fileName in orderedFullPaths {
        do {
            let values = try fileName.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])
            if let date = values.creationDate{
                //let date : Date? = values.contentModificationDate
                print(fileName.lastPathComponent, " ", date)
                let theFileName = fileName.lastPathComponent
                fileNames.append(theFileName)
            }
        }
        catch _{

        }
    }
    return fileNames
}
like image 25
mriaz0011 Avatar answered Sep 21 '22 20:09

mriaz0011