基于 Swift 中的 creationDate 从库中获取所有照片 [更快的方式?]

Posted

技术标签:

【中文标题】基于 Swift 中的 creationDate 从库中获取所有照片 [更快的方式?]【英文标题】:Fetch All Photos from Library based on creationDate in Swift [Faster Way?] 【发布时间】:2018-11-01 09:22:56 【问题描述】:

我有一个UICollectionView 显示基于最新“creationDate”的图书馆照片。为此,我使用以下代码:

struct AssetsData 
    var creationDate: Date, assetResult: PHFetchResult<PHAsset>


func fetchPhotos() -> [AssetsData] 
    //Date Formatter
    let formatter = DateFormatter()
    formatter.dateStyle = DateFormatter.Style.medium
    formatter.timeStyle = DateFormatter.Style.none

    //Photos fetch
    let fetchOptions = PHFetchOptions()
    let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
    fetchOptions.sortDescriptors = sortOrder
    let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
    var arrCreationDate = [Date]()
    var arrDates = [String]()

    //Getting All dates
    for index in 0..<assetsFetchResult.count 
        if let creationDate = assetsFetchResult[index].creationDate 
            let formattedDate = formatter.string(from: creationDate)
            if !arrDates.contains(formattedDate) 
                arrDates.append(formattedDate)
                arrCreationDate.append(creationDate)
            
        
    

    //Fetching Assets based on Dates
    var arrPhotoAssetsData = [AssetsData]()
    for createdDate in arrCreationDate 
        if let startDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: createdDate.day, forMonth: createdDate.month, forYear: createdDate.year, forHour: 23, forMinute: 59, forSecond: 59) 
            fetchOptions.predicate = NSPredicate(format: "creationDate > %@ AND creationDate < %@", startDate as NSDate, endDate as NSDate)
            let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
            arrPhotoAssetsData.append(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult))
        
    
    return arrPhotoAssetsData


func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? 
    var dateComponents = DateComponents()
    dateComponents.day = day
    dateComponents.month = month
    dateComponents.year = year
    dateComponents.hour = hour
    dateComponents.minute = minute
    dateComponents.second = second
    var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
    gregorian.timeZone = NSTimeZone.system
    return gregorian.date(from: dateComponents)

代码运行良好!但问题是加载 10k+ 张照片需要将近 7 - 9 秒。直到 6k 张照片都没有问题,但我真的需要一些有效的方法,以便我可以在 UICollectionView 中加载一些 asset,其余的我可以稍后添加。我需要无论照片数量如何,都不应超过 2 - 3 秒。有人可以帮忙吗?

【问题讨论】:

【参考方案1】:

假设您有 8k 张照片。因此,您遍历两个“for”循环以获取 arrCreationDate 和 arrPhotoAssets 数据(这是所需工作的两倍)

相反,您可以尝试通过一个循环来完成。这是一个粗略的方法:-

    let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
    let fetchOptions = PHFetchOptions()
    var arrCreationDate = [Date]()
    var arrPhotoAssetsData = [AssetsData]()
    var arrDates = [String]()

     for index in 0..<assetsFetchResult.count 
        if let creationDate = assetsFetchResult[index].creationDate 
            let formattedDate = formatter.string(from: creationDate)
            if !arrDates.contains(formattedDate) 
                //You can convert the formattedDate to actual date here and do a check similar to this, do what you do in the other loop here too
                if(actualDate < actualDateOfTheFirstElementAtArray)
                   arrCreationDate.insert(actualDate, at: 0)
                   fetchOptions.predicate = NSPredicate(format: "creationDate > %@ AND creationDate < %@", startDate as NSDate, endDate as NSDate)
                   let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
                   arrPhotoAssetsData.insert(AssetsData(creationDate: createdDate, assetResult: assetsPhotoFetchResult), at: 0)
                
            
        
     

这只是为了让您大致了解我在说什么,因为这样可以减轻一半的负担(只需一个循环)

还可以尝试使用 prefetchDataSource 为您的集合视图预加载一些数据

编辑:-

我假设您已经尝试过以下方法:-

        func fetchPhotos() -> [AssetsData] 
    //Date Formatter
    let formatter = DateFormatter()
    formatter.dateStyle = DateFormatter.Style.medium
    formatter.timeStyle = DateFormatter.Style.none

    //Photos fetch
    let fetchOptions = PHFetchOptions()
    let sortOrder = [NSSortDescriptor(key: "creationDate", ascending: false)]
    fetchOptions.sortDescriptors = sortOrder
    let assetsFetchResult = PHAsset.fetchAssets(with: .image, options: fetchOptions)
    var arrCreationDate = [Date]()
    var arrDates = [String]()
    var arrPhotoAssetsData = [AssetsData]()

    //Getting All dates
    for index in 0..<assetsFetchResult.count 
        if let creationDate = assetsFetchResult[index].creationDate 
            let formattedDate = formatter.string(from: creationDate)
            if !arrDates.contains(formattedDate) 
                arrDates.append(formattedDate)
                arrCreationDate.append(creationDate)
                convertToAssetsDataAndAppend(date: creationDate, fetchOptions: fetchOptions, toArray: &arrPhotoAssetsData)
            
        
    
    return arrPhotoAssetsData
    

    func convertToAssetsDataAndAppend(date: Date, fetchOptions: PHFetchOptions, toArray: inout [AssetsData])
    if let startDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 0, forMinute: 0, forSecond: 0), let endDate = getDate(forDay: date.day, forMonth: date.month, forYear: date.year, forHour: 23, forMinute: 59, forSecond: 59) 
        fetchOptions.predicate = NSPredicate(format: "creationDate > %@ AND creationDate < %@", startDate as NSDate, endDate as NSDate)
        let assetsPhotoFetchResult = PHAsset.fetchAssets(with: PHAssetMediaType.image, options: fetchOptions)
        toArray.append(AssetsData(creationDate: date, assetResult: assetsPhotoFetchResult))
    


func getDate(forDay day: Int, forMonth month: Int, forYear year: Int, forHour hour: Int, forMinute minute: Int, forSecond second: Int) -> Date? 
    var dateComponents = DateComponents()
    dateComponents.day = day
    dateComponents.month = month
    dateComponents.year = year
    dateComponents.hour = hour
    dateComponents.minute = minute
    dateComponents.second = second
    var gregorian = Calendar(identifier: Calendar.Identifier.gregorian)
    gregorian.timeZone = NSTimeZone.system
    return gregorian.date(from: dateComponents)

如果这没有帮助,那么在每次循环迭代后使用某种回调重新加载集合视图怎么样? (使用上述方法) 这样,您不会让用户等到整个内容加载完毕

Idk,这些可能看起来很琐碎,但我只是想帮忙:)

【讨论】:

谢谢。我试过了,但仍然需要 2.09 秒才能获取 3.1k 张照片,而我的需要 1.85 秒。 酷,所以你在当前的 for 循环中使用了另一个循环来对元素进行排序?在这种情况下,它是一个嵌套循环,显然会比通常的两个单独循环消耗更多时间。您可以做的是尝试使用二进制搜索进行排序 嗯..二进制搜索不会像我想的那样有效,因为没有搜索特定日期。我们需要根据从库中获取的日期的所有数据。你怎么看? 不,我明白了,但我认为您采用的方法类似于我的想法,我的错。我已经编辑了答案。让我知道你的想法... 所以你只是区分了附加部分!太酷了,我会尝试这种方式,比如附加前 5 或 6 个日期,然后重新加载 UICollectionView,然后再次附加新数据重新加载它。

以上是关于基于 Swift 中的 creationDate 从库中获取所有照片 [更快的方式?]的主要内容,如果未能解决你的问题,请参考以下文章

Pandas、groupby 和其他列中的计数数据

Swift 为文件的属性设置了一些字符串值

从 CloudKit 记录中选择 creationDate

如何在 sanity.io 中为文档创建默认的“creationDate”?

在 R pdf 输出中抑制或设置 CreationDate/ModDate

如何更改 CreationDate 资源值