Search code examples
iosswifticloud

How to get data from a file in iCloud after reinstalling the app?


The application uses iCloud to store the objects conformed to Codable protocol using NSKeyedArchiver/NSKeyedUnarchiver. Synchronization between devices is OK, except when the application is reinstalled on the device (or installed on a new device) and the file with data exists – in this case NSKeyedUnarchiver.unarchiveObject(withFile: filePathe) return nil. How to get data from an existing file from the iCloud when I install the application on a new device (reinstall on the same device)?

class ViewController: UIViewController {

@IBOutlet weak var nameLabel: UILabel!
@IBOutlet weak var weightLabel: UILabel!
@IBOutlet weak var nameTextField: UITextField!
@IBOutlet weak var weightTextField: UITextField!

var iCloudContainer: URL? {
    return FileManager().url(forUbiquityContainerIdentifier: nil)
}

func getFilePath(container: URL, fileName: String) -> String {
    let filePath = container.appendingPathComponent(fileName).path

    return filePath
}

override func viewDidLoad() {
    super.viewDidLoad()
}

@IBAction func fetchButtonPressed(_ sender: UIButton) {

    let container = self.iCloudContainer
    let filePathe = getFilePath(container: container!, fileName: "Person")

    if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: filePathe) as? Data {
        if let person = try? JSONDecoder().decode(Person.self, from: jsonData) {
            nameLabel.text = person.name
            weightLabel.text = String(person.weight)
        } else {
            nameLabel.text = "No data loaded"
            weightLabel.text = "No data loaded"
        }
    } else {
        nameLabel.text = "No data loaded"
        weightLabel.text = "No data loaded"
    }

}

@IBAction func saveButtonPressed(_ sender: UIButton) {
    let container = self.iCloudContainer
    let filePathe = getFilePath(container: container!, fileName: "Person")

    let person = Person(name: nameTextField.text!, weight: Double(weightTextField.text!)!)
    let jsonData = try? JSONEncoder().encode(person)
    NSKeyedArchiver.archiveRootObject(jsonData!, toFile: filePathe)
}

Solution

  • To get data from iCloud container to your local UbiquityContainer you should use NSMetadata to find and download items from iCloud to device.

        lazy var metadataQuery : NSMetadataQuery = {
        let query = NSMetadataQuery()
        query.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
        query.predicate = NSPredicate(format: "%K CONTAINS %@", NSMetadataItemFSNameKey, "List")
    
        NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: query)
        NotificationCenter.default.addObserver(self, selector: #selector(didFinishGathering), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: query)
    
        return query
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        self.metadataQuery.start()
    
    }
    
        deinit {
            NotificationCenter.default.removeObserver(self)
        }
    
    
    @objc func didFinishGathering(notification: Notification?) {
        let query = notification?.object as? NSMetadataQuery
    
        query?.enumerateResults { (item: Any, index: Int, stop: UnsafeMutablePointer<ObjCBool>) in
            let metadataItem = item as! NSMetadataItem
    
            if isMetadataItemDownloaded(item: metadataItem) == false {
    
                let url = metadataItem.value(forAttribute: NSMetadataItemURLKey) as! URL
    
                try? FileManager.default.startDownloadingUbiquitousItem(at: url)
            }
        }
    
        guard let queryresultsCount = query?.resultCount else { return }
        for index in 0..<queryresultsCount {
            let item = query?.result(at: index) as? NSMetadataItem
            let itemName = item?.value(forAttribute: NSMetadataItemFSNameKey) as! String
    
            let container = filesCoordinator.iCloudContainer
            let filePath = filesCoordinator.getFilePath(container: container!, fileName: "TaskList")
            let addressPath = filesCoordinator.getFilePath(container: container!, fileName: "CategoryList")
    
            if itemName == "TaskList" {
                if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: filePath) as? Data {
                    if let person = try? JSONDecoder().decode(Person.self, from: jsonData) {
                        nameLabel.text = person.name
                        weightLabel.text = String(person.weight)
                    } else {
                        nameLabel.text = "NOT decoded"
                        weightLabel.text = "NOT decoded"
                    }
                } else {
                    nameLabel.text = "NOT unarchived"
                    weightLabel.text = "NOT unarchived"
                }
            } else if itemName == "CategoryList" {
                if let jsonData = NSKeyedUnarchiver.unarchiveObject(withFile: addressPath) as? Data {
                    if let address = try? JSONDecoder().decode(Address.self, from: jsonData) {
                        streetLabel.text = address.street
                        houseLabel.text = String(address.house)
                    } else {
                        streetLabel.text = "NOT decoded"
                        houseLabel.text = "NOT decoded"
                    }
                } else {
                    streetLabel.text = "NOT unarchived"
                    houseLabel.text = "NOT unarchived"
                }
            }
        }
    }
    
    func isMetadataItemDownloaded(item : NSMetadataItem) -> Bool {
        if item.value(forAttribute: NSMetadataUbiquitousItemDownloadingStatusKey) as? String == NSMetadataUbiquitousItemDownloadingStatusCurrent {
            return true
        } else {
            return false
        }
    }
    
    
    @IBAction func saveButtonPressed(_ sender: UIButton) {
        let container = filesCoordinator.iCloudContainer
        let personPath = filesCoordinator.getFilePath(container: container!, fileName: "TaskList")
        let addressPath = filesCoordinator.getFilePath(container: container!, fileName: "CategoryList")
    
        let person = Person(name: nameTextField.text!, weight: Double(weightTextField.text!)!)
        let jsonPersonData = try? JSONEncoder().encode(person)
        NSKeyedArchiver.archiveRootObject(jsonPersonData!, toFile: personPath)
    
        let address = Address(street: streetTextField.text!, house: Int(houseTextField.text!)!)
        let jsonAddressData = try? JSONEncoder().encode(address)
        NSKeyedArchiver.archiveRootObject(jsonAddressData!, toFile: addressPath)
    }
    

    }// end of the class