Saving CloudKit Record to Local File Saves all fields Except CKAsset - ios

I am trying to save an array of CKRecords to the documents directory in
order to have fast startup and offline access.
Downloading the CKRecords from CloudKit works fine and I am able to use the CKAsset in each record without issue. However, when I save the array of CKRecords that I downloaded to a local file, the CKAsset is not included in the data file. I can tell this from the size of the file saved to the documents directory. If I reconstitute the disk file into an array of CKRecords, I can retrieve all of the fields except the CKAsset. Other than the system fields, and the CKAsset field, all of the fields are Strings.
For testing - I have 10 CloudKit records each with six small String fields
and a CKAsset which is about 500KB. When I check the size of the
resulting file in documents the file size is about 15KB.
Here's the function to save the array. AppDelegate.ckStyleRecords is a
static array of the downloaded CKRecords.
func saveCKStyleRecordsToDisk() {
if AppDelegate.ckStyleRecords.count != 0 {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docsDirectoryURL = urls[0]
let ckStyleURL = docsDirectoryURL.appendingPathComponent("ckstylerecords.data")
do {
let data : Data = try NSKeyedArchiver.archivedData(withRootObject: AppDelegate.ckStyleRecords, requiringSecureCoding: true)
try data.write(to: ckStyleURL, options: .atomic)
print("data write ckStyleRecords successful")
} catch {
print("could not save ckStyleRecords to documents directory")
}
}//if count not 0
}//saveCKStyleRecordsToDisk
Here is the function to reconstitute the array.
func checkForExistenceOfCKStyleRecordsInDocuments(completion: #escaping ([CKRecord]) -> Void) {
let urls = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let docsDirectoryURL = urls[0]
let ckStyleURL = docsDirectoryURL.appendingPathComponent("ckstylerecords.data")
var newRecords : [CKRecord] = []
if FileManager.default.fileExists(atPath: ckStyleURL.path) {
do {
let data = try Data(contentsOf:ckStyleURL)
//yes, I know this has been deprecated, but I can't seem to get the new format to work
if let theRecords: [CKRecord] = try NSKeyedUnarchiver.unarchiveObject(with: data) as? [CKRecord] {
newRecords = theRecords
print("newRecords.count is \(newRecords.count)")
}
} catch {
print("could not retrieve ckStyleRecords from documents directory")
}
}//if exists
completion(newRecords)
}//checkForExistenceOfckStyleRecordsInDocuments
Calling the above:
kAppDelegate.checkForExistenceOfCKStyleRecordsInDocuments { (records) in
print("in button press and records.count is \(records.count)")
//this is just for test
for record in records {
print(record.recordID.recordName)
}
AppDelegate.ckStyleRecords = records
}//completion block
Upon refreshing the tableView that uses the ckStyleRecords array, all data
seems correct except the CKAsset (which in this case is a SceneKit
scene) is of course missing.
Any guidance would be appreciated.

A CKAsset was just a file reference. the fileURL property of the CKAsset is where the actual file is located. If you save a SKAsset then you only save the reference to the file. When doing that you do have to remember that this url is on a cache location which could be cleared if you are low on space.
You could do 2 things.
1. when reading your backup CKAsset, then also check if the file is located at the fileURL location. If the file is not there, then read it again from CloudKit.
2. Also backup the file from the fileURl to your documents folder. When you read your CKAsset from your backup, then just don't read the file from fileURL but the location where you have put it in your documents filter.

Related

When exporting core data to CSV, how do I also save an image?

I have an entity named Item. I have an export function that exports all Items to CSV. So name, weight, quantity and etc is all exported correctly. The purpose of this is to save the data so that it may be imported later if all the data was deleted. One of the attributes of Items is a picture that the user chooses from its own library. How do I export that picture, so that it can be reimported later?
This is on iOS using the latest swift and Xcode.
I know I have not included any code, I am mainly asking for a direction to look. I'm not sure if I can get the location of the image on the device and then save that to the CSV or if there's a similar way. Thank you!
So I solved this problem using the code below but I may have created a new one. I'll be posting a new question to better clarify
The code below allowed me to save to Documents which allowed me to export and import the images.
func saveImage(image: UIImage, string: String) -> Bool {
guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else {
return false
}
guard let directory = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) as NSURL else {
return false
}
do {
print(string)
try data.write(to: directory.appendingPathComponent(string)!)
print("Success - \(string)")
return true
} catch {
print(error.localizedDescription)
return false
}
}
func getSavedImage(named: String) -> UIImage? {
if let dir = try? FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false) {
return UIImage(contentsOfFile: URL(fileURLWithPath: dir.absoluteString).appendingPathComponent(named).path)
}
return nil
}
CSV is a text based file format but Images are binary data. So the two does not mix well.
One thing you can do is convert the image to Base64 String and insert that string to the CSV. But the string would be too large and there may be consequences.
If you are using the same device (probably not) you can get the path of the image and append it to the CSV.
If you are using a DB simply upload your images to it and add the path to the CSV. (you can even upload the images to your drive and add the path)
There may be other ways also.

Get the names of files in an iCloud Drive folder that haven't been downloaded yet

I’m trying to get the names of all files and folders in an iCloud Drive directory:
import Foundation
let fileManager = FileManager.default
let directoryURL = URL(string: "folderPathHere")!
do {
let directoryContents = try fileManager.contentsOfDirectory(at: directoryURL, includingPropertiesForKeys: nil, options: [.skipsSubdirectoryDescendants, .skipsHiddenFiles])
for url in directoryContents {
let fileName = fileManager.displayName(atPath: url.absoluteString)
print(fileName)
}
} catch let error {
let directoryName = fileManager.displayName(atPath: directoryURL.absoluteString)
print("Couldnt get contents of \(directoryName): \(error.localizedDescription)")
}
It appears that any iCloud files that haven’t been downloaded to the device don’t return URLs.
I know I can check if a path contains a ubiquitous item when I already know the path with the code below (even if it isn’t downloaded):
fileManager.isUbiquitousItem(at: writePath)
Is there a way to get the URLs & names of those iCloud files without downloading them first?
The directory URL is a security-scoped URL constructed from bookmark data in case that makes any difference (omitted that code here for clarity).
Thanks
Found the answer. I was skipping hidden files with ".skipsHiddenFiles", but the non-downloaded files are actually hidden files, named: ".fileName.ext.iCloud".
Remove the skips hidden files option now works as expected.
You need to use a NSFileCoordinator to access the directory in iCloud Storage, and then normalize placeholder file names for items that haven't been downloaded yet:
let iCloudDirectoryURL = URL(...)
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinate(
readingItemAt: iCloudDirectoryURL,
options: NSFileCoordinator.ReadingOptions(),
error: nil
) { readingURL in
do {
let contents = try FileManager.default.contentsOfDirectory(
at: readingURL, includingPropertiesForKeys: nil
)
for url in contents {
print("\(canonicalURL(url))")
}
} catch {
print("Error listing iCloud directory: '\(error)'")
}
}
func canonicalURL(_ url: URL) -> URL {
let prefix = "."
let suffix = ".icloud"
var fileName = url.lastPathComponent
if fileName.hasPrefix(prefix), fileName.hasSuffix(suffix) {
fileName.removeFirst(prefix.count)
fileName.removeLast(suffix.count)
var result = url.deletingLastPathComponent()
result.append(path: fileName)
return result
} else {
return url
}
}

How can I purge just images in cache directory?

I want to clear all images in default cache directory every 1 minute but the cache files do not have extensions to specific their type and I don't know how to delete just images like PNG (not other data).
this is sample code I saw on this site:
let fileManager = FileManager.default
let documentsUrl = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first! as NSURL
let documentsPath = documentsUrl.path
do {
if let documentPath = documentsPath
{
let fileNames = try fileManager.contentsOfDirectory(atPath: "\(documentPath)")
print("all files in cache: \(fileNames)")
for fileName in fileNames {
if (fileName.hasSuffix(".png"))
{
let filePathName = "\(documentPath)/\(fileName)"
try fileManager.removeItem(atPath: filePathName)
}
}
let files = try fileManager.contentsOfDirectory(atPath: "\(documentPath)")
print("all files in cache after deleting images: \(files)")
}
} catch {
print("Could not clear temp folder: \(error)")
}
possibility:
you could use ImageIO to test EVERY file before deleting it but that would mean reading it before removing it. It'd replace testing for a suffix BUT
as it'd be really unnecessarily expensive IMHO I wont even provide code.
okay way:
=> Rename your images to have a suffix or prefix so you can identify them by name (n calls)
good way
=> put the images in a seperate folder and just remove the folder to purge them. (1 call)

IOS How to save and load JSON String to memory

I am using NSUserDefaults.standardUserDefaults() to save my JSON String got from WebService to iPhone memory. When I load it to use with my parser function, the processing speed is so slow. I don't want to use RealmIO or any database because that thing doesn't necessary. I would like to ask is there any way faster than NSUserDefaults? Please check my JSON file (I need store more than 20 files like that)
Instead of saving your data to NSUserDefaults, you should save it to a different file, this will be much more efficient.
Here is how you can do it :
// Build file url
let documentsURL = NSFileManager.defaultManager().URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask).last!
let fileURL = documentsURL.URLByAppendingPathComponent("file_1.json", isDirectory: false)
// Write
let jsonString = "..."
let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)
do {
try jsonData?.writeToURL(fileURL, options: NSDataWritingOptions())
} catch {
NSLog("Writing file to `\(fileURL)` failed with error : \(error)")
}
// Read
do {
let jsonData = try NSData(contentsOfURL: fileURL, options: NSDataReadingOptions())
let jsonString = String(data: jsonData, encoding: NSUTF8StringEncoding)
} catch {
NSLog("Reading file at url `\(fileURL)` failed with error : \(error)")
}
NSUserDefaults is not a database. If your JSON documents are more than 100 KB altogether store them into separate documents. Note that every time you change any user default, all the user defaults have to be written to a file. If you have 20 JSON documents of 1MB each, that's writing 20MB or more for every user default the you change.

How to save NSData as gif to Album

I use function here https://gist.github.com/westerlund/eae8ec71cdac88be7c3a
to create a gif file of an array of images, but the return type is NSData.
How can I use this data and save it to album?
I have tried UIImageWriteToSavedPhotosAlbum. It's not work, it only saved the first image of the image array. I have googled a function named writeimagedatatosavedphotosalbum, but it's already been deprecated in iOS9.
So what's the latest way the save a .gif file to album?
First Get a file URL
guard let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
let fileURL = documentsDirectory.appendingPathComponent("myGifFile.gif")
Write gifData to File URL
try? gifData.write(to: fileURL)
Save the file from URL to Album
PHPhotoLibrary.shared().performChanges ({
PHAssetChangeRequest.creationRequestForAssetFromImage(atFileURL: fileURL)
}) { saved, error in
if saved {
print("Your image was successfully saved")
}
}

Resources