Reputation: 2400
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
}
}
Upvotes: 24
Views: 14023
Reputation: 473
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)
}
Upvotes: 10
Reputation: 4751
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 }
}
}
Upvotes: 10
Reputation: 539745
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
}
Upvotes: 29
Reputation: 1897
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
}
Upvotes: 7
Reputation: 11646
Swift 3.0 Sierra 10.12
Getting a sorted array on date (creation or modification):
func enumAndSortFilesAt(path: String){
let fm = FileManager.default
let url = URL(fileURLWithPath: path)
let optionMask: FileManager.DirectoryEnumerationOptions = [
.skipsHiddenFiles
]
let keys = [URLResourceKey.contentModificationDateKey]
guard let files = try? fm.contentsOfDirectory(at: url,
includingPropertiesForKeys : keys,
options: optionMask ) else {
return
}
let ordered = files.sorted { ( u1: URL, u2: URL) -> Bool in
do{
let values1 = try u1.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])
let values2 = try u2.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.orderedAscending
}
}catch _{
}
return true
}
for f in ordered {
do {
let values = try f.resourceValues(forKeys: [.creationDateKey, .contentModificationDateKey])
if let date = values.creationDate{
//let date : Date? = values.contentModificationDate
print(f.lastPathComponent, " ", date)
}
}
catch _{
}
}
}
Upvotes: 0
Reputation: 1571
// MARK: - String
extension String {
func stringByAppendingPathComponent(path: String) -> String {
let nsSt = self as NSString
return nsSt.appendingPathComponent(path)
}
}
// MARK: - File manager
let fileManagerController = FileManager.default
// MARK: - Application Document Directory
// NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] return an array of String, we will catch just the first item from array
let applicationDocumentsDirectory = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
// MARK: - File path
// Combined with helpers from String extension
let filePath = applicationDocumentsDirectory.stringByAppendingPathComponent(path: name)
do {
let atributes = try fileManagerController.attributesOfItem(atPath: filePath)
// File creation date
if let fileDate = atributes[FileAttributeKey.creationDate] as? Date {
// fileDate has a String value
print(fileDate)
// Will print a creation date of file
}
} catch let error as NSError {
print("Failure to know creation date \(error.description)")
}
Upvotes: 0
Reputation: 2107
return urlDictionary.filter{$0 != nil}.sort{$0.1.compare($1.1) == NSComparisonResult.OrderedDescending }.map{$0.0}.map{$0.lastPathComponent!}
is definitely an overkilling line of code :) You can skip a couple of filter/map steps by using another method of NSFileManager:
func contentsOfDirectoryAtPath(_ path: String) throws -> [String]
and
func attributesOfItemAtPath(_ path: String) throws -> [String : AnyObject].
In the end, you would end up with something equivalent to what you have already done. I think your code is a little bit sophisticated, but the approach is quite good.
Upvotes: 3