본문 바로가기

Swift

UIDocument를 이용한 iCloud Storage 사용하기

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")
                }
            })
       }
    }
}