Merge folders with NSFileManager, overwrite only existing files - ios

Basically I am looking for a way to merge two folder in the filesystem with the cocoa API:
I have a folder containing files and sub-folders, which I want to copy to a different location in the filesystem.
At my destination path, an equally-named folder already exists, which may contain files and folders as well.
Now I want to overwrite existing files inside my destination folder (or its subfolders) with the new content of my source folder if they have the same name.
All the rest of the files I want to leave untouched.
sourcefolder
|
- file1
- subfolder
- file2
destinationfolder
|
- file3
- subfolder
- file2
- file4
resultingfolder
|
- file1
- file3
- subfolder
- file2 <-- version from source folder
- file4
How can I do that?
Thanks a lot for your help!

I searched everywhere but found nothing. So I came up with my own solution, utilizing NSDirectoryEnumerator. This should work the diagram (overriding old files). Hope it helps.
- (void)mergeContentsOfPath:(NSString *)srcDir intoPath:(NSString *)dstDir error:(NSError**)err {
NSLog(#"- mergeContentsOfPath: %#\n intoPath: %#", srcDir, dstDir);
NSFileManager *fm = [NSFileManager defaultManager];
NSDirectoryEnumerator *srcDirEnum = [fm enumeratorAtPath:srcDir];
NSString *subPath;
while ((subPath = [srcDirEnum nextObject])) {
NSLog(#" subPath: %#", subPath);
NSString *srcFullPath = [srcDir stringByAppendingPathComponent:subPath];
NSString *potentialDstPath = [dstDir stringByAppendingPathComponent:subPath];
// Need to also check if file exists because if it doesn't, value of `isDirectory` is undefined.
BOOL isDirectory = ([[NSFileManager defaultManager] fileExistsAtPath:srcFullPath isDirectory:&isDirectory] && isDirectory);
// Create directory, or delete existing file and move file to destination
if (isDirectory) {
NSLog(#" create directory");
[fm createDirectoryAtPath:potentialDstPath withIntermediateDirectories:YES attributes:nil error:err];
if (err && *err) {
NSLog(#"ERROR: %#", *err);
return;
}
}
else {
if ([fm fileExistsAtPath:potentialDstPath]) {
NSLog(#" removeItemAtPath");
[fm removeItemAtPath:potentialDstPath error:err];
if (err && *err) {
NSLog(#"ERROR: %#", *err);
return;
}
}
NSLog(#" moveItemAtPath");
[fm moveItemAtPath:srcFullPath toPath:potentialDstPath error:err];
if (err && *err) {
NSLog(#"ERROR: %#", *err);
return;
}
}
}
}

A solution in Swift 3
let merger = FoldersMerger(actionType: .copy, conflictResolution: .keepSource)
merger.merge(atPath: sourceFolder, toPath: destinationFolder)
class FoldersMerger {
enum ActionType { case move, copy }
enum ConflictResolution { case keepSource, keepDestination }
private let fileManager = FileManager()
private var actionType: ActionType!
private var conflictResolution: ConflictResolution!
private var deleteEmptyFolders: Bool!
init(actionType: ActionType = .move, conflictResolution: ConflictResolution = .keepDestination, deleteEmptyFolders: Bool = false) {
self.actionType = actionType
self.conflictResolution = conflictResolution
self.deleteEmptyFolders = deleteEmptyFolders
}
func merge(atPath: String, toPath: String) {
let pathEnumerator = fileManager.enumerator(atPath: atPath)
var folders: [String] = [atPath]
while let relativePath = pathEnumerator?.nextObject() as? String {
let subItemAtPath = URL(fileURLWithPath: atPath).appendingPathComponent(relativePath).path
let subItemToPath = URL(fileURLWithPath: toPath).appendingPathComponent(relativePath).path
if isDir(atPath: subItemAtPath) {
if deleteEmptyFolders! {
folders.append(subItemAtPath)
}
if !isDir(atPath: subItemToPath) {
do {
try fileManager.createDirectory(atPath: subItemToPath, withIntermediateDirectories: true, attributes: nil)
NSLog("FoldersMerger: directory created: %#", subItemToPath)
}
catch let error {
NSLog("ERROR FoldersMerger: %#", error.localizedDescription)
}
}
else {
NSLog("FoldersMerger: directory %# already exists", subItemToPath)
}
}
else {
if isFile(atPath:subItemToPath) && conflictResolution == .keepSource {
do {
try fileManager.removeItem(atPath: subItemToPath)
NSLog("FoldersMerger: file deleted: %#", subItemToPath)
}
catch let error {
NSLog("ERROR FoldersMerger: %#", error.localizedDescription)
}
}
do {
try fileManager.moveItem(atPath: subItemAtPath, toPath: subItemToPath)
NSLog("FoldersMerger: file moved from %# to %#", subItemAtPath, subItemToPath)
}
catch let error {
NSLog("ERROR FoldersMerger: %#", error.localizedDescription)
}
}
}
if deleteEmptyFolders! {
folders.sort(by: { (path1, path2) -> Bool in
return path1.characters.split(separator: "/").count < path2.characters.split(separator: "/").count
})
while let folderPath = folders.popLast() {
if isDirEmpty(atPath: folderPath) {
do {
try fileManager.removeItem(atPath: folderPath)
NSLog("FoldersMerger: empty dir deleted: %#", folderPath)
}
catch {
NSLog("ERROR FoldersMerger: %#", error.localizedDescription)
}
}
}
}
}
private func isDir(atPath: String) -> Bool {
var isDir: ObjCBool = false
let exist = fileManager.fileExists(atPath: atPath, isDirectory: &isDir)
return exist && isDir.boolValue
}
private func isFile(atPath: String) -> Bool {
var isDir: ObjCBool = false
let exist = fileManager.fileExists(atPath: atPath, isDirectory: &isDir)
return exist && !isDir.boolValue
}
private func isDirEmpty(atPath: String) -> Bool {
do {
return try fileManager.contentsOfDirectory(atPath: atPath).count == 0
}
catch _ {
return false
}
}
}

Look at the file manager methods and instead of using the default file manager, create your own with alloc/init, set a delegate, and use the delegate methods.

Related

How check directory exist?

I have functions to create directorys:
func createSystemFolders(){
// Create a FileManager instance
let fileManager = FileManager.default
do {
try fileManager.createDirectory(atPath: "json", withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError {
debugPrint("\(ErrorsLabels.AppDelegate01): \(error)")
}
do {
try fileManager.createDirectory(atPath: "inspirations", withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError {
debugPrint("\(ErrorsLabels.AppDelegate02): \(error)")
}
do {
try fileManager.createDirectory(atPath: "products", withIntermediateDirectories: true, attributes: nil)
}
catch let error as NSError {
debugPrint("\(ErrorsLabels.AppDelegate03): \(error)")
}
}
I need second function to check directory exist.
Haw can I check it?
You can use this,
fileprivate func directoryExistsAtPath(_ path: String) -> Bool {
var isdirectory : ObjCBool = true
let exists = FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory)
return exists && isDirectory.boolValue
}
You can do smarter solution Like that without Two function completion(isExit,directoryURL) :
And simple use it in one line :
self.createSystemFolders("json") { (isExit, url) in
print(isExit)
print(url)
}
CreateSystemFolders:
func createSystemFolders(_ folderName:String ,_ completion:(_ isExit:Bool?,_ directoryURL:URL?) -> Void){
let paths = NSSearchPathForDirectoriesInDomains(.applicationSupportDirectory, .userDomainMask, true)
let directory = paths[0]
let fileManager = FileManager.default
let url = URL(fileURLWithPath: directory).appendingPathComponent(folderName)
if !fileManager.fileExists(atPath: url.path) {
do {
try fileManager.createDirectory(at: url, withIntermediateDirectories: true, attributes: nil)
completion(false,url)
}
catch {
print("Error: Unable to create directory: \(error)")
completion(nil,nil)
}
var url = URL(fileURLWithPath: directory)
var values = URLResourceValues()
values.isExcludedFromBackup = true
do {
try url.setResourceValues(values)
completion(false,url)
}
catch {
print("Error: Unable to exclude directory from backup: \(error)")
completion(nil,nil)
}
}else{
completion(true,url)
}
}
Try this:
let fileManager = FileManager.default
var isdirectory = true
if fileManager.fileExists(atPath: fullPath, isDirectory:&isdirectory) {
if isdirectory.boolValue {
// file exists and is a directory
} else {
// file exists and is not a directory
}
} else {
// file does not exist
}
here how you can
1 Find a Documents directory on device
2 Check if file exists at specified file path
let fileNameToDelete = "myFileName.txt"
var filePath = ""
// Fine documents directory on device
let dirs : [String] = NSSearchPathForDirectoriesInDomains(FileManager.SearchPathDirectory.documentDirectory, FileManager.SearchPathDomainMask.allDomainsMask, true)
if dirs.count > 0 {
let dir = dirs[0] //documents directory
filePath = dir.appendingFormat("/" + fileNameToDelete)
print("Local path = \(filePath)")
} else {
print("Could not find local directory to store file")
return
}
let fileManager = FileManager.default
// Check if file exists
if fileManager.fileExists(atPath: filePath) {
print("File exists")
} else {
print("File does not exist")
}

iOS - Best practices for FileManager extensions

I created this FileManager extension. With this extension, I want to create a file hierarchy like so:
Application Support
Favorites
Feed
Images
This is the code I have in FileManager extension which I would call in app delegate as soon as the app launches. Then I would use this code to always retrieve the path's of the folders.
Is this a good way to create this hierarchy and retrieve the paths when I need them? Is this good practice?
extension FileManager {
static func createOrFindApplicationDirectory() -> URL? {
let bundleID = Bundle.main.bundleIdentifier
// Find the application support directory in the home directory.
let appSupportDir = self.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard appSupportDir.count > 0 else {
return nil
}
// Append the bundle ID to the URL for the Application Support directory.
let dirPath = appSupportDir[0].appendingPathComponent(bundleID!)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Application Support directory with error: \(error)")
return nil
}
}
static func createOrFindFavoritesDirectory() -> URL? {
guard let appSupportDir = createOrFindApplicationDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent("Favorites")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Favorites directory with error: \(error)")
return nil
}
}
static func createOrFindFeedDirectory() -> URL? {
guard let appSupportDir = createOrFindFavoritesDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent("Feed")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Favorites directory with error: \(error)")
return nil
}
}
static func currentImagesDirectory() -> URL? {
guard let feedDir = createOrFindFeedDirectory() else {
return nil
}
let dirPath = feedDir.appendingPathComponent("Images")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Images directory with error: \(error)")
return nil
}
}
}
It looks pretty good, but you could combine a bit of the code and have better error checking:
extension FileManager {
static func createOrFindApplicationDirectory() -> URL? {
guard let bundleID = Bundle.main.bundleIdentifier else {
return nil
}
// Find the application support directory in the home directory.
let appSupportDirArray = self.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
guard let appSupportDir = appSupportDirArray.first else {
return nil
}
// Append the bundle ID to the URL for the Application Support directory.
let dirPath = appSupportDir.appendingPathComponent(bundleID)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Application Support directory with error: \(error)")
return nil
}
}
static func createOrFindDirectory(named name: String) -> URL? {
guard let appSupportDir = createOrFindApplicationDirectory() else {
return nil
}
let dirPath = appSupportDir.appendingPathComponent(name)
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating \(name) directory with error: \(error)")
return nil
}
}
static func currentImagesDirectory() -> URL? {
guard let feedDir = createOrFindDirectory(named: "Feed") else {
return nil
}
let dirPath = feedDir.appendingPathComponent("Images")
// If the directory does not exist, this method creates it.
do {
try self.default.createDirectory(at: dirPath, withIntermediateDirectories: true, attributes: nil)
return dirPath
} catch let error {
print("Error creating Images directory with error: \(error)")
return nil
}
}
}

Copy Sqlite DataBase in Swift does not work properly

I am using following code for objective c to copy the sqlite database and it works fine. But when I convert this code to swift it shows error on Bool type.
Here is objective c code
- (void) copyDatabaseIfNeeded {
//Using NSFileManager we can perform many file system operations.
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error;
NSString *dbPath = [self getDBPath];
BOOL success = [fileManager fileExistsAtPath:dbPath];
if(!success) {
NSString *defaultDBPath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:#"database.sqlite"];
success = [fileManager copyItemAtPath:defaultDBPath toPath:dbPath error:&error];
if (!success)
NSAssert1(0, #"Failed to create writable database file with message '%#'.", [error localizedDescription]);
}
}
- (NSString *) getDBPath
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory , NSUserDomainMask, YES);
NSString *documentsDir = [paths objectAtIndex:0];
return [documentsDir stringByAppendingPathComponent:#"database.sqlite"];
}
Here is the CopyDataBase for Swift which is causing issue.
var fileManager = FileManager.default
var error: Error!
var dbPath = self.getDBPath()
var success = fileManager.fileExists(atPath: dbPath)
if !success {
var defaultDBPath = URL(fileURLWithPath: Bundle.main.resourcePath!).appendingPathComponent("CapalinoDataBase.sqlite").absoluteString
do {
success = try fileManager.copyItem(atPath: defaultDBPath, toPath: dbPath)
}
catch {
}
if !success {
assert(false, "Failed to create writable database file with message '\(error.localizedDescription)'.")
}
}
Please try this one.
func copyDatabse() {
let fileMgr = FileManager.default
if let path = Bundle.main.path(forResource: "db", ofType:"sqlite") {
do {
try fileMgr.copyItem(atPath: path, toPath: dbPath())
print("Copy success")
}
catch {
print(error.localizedDescription )
}
}
}
func dbPath() -> String {
let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
let docsDir = dirPaths[0]
let destPath = (docsDir as NSString).appendingPathComponent("/db.sqlite")
return destPath
}
Best way to use SQLIte using single ton class in swift.
Download example
func methodToCreateDatabase() -> NSURL? {
let fileManager = NSFileManager.defaultManager()
let urls = fileManager.URLsForDirectory(.DocumentDirectory, inDomains: .UserDomainMask)
if let documentDirectory:NSURL = urls.first { // No use of as? NSURL because let urls returns array of NSURL
// exclude cloud backup
do {
try documentDirectory.setResourceValue(true, forKey: NSURLIsExcludedFromBackupKey)
} catch _{
print("Failed to exclude backup")
}
// This is where the database should be in the documents directory
let finalDatabaseURL = documentDirectory.URLByAppendingPathComponent("contact.db")
if finalDatabaseURL.checkResourceIsReachableAndReturnError(nil) {
// The file already exists, so just return the URL
return finalDatabaseURL
} else {
// Copy the initial file from the application bundle to the documents directory
if let bundleURL = NSBundle.mainBundle().URLForResource("contact", withExtension: "db") {
do {
try fileManager.copyItemAtURL(bundleURL, toURL: finalDatabaseURL)
} catch _ {
print("Couldn't copy file to final location!")
}
} else {
print("Couldn't find initial database in the bundle!")
}
}
} else {
print("Couldn't get documents directory!")
}
return nil
}
Please try this one it is working on swift 3.0
func copyDatabaseIfNeeded() {
//Using NSFileManager we can perform many file system operations.
let fileManager = FileManager.default
let error: Error?
let dbPath: String = self.getDBPath()
var success: Bool = fileManager.fileExists(atPath: dbPath)
if !success {
let defaultDBPath: String = URL(fileURLWithPath: (Bundle.main.resourcePath)!).appendingPathComponent("database.sqlite").absoluteString
do {
success = try fileManager.copyItem(atPath: defaultDBPath, toPath: dbPath) as Any as! Bool
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
if !success {
assert(false, "Failed to create writable database file with message '\(error?.localizedDescription)'.")
}
}
}
func getDBPath() -> String {
let paths: [Any] = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)
let documentsDir: String = paths[0] as! String
return URL(fileURLWithPath: documentsDir).appendingPathComponent("database.sqlite").absoluteString
}

How to properly send an image to CloudKit as CKAsset?

I have an image (UIImage and it's url too) and I'm trying to send it to CloudKit as a CKAsset but I'm having this error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Non-file URL'. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
send2Cloud()
}
func send2Cloud() {
let newUser = CKRecord(recordType: "User")
let url = NSURL(string: self.photoURL)
let asset = CKAsset(fileURL: url!)
newUser["name"] = self.name
newUser["photo"] = asset
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(newUser, completionHandler: { (record: CKRecord?, error: NSError?) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("User saved")
})
} else {
print(error?.localizedDescription)
}
})
}
I have the URL, I can print it, copy and paste to my navigator and it will show my image! So, I don't know what is happening here...
It would be easier if I worked with an UIImage instead of it's URL? Because, as I sais before, I have both of them! Any help is very appreciated! Thanks, guys!!
In my experience, the only way to save upload UIImage as a CKAsset is to:
Save the image temporarily to disk
Create the CKAsset
Delete the temporary file
let data = UIImagePNGRepresentation(myImage); // UIImage -> NSData, see also UIImageJPEGRepresentation
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString+".dat")
do {
try data!.writeToURL(url, options: [])
} catch let e as NSError {
print("Error! \(e)");
return
}
newUser["photo"] = CKAsset(fileURL: url)
// ...
publicData.saveRecord(newUser, completionHandler: { (record: CKRecord?, error: NSError?) in
// Delete the temporary file
do { try NSFileManager.defaultManager().removeItemAtURL(url) }
catch let e { print("Error deleting temp file: \(e)") }
// ...
}
I filed a bug report a few months ago requesting the ability to initialize CKAsset from in-memory NSData, but it hasn't been done yet.
This is Objective C version of how to save an image to Cloudkit
This took quite a bit of digging as there is not much info to go on, but this works
if([results count] <= 0) {
NSLog(#"this Record doesnt exist so add it ok!! %#", error);
CKRecordID *wellKnownID = [[CKRecordID alloc]
initWithRecordName:idString];
CKRecord *entitiesName = [[CKRecord alloc] initWithRecordType:#"mySavedDetails"
recordID:wellKnownID];
[entitiesName setObject:idString
forKey:#"myDetailsId"];
[entitiesName setObject:self.myName.text
forKey:#"myName"];
if (myUIImage.image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
#"test.png" ];
NSData* data = UIImagePNGRepresentation(myUIImage.image.image);
[data writeToFile:path atomically:YES];
//so we get the full path of the uiimage
NSLog(#"Path details %#",path);
NSURL* myImagePath = nil;
myImagePath =
[[NSBundle mainBundle] URLForResource:path
withExtension:#"png"];
//here we change the path of Image which is a string to a URL
NSURL *yourURL = [NSURL fileURLWithPath:path];
CKAsset* myImageAsset = nil;
myImageAsset =
[[CKAsset alloc] initWithFileURL:yourURL];
[entitiesName setObject: myImageAsset
forKey:#"myImage"];
[publicDatabase saveRecord: entitiesName
completionHandler:^(CKRecord *savedState, NSError *error) {
if (error) {
NSLog(#"ERROR SAVING: %#", error);
}
}];
}
}
I did something a tad different: I made a class that you can use in multiple places, and thanks to the fact that Swift has deinitialization that works (unlike C++), it cleans up after itself:
//
// ImageAsset.swift
//
import CloudKit
import UIKit
class ImageAsset {
let image:UIImage
var url:NSURL?
var asset:CKAsset? {
get {
let data = UIImagePNGRepresentation(self.image)
self.url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString+".dat")
if let url = self.url {
do {
try data!.writeToURL(url, options: [])
} catch let e as NSError {
print("Error! \(e)")
}
return CKAsset(fileURL: url)
}
return nil
}
}
init(image:UIImage){
self.image = image
}
deinit {
if let url = self.url {
do {
try NSFileManager.defaultManager().removeItemAtURL(url) }
catch let e {
print("Error deleting temp file: \(e)")
}
}
}
}
Here's a unit test that exercises it (presumes there is an image named stopwatch in the test target):
//
// ImageExtensionTests.swift
//
import CloudKit
import XCTest
#testable import BudgetImpactEstimator
class ImageExtensionTests: XCTestCase {
let testImageName = "stopwatch" // provide the name of an image in test bundle
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testConvertingImageToAsset() {
guard let image = UIImage(named: self.testImageName) else {
XCTFail("failed to load image")
return
}
let imageAsset = ImageAsset(image: image)
XCTAssertNotNil(imageAsset)
guard let asset = imageAsset.asset else {
XCTFail("failed to get asset from image")
return
}
print("constructed asset: \(asset)")
}
}
Was originally going to do it as an extension on UIImage but then the deinit made me move to a class.

Swift 3: Upload Image to cloudkit [duplicate]

I have an image (UIImage and it's url too) and I'm trying to send it to CloudKit as a CKAsset but I'm having this error: Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Non-file URL'. Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
send2Cloud()
}
func send2Cloud() {
let newUser = CKRecord(recordType: "User")
let url = NSURL(string: self.photoURL)
let asset = CKAsset(fileURL: url!)
newUser["name"] = self.name
newUser["photo"] = asset
let publicData = CKContainer.defaultContainer().publicCloudDatabase
publicData.saveRecord(newUser, completionHandler: { (record: CKRecord?, error: NSError?) in
if error == nil {
dispatch_async(dispatch_get_main_queue(), { () -> Void in
print("User saved")
})
} else {
print(error?.localizedDescription)
}
})
}
I have the URL, I can print it, copy and paste to my navigator and it will show my image! So, I don't know what is happening here...
It would be easier if I worked with an UIImage instead of it's URL? Because, as I sais before, I have both of them! Any help is very appreciated! Thanks, guys!!
In my experience, the only way to save upload UIImage as a CKAsset is to:
Save the image temporarily to disk
Create the CKAsset
Delete the temporary file
let data = UIImagePNGRepresentation(myImage); // UIImage -> NSData, see also UIImageJPEGRepresentation
let url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString+".dat")
do {
try data!.writeToURL(url, options: [])
} catch let e as NSError {
print("Error! \(e)");
return
}
newUser["photo"] = CKAsset(fileURL: url)
// ...
publicData.saveRecord(newUser, completionHandler: { (record: CKRecord?, error: NSError?) in
// Delete the temporary file
do { try NSFileManager.defaultManager().removeItemAtURL(url) }
catch let e { print("Error deleting temp file: \(e)") }
// ...
}
I filed a bug report a few months ago requesting the ability to initialize CKAsset from in-memory NSData, but it hasn't been done yet.
This is Objective C version of how to save an image to Cloudkit
This took quite a bit of digging as there is not much info to go on, but this works
if([results count] <= 0) {
NSLog(#"this Record doesnt exist so add it ok!! %#", error);
CKRecordID *wellKnownID = [[CKRecordID alloc]
initWithRecordName:idString];
CKRecord *entitiesName = [[CKRecord alloc] initWithRecordType:#"mySavedDetails"
recordID:wellKnownID];
[entitiesName setObject:idString
forKey:#"myDetailsId"];
[entitiesName setObject:self.myName.text
forKey:#"myName"];
if (myUIImage.image != nil)
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:
#"test.png" ];
NSData* data = UIImagePNGRepresentation(myUIImage.image.image);
[data writeToFile:path atomically:YES];
//so we get the full path of the uiimage
NSLog(#"Path details %#",path);
NSURL* myImagePath = nil;
myImagePath =
[[NSBundle mainBundle] URLForResource:path
withExtension:#"png"];
//here we change the path of Image which is a string to a URL
NSURL *yourURL = [NSURL fileURLWithPath:path];
CKAsset* myImageAsset = nil;
myImageAsset =
[[CKAsset alloc] initWithFileURL:yourURL];
[entitiesName setObject: myImageAsset
forKey:#"myImage"];
[publicDatabase saveRecord: entitiesName
completionHandler:^(CKRecord *savedState, NSError *error) {
if (error) {
NSLog(#"ERROR SAVING: %#", error);
}
}];
}
}
I did something a tad different: I made a class that you can use in multiple places, and thanks to the fact that Swift has deinitialization that works (unlike C++), it cleans up after itself:
//
// ImageAsset.swift
//
import CloudKit
import UIKit
class ImageAsset {
let image:UIImage
var url:NSURL?
var asset:CKAsset? {
get {
let data = UIImagePNGRepresentation(self.image)
self.url = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(NSUUID().UUIDString+".dat")
if let url = self.url {
do {
try data!.writeToURL(url, options: [])
} catch let e as NSError {
print("Error! \(e)")
}
return CKAsset(fileURL: url)
}
return nil
}
}
init(image:UIImage){
self.image = image
}
deinit {
if let url = self.url {
do {
try NSFileManager.defaultManager().removeItemAtURL(url) }
catch let e {
print("Error deleting temp file: \(e)")
}
}
}
}
Here's a unit test that exercises it (presumes there is an image named stopwatch in the test target):
//
// ImageExtensionTests.swift
//
import CloudKit
import XCTest
#testable import BudgetImpactEstimator
class ImageExtensionTests: XCTestCase {
let testImageName = "stopwatch" // provide the name of an image in test bundle
override func setUp() {
super.setUp()
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
super.tearDown()
}
func testConvertingImageToAsset() {
guard let image = UIImage(named: self.testImageName) else {
XCTFail("failed to load image")
return
}
let imageAsset = ImageAsset(image: image)
XCTAssertNotNil(imageAsset)
guard let asset = imageAsset.asset else {
XCTFail("failed to get asset from image")
return
}
print("constructed asset: \(asset)")
}
}
Was originally going to do it as an extension on UIImage but then the deinit made me move to a class.

Resources