Start a new topic
Answered

How to Upload UIImage to Collection in Swift 2

 Hi,


I am trying to do something similar to how Parse arranges data sets with different types in a collection and I am having trouble with uploading UIImage file. I am confused with the guide and API reference and don't quite understand what they meant. I am coding in Swift 2 with Xcode 7.2.1 and KinveyKit 1.40.7.


My first code:

class Image : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable
    var entityId: String? //Kinvey entity _id
    var image: UIImage?
    var senderUsername: String?
    var recipientUsername: String?
    
    override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
        return [
            "entityId" : KCSEntityKeyId, //the required _id field
            "image" : "image",
            "senderUsername" : "senderUsername",
            "recipientUsername" : "recipientUsername"
        ]
    }
}

KCSAppdataStore successfully saves the 3 entities, except the UIImage. The "Data Store" guide says that UIImage is an accepted data type but with the following note: "If there is a UIImage property and the object is saved with a KCSLinkedAppdataStore a file will be created and linked to the entity. See Automatically Linking Images for more information."

Does that mean that KCSLinkedAppdataStore is required for UIImage? The note says that 'IF' it is used but doesn't say it's required.

The 'KCSLinkedAppdataStore Class Reference' says that 'To make use of this, have an entity map a UIImage property to the KCSFileStoreCollectionName in - [KCSPersistable hostToKinveyPropertyMapping], and save that entity.'


So with that, I modified my class to:

 

class Image : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable
    var entityId: String? //Kinvey entity _id
    var image: UIImage?
    var senderUsername: String?
    var recipientUsername: String?
    
    override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
        return [
            "entityId" : KCSEntityKeyId, //the required _id field
            "image" : KCSFileStoreCollectionName,
            "senderUsername" : "senderUsername",
            "recipientUsername" : "recipientUsername"
        ]
    }
    
    func kinveyPropertyToCollectionMapping() -> [NSObject : AnyObject]! {

        return [
            "image" : "image"
        ]
    }
    
}

 

 and change the saving method to:

var imageToSend = KCSLinkedAppdataStore.storeWithOptions([
            KCSStoreKeyCollectionName : "Images",
            KCSStoreKeyCollectionTemplateClass : Image.self
            ])
    
        
        let imageSendingData = Image()
        imageSendingData.image = UIImage(data: UIImageJPEGRepresentation(image, 0.1)!)
        imageSendingData.senderUsername = KCSUser.activeUser().username
        imageSendingData.recipientUsername = userArray[activeRecipient]
        
        imageToSend.saveObject(imageSendingData, withCompletionBlock: { (objectsOrNil, errorOrNil) -> Void in
            
            if errorOrNil != nil {
                
                print("Error saving image: \(errorOrNil)")
                
            } else {
                
                print("Save image successful")
                
            }
            
            }, withProgressBlock: nil)

 Still doesn't work. Then changing the class to:

class Image : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable
    var entityId: String? //Kinvey entity _id
    var image: UIImage?
    var senderUsername: String?
    var recipientUsername: String?
    
    override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
        return [
            "entityId" : KCSEntityKeyId, //the required _id field
            "image" : "image",
            "senderUsername" : "senderUsername",
            "recipientUsername" : "recipientUsername"
        ]
    }
    
    func kinveyPropertyToCollectionMapping() -> [NSObject : AnyObject]!{
        
        return [
            "image" : KCSFileStoreCollectionName
        ]
    }
    
}

 

 Now the collection has a new column called '_blob' but the value is null.


What is the correct way of uploading images in a collection?


Thanks.


Best Answer

Sam,


Following are the steps for uploading images:



Step 1: Define the class :

    class Image : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable
        var entityId: String! //Kinvey entity _id
        var image: UIImage!
    }

Step 2: Override the hostToKinveyPropertyMapping & kinveyPropertyToCollectionMapping method:
   
        override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
            return [
                "entityId" : KCSEntityKeyId, //the required _id field
                "image" : "image"
            ]
        }
       
        override class func kinveyPropertyToCollectionMapping() -> [NSObject : AnyObject]! {
           
            return [
                "image" : KCSFileStoreCollectionName
            ]
        }
   
Step 3: Initialize store object with KCSLinkedAppDataStore object.

        var collection = KCSCollection(fromString: "Images", ofClass:Image.self)
       
        var imageToSend = KCSLinkedAppdataStore.storeWithOptions([
            KCSStoreKeyResource : collection,
            KCSStoreKeyCachePolicy : KCSCachePolicy.Both.rawValue,
            KCSStoreKeyOfflineUpdateEnabled : true
            ])

Step 4: Upload the Object as follows:

        let imageSendingData = Image()
        imageSendingData.image = UIImage(data: UIImageJPEGRepresentation(image, 0.5)!)
       
        imageToSend.saveObject(imageSendingData, withCompletionBlock: { (objectsOrNil, errorOrNil) -> Void in
            if errorOrNil != nil {
                print("Error saving image: \(errorOrNil)")
            } else {
                print("Save image successful")
            }
        }, withProgressBlock: nil)



In the Console, you should see a new entry created in that collection (with image column storing the _KinveyRef and the newly uploaded file should appear under “Console->Data->Files (Select Files instead of a collection)”).



Thanks,

Pranav

Kinvey Support

 


Hi Pranav,

Thanks for the reply but if you look at the code that you've provided, it's the same as mine. I don't know what to say but it just does not work. There is no reference to the image, it's just a _blob column with a null entry. The image is not uploaded to the Files storage.

As I've said, there is a workaround to this problem by creating my own reference to the object ID that points to a file in Files storage. That file has to be uploaded in a separate asynchronous thread.

Sorry for the delay in replying because during this time, unfortunately, I found another solution with a different vendor that works better for what I'm looking for. The solution that I am using right now requires no special code to upload an image (or any NSData file). I think Kinvey can also add that feature in the future perhaps?
Among these issues, my strongest objection to my experience with Kinvey has been its poor documentation. They require significant overhaul, testing and re-casting.

Thanks.

 

Answer

Sam,


Following are the steps for uploading images:



Step 1: Define the class :

    class Image : NSObject {    //all NSObjects in Kinvey implicitly implement KCSPersistable
        var entityId: String! //Kinvey entity _id
        var image: UIImage!
    }

Step 2: Override the hostToKinveyPropertyMapping & kinveyPropertyToCollectionMapping method:
   
        override func hostToKinveyPropertyMapping() -> [NSObject : AnyObject]! {
            return [
                "entityId" : KCSEntityKeyId, //the required _id field
                "image" : "image"
            ]
        }
       
        override class func kinveyPropertyToCollectionMapping() -> [NSObject : AnyObject]! {
           
            return [
                "image" : KCSFileStoreCollectionName
            ]
        }
   
Step 3: Initialize store object with KCSLinkedAppDataStore object.

        var collection = KCSCollection(fromString: "Images", ofClass:Image.self)
       
        var imageToSend = KCSLinkedAppdataStore.storeWithOptions([
            KCSStoreKeyResource : collection,
            KCSStoreKeyCachePolicy : KCSCachePolicy.Both.rawValue,
            KCSStoreKeyOfflineUpdateEnabled : true
            ])

Step 4: Upload the Object as follows:

        let imageSendingData = Image()
        imageSendingData.image = UIImage(data: UIImageJPEGRepresentation(image, 0.5)!)
       
        imageToSend.saveObject(imageSendingData, withCompletionBlock: { (objectsOrNil, errorOrNil) -> Void in
            if errorOrNil != nil {
                print("Error saving image: \(errorOrNil)")
            } else {
                print("Save image successful")
            }
        }, withProgressBlock: nil)



In the Console, you should see a new entry created in that collection (with image column storing the _KinveyRef and the newly uploaded file should appear under “Console->Data->Files (Select Files instead of a collection)”).



Thanks,

Pranav

Kinvey Support

 

Hi Pranav,

I don't know what to say... The StatusShare example is the same method I used in the last code I attached above. It does not work with Swift. The image column is always null. If you compare the code I attached above, please let me know if there is anything wrong. I don't know much about Objective-C but looking at the example you provided, they are both the same.

Furthermore, as I've stated above, the API documentation for KCSLinkedAppdataStore is incorrect.

 

Sam,

Please refer to the following sample app (it demonstrates the use of KCSLinkedAppdataStore with uploading images):

http://devcenter.kinvey.com/ios/samples/statusshare

Take a look at StatusShareUpdate.h,  StatusShareUpdate.m and WriteUpdateViewController.m which uses KCSLinkedAppdataStore.

Thanks,
Pranav

Kinvey Support

Hello, is there anyone who can shed light into this? I believe the documentation is incorrect.

Currently, I applied a workaround by storing the binary files in the Files data store and keep a reference to the id name in the working store. But this is not elegant, very slow and requires nested asynchronous container blocks inside Swift.
What is the proper way to store UIImage in the AppdataStore?

Thanks.

 

Login or Signup to post a comment