美文网首页
iCloud Drive经验分享

iCloud Drive经验分享

作者: Jnxss | 来源:发表于2020-11-13 23:08 被阅读0次

背景

公司最近开发一款App,需要支持iCloud来存储一些文件.在此分享一下实现过程中的一些开发经验.

iCloud工作流程

iCloud Drive 工作流程

以上就是对iCloud工作的一个图解,相信大家一看就懂,我就不过多解释了,让我们赶紧微笑面对需求吧

产品需求一

要求iCloud Drive中包含我们产品的文件夹(即我们的iCloud文件对外开放)

看到这个需求,立马想到肯定是info.plist里面设置就行了呗
眼睛都看花了,找到这三个跟需求搭点边,有没有用先试试咯

Supports opening documents in place = true
Supports Document Browser = true
Application supports iTunes file sharing = true

然后验证之后,发现没啥用.经过多番苦找,方法如下:

  • 第一步
<key>NSUbiquitousContainers</key>
<dict>
    <key>iCloud.$(PRODUCT_BUNDLE_IDENTIFIER)</key>
    <dict>
        <key>NSUbiquitousContainerIsDocumentScopePublic</key>
        <true/>
        <key>NSUbiquitousContainerName</key>
        <string>你的App名称</string>
        <key>NSUbiquitousContainerSupportedFolderLevels</key>
        <string>Any</string>
    </dict>
</dict>
  • 第二步
    随便往iCloud中上传一点文件或创建文件夹,运行程序,大功告成

产品需求二

上传文件

继承UIDocument

class CloudDocument: UIDocument {
    
    lazy var data = Data()
    
    override func contents(forType typeName: String) throws -> Any {
        data
    }
    
    override func load(fromContents contents: Any, ofType typeName: String?) throws {
        guard let data = contents as? Data else { return }
        self.data = data
    }
    
}

开始上传,大概就这样(上传完可能并不会立马上传到云端,想知道进度需要主动监听通知,跟监听下载进度一样)

func upload(_ file: File, to url: URL) {
    let document = CloudDocument(fileURL: file.url)
    document.data = file.data
    document.save(to: url, for: .forCreating) {
        print($0)
    }
}

如果想将文件改动上传也很简单,只需将已经下载好的文件通过它的path或者url打开之后保存即可,系统会自动上传

产品需求三

文件下载带进度

  • 第一步,获取容器路径
func documentRootURL() {
        // 官方建议异步线程获取
        DispatchQueue.global().async { [weak self] in
            let url = FileManager.default.url(forUbiquityContainerIdentifier: nil)
            DispatchQueue.main.async { [weak self] in
                if url == nil {
                    print("前往开启iCloud Drive")
                } else {
                    self?.documentURL = url?.appendingPathComponent("Document")
                }
            }
        }   
    }
  • 第二步获取文件列表
func iCloudFiles() {
        let query = NSMetadataQuery()
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        var observer: NSObjectProtocol?
        observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: .main) { _ in
            query.stop()
            if observer != nil {
                NotificationCenter.default.removeObserver(observer!)
            }
            let files = query.results.compactMap { $0 as? NSMetadataItem }
            files.forEach {
                print($0.value(forAttribute: NSMetadataItemDisplayNameKey)!)
            }
        }
        query.start()
    }

乍一看没啥问题(墙裂建议对NSMetadataItem进行封装),运行之后才发现这样的文件列表不是我们想要的,它把整个iCloud中的文件都给我们了,不慌,query应该可以设置过滤条件,修改如下

func iCloudFiles() {
        let query = NSMetadataQuery()
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        query.predicate = NSPredicate(format: "%K.URLByDeletingLastPathComponent == %@", NSMetadataItemURLKey, documentURL!)
        var observer: NSObjectProtocol?
        observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidFinishGathering, object: query, queue: .main) { _ in
            query.stop()
            if observer != nil {
                NotificationCenter.default.removeObserver(observer!)
            }
            let files = query.results.compactMap { $0 as? NSMetadataItem }
            files.forEach {
                print($0.value(forAttribute: NSMetadataItemDisplayNameKey)!)
            }
        }
        query.start()
    }

至此,文件列表终于出来了,那我可以开始下载了吧,找找方法,终于找到一个

func download(_ file: NSMetadataItem) {
        do {
            try FileManager.default.startDownloadingUbiquitousItem(at: file.value(forAttribute: NSMetadataItemURLKey) as! URL)
        } catch let error as NSError {
            print(error.localizedDescription)
        }
    }

不过,好像没有进度回调啊,FileManager代理看了下也不像.看来只有指望代理了

// 先查询文件状态(比如下载文件,并不是立即下载的,严谨一点还要判断是否有错误,在这里我就暂时不判断了)
func queryFileStatusChanged(at path: String, callback: (() -> ())? = nil) {
        let query = NSMetadataQuery()
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        query.predicate = NSPredicate(format: "%K == %@", NSMetadataItemPathKey, path)
        var observer: NSObjectProtocol?
        observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: .main) { _ in
            query.stop()
            if observer != nil {
                NotificationCenter.default.removeObserver(observer!)
            }
            callback?()
        }
        query.start()
    }

在上一步监听状态改变之后,我们可以开始监听下载进度了

func queryProgress(at path: String) {
        let query = NSMetadataQuery()
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        query.predicate = NSPredicate(format: "%K == %@", NSMetadataItemPathKey, path)
        var observer: NSObjectProtocol?
        observer = NotificationCenter.default.addObserver(forName: .NSMetadataQueryDidUpdate, object: query, queue: .main) { _ in
            
            func stopQuery() {
                query.stop()
                query.disableUpdates()
                if observer != nil {
                    NotificationCenter.default.removeObserver(observer!)
                }
            }
            
            guard let file = query.results.first as? NSMetadataItem else {
                stopQuery()
                return
            }
            // 上传进度也是如此,只不过参数变了而已
            let downloadPercent = file.value(forAttribute: NSMetadataUbiquitousItemPercentDownloadedKey) as! Double
            print("进度是...", downloadPercent)
            if downloadPercent == 100 {
                stopQuery()
            }
        }
        query.start()
        query.enableUpdates()
    }

需要注意的是iCloud文件操作并不是实时的,大多数情况下需要监听文件状态,所谓的文件操作也都需要将云端的文件下载到本地之后在进行,所以很有必要在文件列表中加上文件状态标识

产品需求四

解决文件冲突

func resolveFileConflict(at url: URL) {
        guard let versions = NSFileVersion.unresolvedConflictVersionsOfItem(at: url) else { return }
        for version in versions {
            // 文档修改时间
            print(version.modificationDate)
            // 上传文档的设备名称
            print(version.localizedNameOfSavingComputer)
            // 文档名称
            print(version.localizedName)
            if user.need {
                version.isResolved = true
            } else {
                try? version.remove()
            }
        }
    }

建议做一个冲突文件列表让用户选择哪一个版本
参考:
https://developer.apple.com/library/archive/documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/Introduction/Introduction.html

相关文章

网友评论

      本文标题:iCloud Drive经验分享

      本文链接:https://www.haomeiwen.com/subject/cmvbbktx.html