Write UIImage to path with gps coordinate - ios

In my app i have created a custom camera viewcontroller and i would like to save the image in the documents folder under a specific path with the image's location info.
This is how i save the image now:
NSData* originalImageJpegRepresentation = UIImageJPEGRepresentation(self.cameraSnapshotImage, 90);
[originalImageJpegRepresentation writeToFile:[self.targetDirectory stringByAppendingPathComponent:filename] atomically:YES];
But the image doesn't contain the location info in its exif data after i read it.
I do have the geo location at the time i wish to save it in a CLLocation* object.
How can i add its coordinate to the saved image?
Thanks in advance

Create a custom subclass of NSObject and then use NSCoding to manually store and retrieve the image data and location data.
#interface MyImageData : NSObject <NSCoding>
#property (nonatomic, strong) UIImage *image;
#property (nonatomic, strong) CLLocation *location;
#end
#implementation MyImageData
...
#pragma mark NSCoding
- (void) encodeWithCoder:(NSCoder *)encoder {
NSData *imageData = UIImageJPEGRepresentation(_image, 90);
[encoder encodeObject:originalImageJpegRepresentation forKey:#"image"];
[encoder encodeObject:_location forKey:#"location"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
NSData *imageData = [decoder decodeObjectForKey:#"image"];
_image = = [[UIImage alloc] initWithData:imageData];
_location = [decoder decodeObjectForKey:#"location"];
}
return self;
}
Then you can save an instance of this class to file and it will include both data items.
MyImageData *myImageData = ...
[myImageData writeToFile:filePath atomically:YES];

You will need to use the ALAssetsLibrary to save the gps data using the writeImageToSavedPhotosAlbum method. Read up on it here

Related

How could I encode an Objective C object instance as a string?

I know there are various ways of archiving objects in objective-c, but is there some way which I can use to turn an object in Objective-C into a string which I can store in a txt file (amongst other data) and then extract from the file to recreate the same object instance again efficiently?
Thanks in advance
It's not recommended to do this.
Why don't you save the object into NSDefaults and extract if from there whenever you want? It's very simple and efficient.
Here is an example.
Let's say you have a User object defined as below
User.h
#interface User : NSObject
#property (nonatomic, strong) NSNumber *id;
#property (nonatomic, strong) NSString *first_name;
#property (nonatomic, strong) NSString *last_name;
#end
User.m
#import "User.h"
#implementation User
-(void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:id forKey:#"id"];
[encoder encodeObject:first_name forKey:#"first_name"];
[encoder encodeObject:last_name forKey:#"last_name"];
}
-(id)initWithCoder:(NSCoder *)decoder {
if (self = [super init]) {
self.id = [decoder decodeObjectForKey:#"id"];
self.first_name = [decoder decodeObjectForKey:#"first_name"];
self.last_name = [decoder decodeObjectForKey:#"last_name"];
}
}
- (id)initWith:(User *)obj {
if (self = [super init]) {
self.id = obj.id;
self.first_name = obj.first_name;
self.last_name = obj.last_name;
}
}
#end
Whenever you want to save the object, you can do something like that
//Save the user object
User *user = [[User alloc] init];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:user];
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:data forKey:#"YOUR_KEY"];
[defaults synchronize];
//Get the user object
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"YOUR_KEY"];
User *user = [[User alloc] initWith:[NSKeyedUnarchiver unarchiveObjectWithData:data]];

Save NSMutableArray of NSStrings to disk

I have a NSMutableaArray of NSString objects. So i'm using NSKeyedArchiever to save it to disk. So when i try to use
- (void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:self.EventsList forKey:#"Events"];
}
i got an error
Event encodeWithCoder:]: unrecognized selector sent to instance 0x7fd06b542780
Here's my parts of code:
//-------------------Events.h--------------------------
#interface Event : NSObject
#property (strong,nonatomic) NSString *nameOfEvent;
#property (strong,nonatomic) NSString *dateOfEvent;
#property (strong,nonatomic) NSString *placeOfEvent;
#property int priorityOfEvent;
#end
//---------------Singleton.h ----------------
#interface GlobalSingleton : NSObject <NSCoding, NSCopying> {
NSMutableArray *EventsList;
}
#property (nonatomic,retain) NSMutableArray *EventsList;
+(GlobalSingleton *)sharedFavoritesSingleton;
#end
//----------------Singleton.m------------------------
....
#implementation GlobalSingleton
#synthesize EventsList;
....
....
- (void)encodeWithCoder:(NSCoder *)aCoder {
NSLog (#"%#",EventsList); // not nil
[aCoder encodeObject:self.EventsList forKey:#"Events"];
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super init])) {
NSMutableArray *temp = [[NSMutableArray alloc] initWithArray:[aDecoder decodeObjectForKey:#"Events"]];
self.EventsList = temp;
}
return self;
}
- (id)copyWithZone:(NSZone *)zone {
GlobalSingleton *copy = [[GlobalSingleton allocWithZone:zone] init];
copy.EventsList = self.EventsList;
return copy;
}
#end
I get textdata from Web-server using ASIFormDataRequest in JSON format, and then i add this object to NSMutableArray, which is also a Singleton, so it looks like this:
NSDictionary *responseDict = [responseString JSONValue];
GlobalSingleton *Singleton = [GlobalSingleton sharedFavoritesSingleton];
for (NSDictionary *str in responseDict) {
Event *newEvent = [[Event alloc] init];
newEvent.nameOfEvent = [str objectForKey:#"EventName"];
newEvent.dateOfEvent = [str objectForKey:#"EventDate"];
newEvent.placeOfEvent = [str objectForKey:#"EventPlace"];
[Singleton.EventsList addObject:newEvent];
}
//------------------Save this data stored in NSMutableArray to disk-------------------------
[NSKeyedArchiver archiveRootObject:Singleton toFile:[self save_path]];
So, again, execution stops on this:
[aCoder encodeObject:self.EventsList forKey:#"Events"];
But when i try to code single NSString object everything goes with no errors.
eventList doesn't contain NSStrings, it contains Event objects.
Your Event class needs to implement encodeWithCoder: - as the exception message says, the Event class doesn't implement this method.
Also you should use a lowercase s for singleton as it is an instance, not a class, and you should probably not use singletons.

Having trouble saving NSMutable dictionary to documents. UIImage won't save/display

I have a sticker class and I am saving sticker objects to a NSMutableDictionary. Sticker class below:
#import "Sticker.h"
#implementation Sticker
-(instancetype)initWithTitle:(NSString *)title stickerNO:(int)stickerNO image:(UIImage *)image {
self=[super init];
if(self){
self.title=title;
self.stickerNO=[NSNumber numberWithInt:stickerNO];;
self.image=image;
}
return self;
}
//CODING METHODS//////////////
-(void)encodeWithCoder:(NSCoder *)aCoder{
//choose what we save, these are objects
[aCoder encodeObject:self.title forKey:#"title"];
[aCoder encodeObject:self.stickerNO forKey:#"stickerNO"];
[aCoder encodeObject:self.image forKey:#"image"];
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self=[super init];
if(self){
self.title=[aDecoder decodeObjectForKey:#"title"];
self.stickerNO=[aDecoder decodeObjectForKey:#"stickerNO"];
self.image=[aDecoder decodeObjectForKey:#"image"];
}
return self;
}
#end
The dictionary is managed by the class StickerManager:
#implementation StickerManager
-(instancetype)init {
self = [super init];
//load the dictionary
NSString *path = [self itemArchivePath];
self.stickerDictionary=[NSKeyedUnarchiver unarchiveObjectWithFile:path];
//if there is no dictionary create it and add the default
if(!self.stickerDictionary) {
NSLog(#"Creating dictionary and adding default");
self.stickerDictionary=[[NSMutableDictionary alloc] init];
[self addDefaultStickers];
[self saveStickerDictionary];
}
//if empty fill it
else if ([self.stickerDictionary count]==0){
NSLog(#"Dictionary exists but it empty");
[self addDefaultStickers];
[self saveStickerDictionary];
}
return self;
}
//add the stickers included in the app bundle
-(void)addDefaultStickers {
Sticker *sticker = [[Sticker alloc] initWithTitle:#“Batman” stickerNO:1 image:[UIImage imageNamed:#“batman.png"]];
[self.stickerDictionary setObject:sticker forKey:sticker.title];
}
-(BOOL)saveStickerDictionary{
NSLog(#"Saving stickers");
//get the path from above
NSString *path = [self itemArchivePath];
return [NSKeyedArchiver archiveRootObject:self.stickerDictionary toFile:path];
}
-(NSString *)itemArchivePath
{
//get the directory
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//set it to a string
NSString *documentDirectory = [documentDirectories firstObject];
//here we call the file items.archive
return [documentDirectory stringByAppendingPathComponent:#"stickers.archive"];
}
#end
If when the StickerManager is init if it is empty or doesn't exist it will create and fill the dictionary with the default stickers by calling addDefaultStickers. This is working for the one sticker I have in the code. I can load and restore the dictionary and use NSLog to check the contents. The sticker is there but for some reason the UIImage is null and I can't display it. I'm really not sure why, I have used the encodeWithCoder for it so shouldn't it work? The odd thing is that if I download a sticker from Parse.com (I have an identical class on it) and then convert that image from NSData to png and save it will work. Could someone give me some pointers please to what might be going wrong here? Thanks
EDIT This is my download from Parse.com code:
- (void)getNewStickersWithCompletionHandler:(stickerCompletionHandler)handler
{
__weak StickerManager *weakSelf = self;
PFQuery *stickersQuery = [PFQuery queryWithClassName:#"sticker"];
[stickersQuery findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if(!error) {
for( PFObject *object in objects) {
NSString *title = object[#"title"];
int stickerNO = [[object objectForKey:#"stickerNO"] intValue];
//DOWNLOAD IMAGE CODE
PFFile *image = object[#"image"];
[image getDataInBackgroundWithBlock:^(NSData *data, NSError *error) {
if(!error){
UIImage *image = [UIImage imageWithData:data];
Sticker *sticker = [[Sticker alloc] initWithTitle:title stickerNO:stickerNO image:image];
[weakSelf.stickerDictionary setObject:sticker forKey:sticker.title];
[self saveStickerDictionary];
handler(YES,nil);
}
}];//end get image block
}//end for
}
}];//end download stickers block
}
If I save a sticker to the dictionary this way there are no issues and I can display the image. This is almost identical to the addDefaultStickers, I am saving a UIImage and not saving the NSData. No Idea what is up here..
just for other people's reference the problem was in not first converting the UIImage to NSData. I changed UIImage lines in the aCoder method to below:
[aCoder encodeObject:UIImagePNGRepresentation(self.image) forKey:#"image"];
and in the aDecoder: self.image = [UIImage imageWithData:[aDecoder decodeObjectForKey:#"image"]];
don't totally understand why it was working for NSData that was converted to a UIImage for the Parse download before but it's all working now anyway.

NSCoding data not saved when returning to the app?

I don't understand why previousArrays returns (null), I would like to save a class containing a bezier path and its color.
The code : (after touchesEnded is called, a path is created and saved in memory. When I come back to the app with initWithCoder, previousArrays is (null) ) :
-(id)initWithCoder:(NSCoder *)aDecoder{
if ( !(self=[super initWithCoder:aDecoder])) return nil;
CGContextRef context = UIGraphicsGetCurrentContext();
NSArray *previousArrays = [SaveData loadDict];
NSLog(#"previousArrays : %#", previousArrays);//***HERE*** : return (null)
for ( id object in previousArrays){
//for ( NSDictionary*dict in previousArrays){
NSLog(#"obj %#", [[object class] description]);
//...
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:myPath];//nscoding compliant
DataForPath *firstPath = [[DataForPath alloc] init];
firstPath.path = bezierPath;
firstPath.colorInArray = #(currentColor);
NSLog(#"touchEnded, firstPath : %#", firstPath);
NSDictionary *dict = #{#"firstPath":firstPath};
[SaveData saveDict:dict];
}
#implementation SaveData
static NSString* kMyData = #"data1";
+ (void) saveDict:(NSDictionary*) dict{
NSLog(#"saving data...");
//retrieve previous data
NSData *previousData = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
NSMutableArray *previousArray = [[NSKeyedUnarchiver unarchiveObjectWithData:previousData] mutableCopy];
[previousArray addObject:dict];
//add new data, and save
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:previousArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:kMyData];
//NSLog(#"%#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
}
+(NSArray*) loadDict {
NSLog(#"loading data...");
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:kMyData];
NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithData:data];
return array;
}
#import <Foundation/Foundation.h>
#import UIKit;
#interface DataForPath : NSObject <NSCoding>
#property (nonatomic, strong) UIBezierPath* path;
#property (nonatomic, strong) NSNumber *colorInArray;
#end
#implementation DataForPath
- (id)initWithCoder:(NSCoder *)decoder{
if ( !(self = [super init]) ) return nil;
self.path = [decoder decodeObjectForKey:#"path"];
self.colorInArray = [decoder decodeObjectForKey:#"colorInArray"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder{
[encoder encodeObject:self.path forKey:#"path"];
[encoder encodeObject:self.colorInArray forKey:#"colorInArray"];
}
Also, is there a way to see what is in the NSUserDefault, such as a "prettyjson" format? -dictionaryRepresentation returns some figures like that <124d1f>
Did you checked saveDict method for previousArray == nil ?, Because before you saved something you have nothing in userDefaults
I had to use this : if ( previousArray == nil ) ...
NSMutableArray *previousArray = [[NSKeyedUnarchiver unarchiveObjectWithData:previousData] mutableCopy];
if ( previousArray == nil ) previousArray = [[NSMutableArray alloc] init];
Not enough information.
Objects can be converted to and from data if they conform to the NSCoding protocol. You have to write code for both the encodeWithCoder and initWithCoder methods that saves all your object's properties.
If you are un-archiving an object that you saved with encodeWithCoder and one of it's properties is not loading then there is most likely a problem with your encodeWithCoder method.
Post the header for the class you're trying to encode, all of the encodeWithCoder and initWithCoder methods, and information about the properties of your object that you are encoding.
Also post the code that converts your object to data and saves it, and provide a description of when/how it is saved.

Can't restore archived data

Ok, I've been over this a million times in the last week and I just am not getting it. (And yes, I've read Apple's docs.)
I am archiving my object and it appears to be archiving correctly (I can see the file written to the file system and if I examine it I can see my data within). However, when I relaunch my app my data is not being restored. Every example I read tells me how easy this is but I'm just not getting it. One unique thing is that my object is a singleton, it's used for passing data between view controllers.
I'd really appreciate some sage advice. Thanks in advance.
Here's my header:
#import <Foundation/Foundation.h>
#interface SharedAppDataObject : NSObject <NSCoding>
{
NSMutableDictionary *apiKeyDictionary;
NSString *skuFieldText;
NSIndexPath *checkmarkIndex;
}
+ (SharedAppDataObject *)sharedStore;
#property (nonatomic, copy) NSString *skuFieldText;
#property (nonatomic, copy) NSIndexPath *checkmarkIndex;
#property (nonatomic, copy) NSMutableDictionary *apiKeyDictionary;
-(void)setValue:(NSString *)apiKey forKey:(NSString *)name;
-(void)setSkuField:(NSString *)s;
-(void)setCheckmarkIndex:(NSIndexPath *)p;
-(NSMutableDictionary *)apiKeyDictionary;
-(BOOL)saveChanges;
#end
Here's my implementation:
#import "SharedAppDataObject.h"
#implementation SharedAppDataObject
#synthesize skuFieldText;
#synthesize checkmarkIndex;
#synthesize apiKeyDictionary;
//create our shared singleton store
+(SharedAppDataObject *)sharedStore {
static SharedAppDataObject *sharedStore = nil;
if (!sharedStore) {
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
if(!sharedStore)
sharedStore = [[super allocWithZone:NULL] init];
}
return sharedStore;
}
-(id) init {
self = [super init];
if (self) {
}
return self;
}
-(void)setValue:(id)apiKey forKey:(NSString *)name {
[apiKeyDictionary setObject:apiKey forKey:name];
}
-(void)setSkuField:(NSString *)s {
skuFieldText = s;
}
-(NSMutableDictionary *)apiKeyDictionary {
return apiKeyDictionary;
}
-(void)setCheckmarkIndex:(NSIndexPath *)p {
checkmarkIndex = p;
}
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeObject:skuFieldText forKey:#"skuFieldText"];
[aCoder encodeObject:checkmarkIndex forKey:#"checkmarkIndex"];
[aCoder encodeObject:apiKeyDictionary forKey:#"apiKeyDictionary"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (self) {
[self setSkuFieldText:[aDecoder decodeObjectForKey:#"skuFieldText"]];
[self setCheckmarkIndex:[aDecoder decodeObjectForKey:#"checkmarkIndex"]];
[self setApiKeyDictionary:[aDecoder decodeObjectForKey:#"apiKeyDictionary"]];
}
return self;
}
+(NSString *)archivePath {
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"bbyo.archive"];
}
-(BOOL)saveChanges {
return [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
}
#end
Save method from App Delegate:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
BOOL success = [[SharedAppDataObject sharedStore] saveChanges];
if (success) {
NSLog(#"Saved all the data");
} else {
NSLog(#"Didn't save any of the data");
}
}
Initialize sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]]; in application:didFinishLaunchingWithOptions:. This method is used to initialize data structures and restore previous app state.
Also, take out static SharedAppDataObject *sharedStore = nil; from sharedStore. If the save file exists, [ShareAppDataObject sharedStore] will always unarchive the file which is not necessary. It can be unarchived once during initialization.
Here's a post that can answer your problem: http://bit.ly/PJO8fM
I cannot give you the answer but some ideas to figure this out. Taking this line:
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
So if the sharedStore is nil, something is wrong - so test for it. If nothing then log the path, and use NSFileManager methods to see if the file is there, its size etc. If you find the file is there and has size, but you cannot unarchive it, that's a problem of course. In that case, add special debug code just after you create the file:
-(BOOL)saveChanges {
BOO ret = [NSKeyedArchiver archiveRootObject:self toFile:[SharedAppDataObject archivePath]];
id foo = [NSKeyedUnarchiver unarchiveObjectWithFile:[SharedAppDataObject archivePath]];
// check if foo is not nil, if its the proper class, etc.
}
If when you save the file you can unarchive it just fine, but cannot on restart of the app, then something is wrong with the file. All this info should point the way to a solution.
Another thought - when you encode the data, log it, just to be sure its not nil - but even if so the unarchive should work.

Resources