왜 UIDocument를 써야 하는가?
파일을 iCloud에 저장하고 싶다면 UIDocument가 애플이 제공하는 여러 방법 중 가장 기본적인 방법이다.
왜 UIDocument를 Subclassing을 해야 하는가?
UIDocument는 추상클래스이니까 직접 호출하여 사용할 수 없다.
sbuclassing할 때 아래 두 개의 메소드는 반드시 구현을 해야 한다.
contentsForType(_:Error:)
이 메소드는 데이터가 파일이나 문서에 쓰여질 때 호출된다. 이 메소드는 쓰여질 데이터를 모으고 이를 NSData나 NSFileWrapper로 리턴할 수 있다.
결국 iCloud에 저장하고자 하는 데이터를 가져와서 NSData나 NSFileWrapper로 리턴하는 역할을 하는 것 같다.
loadFromContents(_:ofType:Error)
이 메소드는 iCloud의 파일이나 문서를 읽어 App내부 데이터 모델에 적재하는 역할을 한다.
iCloud를 사용한다는 것은 동시에 여러 앱이 iCloud의 같은 파일에 접근할 수 있다는 것이고 이는 결국 충돌이 일어날 수 있다는 것을 의미한다. 이러한 상태를 감지하기 위해서 다음 5가지 상태 정보를 제공한다.
UIDocumentStateNormal - 사용자가 파일이나 문서를 편집 가능한 상태, 즉 iCloud의 파일에 쓸 수 있는 상태
UIDocumentStateClosed - 파일이나 문서에 접근할 수 없는 상태. 어떤 다른 App이 이 파일을 점유하고 있을 듯. 문서를 읽는 도중 에러가 발생한 경우도 해당된다.
UIDocumentStateInConflict - 파일이나 문서에 대해 충돌이 감지됨, 그런데 어쩌라는 거지?
UIDocumentStateSavingError - 파일이나 문서를 저장하다가 에러 발생함
UIDocumentStateEditingDisabled - 상태 이름만 보면 편집 불가인데 설명은 파일이나 문서가 사용 중이라 편집하기에 안전하지 않다고 써 있다. 뭥믜?
iCloud에 저장하기 위해서는 iCloud의 몇 가지 특징을 알아야 한다.
iCloud에 저장되는 경로를 알아야 한다. NSURL이지만 일반적으로 ubiquityURL이라 네이밍한다.
iCloud에서는 파일에 직접 접근하지 않고 검색을 통해서 접근하는 것이 좋다고 한다. 그래서 필요한 것이 metaDataQuery이다.
metaDataQuery를 선언하여 iCloud storage 내를 검색하면 되는데 이 metaDataQuery는 별도의 쓰레드로 동작한다고 한다. 그러므로 결과는 notification으로 날아온다.
UIDocument 초기화 시 주의사항
UIDocument()로 초기화이상한 에러를 내뱉는다.
에러 내용:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'do not call -[UIDocument init] - the designated initializer is -[UIDocument initWithFileURL:]
그러므로 초기화 할 경우 UIDocument(fileURL:)로 세팅한다.
NSMetaDataQuery 초기화 시 주의사항
MetaDataQuery를 선언할 때 특정 메소드 내에서 선언하면 안된다. 즉 local로 선언하면 안된다.
컴파일 단계에서는 에러를 발생시키지 않지만 MetaDataQuery의 결과를 알려주는 noti 메소드 (metadataQueryDidFinishGathering)가 호출되지 않기 때문이다.
SampleSource:
import UIKit class ViewController: UIViewController { @IBOutlet weak var textView: UITextView! var document: MyDocument? var documentURL: NSURL? var ubiquityURL: NSURL? var metaDataQuery: NSMetadataQuery? override func viewDidLoad() { super.viewDidLoad() let filemgr = NSFileManager.defaultManager() ubiquityURL = filemgr.URLForUbiquityContainerIdentifier(nil) guard ubiquityURL != nil else { print("Unable to access iCloud Account") print("Open the Settings app and enter your Apple ID into iCloud settings") return } ubiquityURL = ubiquityURL?.URLByAppendingPathComponent("Documents/savefile.txt") metaDataQuery = NSMetadataQuery() metaDataQuery?.predicate = NSPredicate(format: "%K like 'savefile.txt'", NSMetadataItemFSNameKey) metaDataQuery?.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope] NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.metadataQueryDidFinishGathering), name: NSMetadataQueryDidFinishGatheringNotification, object: metaDataQuery!) metaDataQuery!.startQuery() } func metadataQueryDidFinishGathering(notification: NSNotification) -> Void { let query: NSMetadataQuery = notification.object as! NSMetadataQuery query.disableUpdates() NSNotificationCenter.defaultCenter().removeObserver(self, name: NSMetadataQueryDidFinishGatheringNotification, object: query) query.stopQuery() let results = query.results if query.resultCount == 1 { let resultURL = results[0].valueForAttribute(NSMetadataItemURLKey) as! NSURL document = MyDocument(fileURL: resultURL) document?.openWithCompletionHandler({(success: Bool) -> Void in if success { print("iCloud file open OK") self.textView.text = self.document?.userText self.ubiquityURL = resultURL } else { print("iCloud file open failed") } }) } else { document = MyDocument(fileURL: ubiquityURL!) document?.saveToURL(ubiquityURL!, forSaveOperation: .ForCreating, completionHandler: {(success: Bool) -> Void in if success { print("iCloud create OK") } else { print("iCloud create failed") } }) } } }