UIDocument
- 如何使用icloud 同步文件
- 如何添加文件到icloud
- 如何獲取icloud中文件列表
- 如何刪除icloud中文件
- 多個(gè)設(shè)備同時(shí)打開(kāi)一個(gè)document 如何同步
- 多個(gè)設(shè)備如何同步document列表
- 如何處理沖突
1. 基本配置
首先添加icloud capability,這個(gè)需要開(kāi)發(fā)者賬號(hào), 然后添加一個(gè)icloud container id熄攘。
2. 添加數(shù)據(jù)
想把一個(gè)文件數(shù)據(jù)放到icloud上录语,要先檢查icloud 是否可用
- 檢查icloud是否已經(jīng)可用,并獲取到icloud container url
guard FileManager.default.ubiquityIdentityToken != nil else {
print("?? iCloud isn't enabled yet. Please enable iCloud and run again.")
return nil
}
// Dispatch to a global queue because url(forUbiquityContainerIdentifier:) might take a nontrivial
// amount of time to set up iCloud and return the requested URL
//
DispatchQueue.global().async {
if let url = FileManager.default.url(forUbiquityContainerIdentifier: containerIdentifier) {
DispatchQueue.main.async {
self.containerRootURL = url
}
return
}
print("?? Failed to retrieve iCloud container URL for:\(containerIdentifier ?? "nil")\n"
+ "Make sure your iCloud is available and run again.")
}
上面代碼檢查icloud 功能是否開(kāi)啟,并獲取icloud container 容器的url 地址芯侥。
- 下面要封裝數(shù)據(jù)缠犀,然后放到icloud container里面
需要自定義類(lèi)繼承UIDocument,然后通過(guò)操作document來(lái)實(shí)現(xiàn)文件的增刪改
let document = Document(fileURL: fileURL)
document.save(to: fileURL, for: .forCreating) { _ in
document.close { success in
if !success {
print("Failed to close the document: \(fileURL)")
}
completionHandler?(success)
}
}
func removeDocument(at fileURL: URL) {
DispatchQueue.global().async {
NSFileCoordinator().coordinate(writingItemAt: fileURL, options: .forDeleting, error: nil) { newURL in
do {
try FileManager.default.removeItem(atPath: newURL.path)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
要看看文件是否已經(jīng)添加成功臭觉,可以在設(shè)置->iCloud->存儲(chǔ)空間昆雀,
但是要配置一些數(shù)據(jù)
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.example.apple-samplecode.SimpleiCloudDocument</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key> // 是否支持在File app中查看
<true/>
<key>NSUbiquitousContainerName</key> // 在File app中顯示的名稱(chēng)
<string>SimpleiCloudDocument</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>ANY</string>
</dict>
</dict>
要實(shí)現(xiàn)在File app查看自己app中數(shù)據(jù),并實(shí)現(xiàn)點(diǎn)擊自定義格式文件能打開(kāi)app蝠筑,要實(shí)現(xiàn)配置CFBundleDocumentTypes and UTExportedTypeDeclarations
設(shè)置LSSupportsOpeningDocumentsInPlace = true
實(shí)現(xiàn)scene(_:openURLContexts:) 方法 具體配置看demo
<key>UTExportedTypeDeclarations</key>
<array>
<dict>
<key>UTTypeConformsTo</key>
<array>
<string>com.apple.package</string>
<string>public.content</string>
<string>public.data</string>
</array>
<key>UTTypeDescription</key>
<string>AIScanner</string>
<key>UTTypeIconFiles</key>
<string>Icon.png</string>
<key>UTTypeIdentifier</key>
<string>com.daping.pdfdoc.icloud.aisc</string>
<key>UTTypeTagSpecification</key>
<dict>
<key>public.filename-extension</key>
<array>
<string>aisc</string>
</array>
<key>public.mime-type</key>
<string>application/aisc</string>
</dict>
</dict>
</array>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeIconFiles</key>
<array/>
<key>CFBundleTypeName</key>
<string>AIScanner</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSTypeIsPackage</key>
<true/>
<key>LSItemContentTypes</key>
<array>
<string>com.daping.pdfdoc.icloud.aisc</string>
</array>
</dict>
</array>
<key>NSUbiquitousContainers</key>
<dict>
<key>iCloud.com.danping.pdfdoc.aiscanner</key>
<dict>
<key>NSUbiquitousContainerIsDocumentScopePublic</key>
<true/>
<key>NSUbiquitousContainerName</key>
<string>SimpleiCloudDocument</string>
<key>NSUbiquitousContainerSupportedFolderLevels</key>
<string>ANY</string>
</dict>
</dict>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
3. 查詢(xún)數(shù)據(jù)
如何查詢(xún)icloud document
當(dāng)在一個(gè)設(shè)備添加了document狞膘, icloud并不會(huì)立即把數(shù)據(jù)同步到其他設(shè)備,而是先同步metadata什乙,因?yàn)閙etadata 數(shù)據(jù)量小挽封,根據(jù)不同設(shè)置選擇是否立即同步數(shù)據(jù),iOS設(shè)備不會(huì)立即同步臣镣,mac會(huì)立即同步辅愿。
其他設(shè)置收到icloud 新數(shù)據(jù)通知后,數(shù)據(jù)并沒(méi)有同步過(guò)來(lái)忆某,所有不能使用FileManager API 去操作數(shù)據(jù)渠缕。
要查詢(xún)和監(jiān)聽(tīng)icloud 數(shù)據(jù)變換要使用 NSMetadataQuery 接口。
metadataQuery.notificationBatchingInterval = 1
metadataQuery.searchScopes = [NSMetadataQueryUbiquitousDataScope, NSMetadataQueryUbiquitousDocumentsScope]
metadataQuery.predicate = NSPredicate(format: "%K LIKE %@", NSMetadataItemFSNameKey, "*." + Document.extensionName)
metadataQuery.sortDescriptors = [NSSortDescriptor(key: NSMetadataItemFSNameKey, ascending: true)]
metadataQuery.start()
當(dāng)讀取數(shù)據(jù)的時(shí)候褒繁,為了避免沖突亦鳞,先關(guān)閉查詢(xún)接口,然后再開(kāi)啟。
func metadataItemList() -> [MetadataItem] {
var result = [MetadataItem]()
metadataQuery.disableUpdates()
if let metadatItems = metadataQuery.results as? [NSMetadataItem] {
result = metadataItemList(from: metadatItems)
}
metadataQuery.enableUpdates()
return result
}
4. 沖突解決
icloud 多個(gè)設(shè)備之前同步數(shù)據(jù)燕差,不同的網(wǎng)絡(luò)環(huán)境遭笋,沖突不可避免。比如多個(gè)設(shè)備都斷網(wǎng)徒探,然后同時(shí)修改數(shù)據(jù)瓦呼,再同時(shí)打開(kāi)網(wǎng)絡(luò), 就會(huì)出現(xiàn)沖突测暗。
@objc
func documentStateChanged(_ notification: Notification) {
guard let document = document else { return }
printDocumentState(for: document)
// The document state is normal.
// Update the UI with unpresented peer changes, if any.
//
if document.documentState == .normal {
navigationItem.rightBarButtonItem?.isEnabled = true
handleConflictsItem.isEnabled = false
if !document.unpresentedPeerChanges.isEmpty {
let changes = document.unpresentedPeerChanges
document.clearUnpresentedPeerChanges()
updateCollectionView(with: changes)
}
return
}
// The document has conflicts but no error.
// Update the UI with unpresented peer changes if any.
//
if document.documentState == .inConflict {
navigationItem.rightBarButtonItem?.isEnabled = true
handleConflictsItem.isEnabled = true
if !document.unpresentedPeerChanges.isEmpty {
let changes = document.unpresentedPeerChanges
document.clearUnpresentedPeerChanges()
updateCollectionView(with: changes)
}
return
}
// The document is in a closed state with no error. Clear the UI.
//
if document.documentState == .closed {
navigationItem.rightBarButtonItem?.isEnabled = false
handleConflictsItem.isEnabled = false
title = ""
var snapshot = DiffableImageSourceSnapshot()
snapshot.appendSections([0])
diffableImageSource.apply(snapshot)
return
}
// The document has conflicts. Enable the toolbar item.
//
if document.documentState.contains(.inConflict) {
handleConflictsItem.isEnabled = true
}
// The document is editingDisabled. Disable the UI for editing.
//
if document.documentState.contains(.editingDisabled) {
navigationItem.rightBarButtonItem?.isEnabled = false
handleConflictsItem.isEnabled = false
}
}
上面代碼顯示 當(dāng)打開(kāi)一個(gè)document后央串,可以監(jiān)聽(tīng)document的變化,包括沖突
不過(guò)解決沖突很簡(jiǎn)單的碗啄,Document 監(jiān)聽(tīng)狀態(tài)變化质和,當(dāng)檢測(cè)到?jīng)_突的時(shí)候,根據(jù)不同策略去解決稚字,比如再簡(jiǎn)單的根據(jù)修改時(shí)間饲宿,最新的保留,其他的刪除胆描。
private func resolveConflictsAsynchronously(document: Document, completionHandler: ((Bool) -> Void)?) {
DispatchQueue.global().async {
NSFileCoordinator().coordinate(writingItemAt: document.fileURL,
options: .contentIndependentMetadataOnly, error: nil) { newURL in
let shouldRevert = self.pickLatestVersion(for: newURL)
completionHandler?(shouldRevert)
}
}
}
private func pickLatestVersion(for documentURL: URL) -> Bool {
guard let versionsInConflict = NSFileVersion.unresolvedConflictVersionsOfItem(at: documentURL),
let currentVersion = NSFileVersion.currentVersionOfItem(at: documentURL) else {
return false
}
var shouldRevert = false
var winner = currentVersion
for version in versionsInConflict {
if let date1 = version.modificationDate, let date2 = winner.modificationDate,
date1 > date2 {
winner = version
}
}
if winner != currentVersion {
do {
try winner.replaceItem(at: documentURL)
shouldRevert = true
} catch {
print("Failed to replace version: \(error)")
}
}
do {
try NSFileVersion.removeOtherVersionsOfItem(at: documentURL)
} catch {
print("Failed to remove other versions: \(error)")
}
return shouldRevert
}