How to add multiple email attachments using UIActivityItemProvider - objective-c

I'm working on an iOS app in which I want to add MULTIPLE attachments to an email using UIActivityItemProvider. I want to do it using UIActivityItemProvider because I do not want to incur the overhead of processing the record before I display the UIActivtyViewController to the user. If I pass in one image using an NSData or an NSURL object to the "url" element of the returned object, then the inline image shows fine. If I pass in an array of these objects then nothing shows up. I believe that passing an array will work if I use the ActivityItems parameter when initializing an NSActivityViewController, but again, I do not want to do this because I want to take advantage of the delayed processing available by using the UIActivityItemProvider. Below is my code
#implementation NoteRecordActivityProvider
- (id)initWithPlaceholderItem:(id)placeholderItem
{
//Initializes and returns a provider object with the specified placeholder data
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
// //Generates and returns the actual data object
NSData *imageFile = [[NSData alloc]init];
NSString *imageFileName;
NSURL *url;
NSString* exportPath;
NSMutableArray* imageArray = [[NSMutableArray alloc]initWithCapacity:0];
NSInteger photoCount = self.noteRecord.photoCount;
for (NSInteger i = 0; i < photoCount; i+=1)
{
//Add File Attachment
PhotoObject *po = (PhotoObject*)[self.noteRecord photoObjects:i];
NSString *photoGUID = [(PhotoObject*)[self.noteRecord photoObjects:i]GUID];
imageFile = ImageDataReturningMethodHere;
imageFileName = [[NSArray arrayWithObjects:#"Image", [NSString stringWithFormat:#"%ld", (long)i], #".png", nil] componentsJoinedByString:#""];
exportPath = [[FileSystemProvider exportPath] stringByAppendingPathComponent:imageFileName];
[imageFile writeToFile:exportPath atomically:YES];
url = [NSURL fileURLWithPath:exportPath];
[imageArray addObject:url];
}
if ([self.activityType isEqualToString:UIActivityTypeMail])
return imageArray;
else
return nil;
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
return #{#"body":#"", #"url":[[NSURL alloc]init]};
}
-(NSString *) activityViewController:(UIActivityViewController *)activityViewController subjectForActivityType:(NSString *)activityType {
return [NSString stringWithFormat:#"Attached Record: %#", self.noteRecord.title];
}
#end

I did find the answer to this question. First I created an PhotoAttachmentActivityProvider that had a property for the source document which contains the photo I wanted to attach, and an index to the attachment in that document. I'm pasting my code here which uses a custom document called a NoteRecord:
#interface EMailPhotoAttachmentItemProvider : UIActivityItemProvider
#property (nonatomic, readwrite) NSInteger photoIndex;
#property (nonatomic, strong) NoteRecord* noteRecord;
#end
Then when I am showing the UIActivityViewController I add 1 of these custom UIActivityItemProvider objects for each attachment:
for (NSInteger i = 0; i < self.noteRecord.photoCount; i++)
{
EMailPhotoAttachmentItemProvider* photoProvider = [[EMailPhotoAttachmentItemProvider alloc]initWithPlaceholderItem:#{#"body":textToShare, #"url":url}];
photoProvider.photoIndex = i;
photoProvider.noteRecord = self.noteRecord;
[activityProviders addObject:photoProvider];
}
//Initialize the ActivityViewController
UIActivityViewController *activityController = [[UIActivityViewController alloc] initWithActivityItems:activityProviders applicationActivities:applicationActivities];
Then in the custom UIActivityItemProvider I check for whether I'm processing a EMAIL, and then I create a URL for the image using the document and image index provided:
#import "EMailPhotoAttachmentItemProvider.h"
#import "MiscUtilities.h"
#import "FileSystemProvider.h"
#implementation EMailPhotoAttachmentItemProvider
- (id)initWithPlaceholderItem:(id)placeholderItem
{
//Initializes and returns a provider object with the specified placeholder data
return [super initWithPlaceholderItem:placeholderItem];
}
- (id)item
{
if ([self.activityType isEqualToString:UIActivityTypeMail])
{
// Code here gets the image file from the NoteRecord at the PhotoIndex provided to
// the UIActivityItemProvider at the imageIndex, creates a URL for that image and returns it here.
// Your implementation will vary
PhotoObject *po = (PhotoObject*)[self.noteRecord photoObjects:self.photoIndex];
NSString *photoGUID = [(PhotoObject*)[self.noteRecord photoObjects:self.photoIndex]GUID];
NSData *imageFile = [[[MiscUtilities getApplicationDelegate]imageProvider]imageDataWithCaptionFromGUID:photoGUID caption:po.caption maxResolution:600];
NSString *imageFileName = [[NSArray arrayWithObjects:#"Image", [NSString stringWithFormat:#"%ld", (long)self.photoIndex], #".png", nil] componentsJoinedByString:#""];
imageFileName = [[FileSystemProvider exportPath] stringByAppendingPathComponent:imageFileName];
[imageFile writeToFile:imageFileName atomically:YES];
NSURL *url = [NSURL fileURLWithPath:imageFileName];
return url;
}
else
{
return nil;
};
}
- (id)activityViewControllerPlaceholderItem:(UIActivityViewController *)activityViewController
{
NSString* defaultImagePath = [[FileSystemProvider imagePath]stringByAppendingPathComponent:#"default.png"];
NSURL *url = [[NSURL alloc]initWithString:defaultImagePath];
return #{#"body":#"", #"url":url};
}
#end

Related

Exporting all contacts in one .vcf file using Contacts.Framework in Objective - C

Using I AddressBook.framework I used to create Contacts.vcf from all contacts and save it in Documents Directory.
Here is the code I used to use :
ABAddressBookRef addressBook1 = ABAddressBookCreate();
NSArray *arrayOfAllPeople = (__bridge_transfer NSArray *) ABAddressBookCopyArrayOfAllPeople(addressBook1);
long cnt = (unsigned long)[arrayOfAllPeople count];
if (cnt==0) {
ABAddressBookRequestAccessWithCompletion(addressBook1, nil);
}
if(ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
{
ABAddressBookRef addressBook2 = ABAddressBookCreate();
CFArrayRef contacts = ABAddressBookCopyArrayOfAllPeople(addressBook2);
CFDataRef vcards = (CFDataRef)ABPersonCreateVCardRepresentationWithPeople(contacts);
NSString *vcardString = [[NSString alloc] initWithData:(__bridge NSData *)vcards encoding:NSUTF8StringEncoding];
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *folderPath = [paths objectAtIndex:0];
NSString *filePath = [folderPath stringByAppendingPathComponent:#"Contacts.vcf"];
[vcardString writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
CFRelease(addressBook2); }
How do I create a Contacts.vcf file having all device contacts using Contacts.framework and save it in documents directory ?
You can use this method to get all the contacts in .vcf file. It return the same output that you get using AddressBook.framework.
- (void)getContacts {
NSMutableArray *contactsArray=[[NSMutableArray alloc] init];
CNContactStore *store = [[CNContactStore alloc] init];
[store requestAccessForEntityType:CNEntityTypeContacts completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (!granted) {
dispatch_async(dispatch_get_main_queue(), ^{
});
return;
}
NSMutableArray *contacts = [NSMutableArray array];
NSError *fetchError;
CNContactFetchRequest *request = [[CNContactFetchRequest alloc] initWithKeysToFetch:#[[CNContactVCardSerialization descriptorForRequiredKeys], [CNContactFormatter descriptorForRequiredKeysForStyle:CNContactFormatterStyleFullName]]];
BOOL success = [store enumerateContactsWithFetchRequest:request error:&fetchError usingBlock:^(CNContact *contact, BOOL *stop) {
[contacts addObject:contact];
}];
if (!success) {
NSLog(#"error = %#", fetchError);
}
// you can now do something with the list of contacts, for example, to show the names
CNContactFormatter *formatter = [[CNContactFormatter alloc] init];
for (CNContact *contact in contacts) {
[contactsArray addObject:contact];
// NSString *string = [formatter stringFromContact:contact];
//NSLog(#"contact = %#", string);
}
//NSError *error;
NSData *vcardString =[CNContactVCardSerialization dataWithContacts:contactsArray error:&error];
NSString* vcardStr = [[NSString alloc] initWithData:vcardString encoding:NSUTF8StringEncoding];
NSLog(#"vcardStr = %#",vcardStr);
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *folderPath = [paths objectAtIndex:0];
NSString *filePath = [folderPath stringByAppendingPathComponent:#"Contacts.vcf"];
[vcardStr writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil];
}];
}
From iOS 9+ version AddressBookUI.framework and Addressbook.framework becomes deprecated. Apple introduced ContactUI.framework and Contact.framework with enhancements over AddressBookUI.framework and Addressbook.framework. In this blog we will talk about how to use these two new frameworks and export VCard. Let’s start picking contact from phone contacts and access basic information of that person.
Step 1. Create new Xcode project name ContactDemo and import Contacts.framework and ContactsUI.framework as shown in picture.
Step 2. In project add UIButton, UIImageView and 3 UILabels as shown in picture :
Step 3. Create outlets of button action, imageview and labels in respective view controller as :
#property (weak, nonatomic) IBOutlet UIImageView *personImage;
#property (weak, nonatomic) IBOutlet UILabel *personName;
#property (weak, nonatomic) IBOutlet UILabel *emailId;
#property (weak, nonatomic) IBOutlet UILabel *phoneNo;
- (IBAction)selectAction:(id)sender;
Step 4. Add delegate CNContactPickerDelegate to viewController.
Step 5. Add delegate method :
- (void) contactPicker:(CNContactPickerViewController *)picker
didSelectContact:(CNContact *)contact {
[self getContactDetails:contact];
}
This delegate method will return contact in the form of CNContact object which will be further processed in local method
-(void)getContactDetails:(CNContact *)contactObject {
NSLog(#"NAME PREFIX :: %#",contactObject.namePrefix);
NSLog(#"NAME SUFFIX :: %#",contactObject.nameSuffix);
NSLog(#"FAMILY NAME :: %#",contactObject.familyName);
NSLog(#"GIVEN NAME :: %#",contactObject.givenName);
NSLog(#"MIDDLE NAME :: %#",contactObject.middleName);
NSString * fullName = [NSString stringWithFormat:#"%# %#",contactObject.givenName,contactObject.familyName];
[self.personName setText:fullName];
if(contactObject.imageData) {
NSData * imageData = (NSData *)contactObject.imageData;
UIImage * contactImage = [[UIImage alloc] initWithData:imageData];
[self.personImage setImage:contactImage];
}
NSString * phone = #"";
NSString * userPHONE_NO = #"";
for(CNLabeledValue * phonelabel in contactObject.phoneNumbers) {
CNPhoneNumber * phoneNo = phonelabel.value;
phone = [phoneNo stringValue];
if (phone) {
userPHONE_NO = phone;
}}
NSString * email = #"";
NSString * userEMAIL_ID = #"";
for(CNLabeledValue * emaillabel in contactObject.emailAddresses) {
email = emaillabel.value;
if (email) {
userEMAIL_ID = email;
}}
NSLog(#"PHONE NO :: %#",userPHONE_NO);
NSLog(#"EMAIL ID :: %#",userEMAIL_ID);
[self.emailId setText:userEMAIL_ID];
[self.phoneNo setText:userPHONE_NO];
}
Step 6. Create CNContactPickerViewController class object and register its delegate in button IBAction method :
- (IBAction) selectAction:(id)sender {
CNContactPickerViewController *contactPicker = [CNContactPickerViewController new];
contactPicker.delegate = self;
[self presentViewController:contactPicker animated:YES completion:nil];
}
[self presentViewController:contactPicker animated:YES completion:nil]; will present view of contact list.
Step 7. Run project
A . Main View
B. On Tapping “Select Contact” button CNContactPickerViewController will open as shown in picture :
C. Pick one contact and view will dismiss and you will get details of that contact as shown in picture :
Earlier we have write permission code to access contacts but now it implicitly grants permission for accessing contacts. With this framework we can also generate VCard(VCF) and share among other platforms. Here is the steps to create VCard.
Step 1. Pick contact from CNContactPickerViewController and you will get CNContact Object in delegate as mention above.
Step 2. Save contact in document directory. As data is stored in NSData form so to convert contact to NSData
use CNContactVCardSerialization class that represents VCard in NSData format.
- (NSString *) saveContactToDocumentDirectory:(CNContact *)contact {
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString * VCardPath = [documentsDirectory stringByAppendingString:#"/VCard.vcf"];
NSArray *array = [[NSArray alloc] initWithObjects:contact, nil];
NSError *error;
NSData *data = [CNContactVCardSerialization dataWithContacts:array error:&error];
[data writeToFile:VCardPath atomically:YES];
return VCardPath;
}
CNContactVCardSerialization class method dataWithContacts:error: takes array of contact objects(CNContact class Object).
saveContactToDocumentDirectory method will return the file path of Vcard. With File path you can export contact anywhere you want.
Source: Contacts UI, Contacts Framework and create VCard(VCF) in Objective-C

How to parse specific Json Array

I have a Json array like this:
{"Response":{"Token":"///","Name":"///","Surname":"///","Phone":"///","Street":"///","Interno":"///","PostalCode":"///","City":"///","Province":{"ID":"///","Code":"///","Name":"///"},"Email":"///#gmail.com"},"Error":false,"ErrorDetails":null}
How can I parse the values inside Response and inside Province using objective-c?
I tried with the following code:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the locations
if(_fproducts == nil)
{
_fproducts = [[NSMutableArray alloc] init];
}
// Parse the JSON that came in
NSError *error;
jsonArray = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < jsonArray.count; i++)
{
NSDictionary *jsonElement = jsonArray[i];
// Create a new location object and set its props to JsonElement properties
LoginCredentials *newFProduct = [[LoginCredentials alloc] init];
newFProduct.token = jsonElement[#"Id"];
newFProduct.nomeUser = jsonElement[#"nome"];
NSLog(#"TOKEN:%#", newFProduct.token);
NSLog(#"NOME:%#", newFProduct.nomeUser);
// Add this question to the locations array
[_fproducts addObject:newFProduct];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_fproducts];
}
}
But I can't parse the values inside Response.. Do i need to create an array of Response and then parse it?
Convert all in NSDictionary, not in NSArray, and then:
jsonDictionary = [NSJSONSerialization JSONObjectWithData:_downloadedData options:NSJSONReadingAllowFragments error:&error];
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* province = response[#"province"];
NSLog("Province: %#", province);
/* here you can save all your values */
if(province) {
NSString* identificator = province[#"ID"];
NSString* code = province[#"Code"];
NSString* name = province[#"Name"];
}
}
}
An elegant way to do this is creating your custom interface:
Province.h
#interface Province : NSObject
- (id)initWithDictionary:(NSDictionary*)dictionary;
- (nonatomic, strong) NSString* identificator;
- (nonatomic, strong) NSString* code;
- (nonatomic, strong) NSString* name;
#end
Province.m
#implementation Province
- (id)initWithDictionary:(NSDictionary*)dictionary {
self = [super init];
self.identificator = dictionary[#"ID"];
self.code = dictionary[#"Code"];
self.name = dictionary[#"Name"];
return self;
}
#end
So your code becomes:
if(!error) {
NSDictionary* response = jsonDictionary[#"response"];
if(response) {
NSDictionary* provinceDictionary = response[#"province"];
if(province) {
Province* province = [Province initWithDictionary:provinceDictionary];
}
}
}

Objective C assigning a dictionary to a variable and accessing it

I'm sorry to ask this question again, but I'm still stuck.
I have a city object trying to fetch weather from a weather fetcher object
#interface WeatherFetcher : NSObject {
}
#property (nonatomic, strong) NSMutableDictionary *weatherData;
- (void)fetchWeather:(NSString *)cityName;
- (void)handleNetworkErorr:(NSError *)error;
- (void)handleNetworkResponse:(NSData *)myData;
#end
This is were I assign the value to weatherData
#import "WeatherFetcher.h"
#implementation WeatherFetcher
- (void)fetchWeather:(NSString *)cityName
{
NSString *urlString = #"http://api.openweathermap.org/data/2.5/weather?q=";
urlString = [urlString stringByAppendingString:cityName];
urlString = [urlString stringByAppendingString:#",Aus"];
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
if (connectionError)
{
[self handleNetworkErorr:connectionError];
}
else
{
[self handleNetworkResponse:data];
}
}];
}
#pragma mark - Private Failure Methods
- (void)handleNetworkErorr:(NSError *)error
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Network Error" message:#"Please try again later" delegate:nil cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[alert show];
}
#pragma mark - Private Success Methods
- (void)handleNetworkResponse:(NSData *)myData
{
//NSMutableDictionary *data = [NSMutableDictionary dictionary];
NSMutableDictionary *data = [[NSMutableDictionary alloc] init];
// now we'll parse our data using NSJSONSerialization
id myJSON = [NSJSONSerialization JSONObjectWithData:myData options:NSJSONReadingMutableContainers error:nil];
// typecast an array and list its contents
NSDictionary *jsonArray = (NSDictionary *)myJSON;
//NSLog([jsonArray description]);
// take a look at all elements in the array
for (id element in jsonArray) {
id key = [element description];
id innerArr = [jsonArray objectForKey:key];
NSDictionary *inner = (NSDictionary *)innerArr;
if ([inner conformsToProtocol:#protocol(NSFastEnumeration)]) {
for(id ele in inner) {
if ([ele conformsToProtocol:#protocol(NSFastEnumeration)]) {
NSDictionary *innerInner = (NSDictionary *)ele;
for(id eleEle in innerInner) {
id innerInnerKey = [eleEle description];
[data setObject:[[inner valueForKey:innerInnerKey] description] forKey:[eleEle description]];
}
}
else {
id innerKey = [ele description];
[data setObject:[[inner valueForKey:innerKey] description] forKey:[ele description]];
}
}
}
else {
[data setObject:[inner description] forKey:[element description]];
}
}
self.weatherData = data;
NSLog([self.weatherData description]) **//there is data**
}
#end
However every time I call this from by city object I get nothing back at all.
#import <Foundation/Foundation.h>
#import "WeatherFetcher.h"
#interface City : NSObject {
}
#property (nonatomic, strong) NSString *cityName;
#property (nonatomic, strong) NSString *stateName;
#property (nonatomic, strong) UIImage *cityPicture;
#property (nonatomic, strong) NSString *weather;
#property (nonatomic, strong) NSMutableDictionary *weatherData;
-(NSString *)getWeather;
#end
UI calls getWeather by a button press to get the string value to be displayed on screen
#implementation City {
}
-(NSString *)getWeather {
//return self.weather;
NSString *info = #"";
WeatherFetcher *weatherFetcher = [[WeatherFetcher alloc] init];
[weatherFetcher fetchWeather:self.cityName];
self.weatherData = [weatherFetcher weatherData];
for (id element in self.weatherData) {
info = [info stringByAppendingString:[element description]];
info = [info stringByAppendingString:#"-->"];
info = [info stringByAppendingString:[self.weatherData valueForKey:[element description]]];
info = [info stringByAppendingString:#"\n"];
}
return info;
}
#end
What am I doing wrong here?
getWeather method in the city class gets called when a button is pressed and I'm trying to display this string in a text area. I don't have much experience with Objective C and this is my first app other than Hello World app.
Thank you!
Your WeatherFetcher is asynchronous (sendAsynchronousRequest:) - it sets a task to obtain the data and then returns (usually) before that data has been obtained. So when you try to access the weatherData immediately after the call to fetchWeather: it is not there yet.
You need to redesign your model to handle asynchronicity - getWeather cannot be synchronous. For example you could make fetchWeather: take a completion block to invoke when the data is available and have getWeather pass in a suitable block.

Can't get NSData into NSMutableArray

I have an array of objects that I want to save as a file and reload back into my app. It's saving the file (with some data inside) but I can't get it to read back into a NSMutable Array.
The objects are models that conform to the NSCoding protocol:
#implementation myModel
#synthesize name;
#synthesize number;
-(void) encodeWithCoder: (NSCoder *) encoder
{
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
-(id) initWithCoder: (NSCoder *) decoder
{
name = [decoder decodeObjectForKey:#"name"];
number = [decoder decodeIntegerForKey:#"number"];
return self;
}
#end
So I create an array of these objects, then I save it...
- (void) saveMyOptions {
// Figure out where we're going to save the app's data files
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Figure out if that directory exists or not
BOOL isDir;
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager fileExistsAtPath:directoryPath isDirectory:&isDir];
// If the directory doesn't exist, create it
if (!isDir)
{
[fileManager createDirectoryAtPath:directoryPath withIntermediateDirectories:YES attributes:nil error:NULL];
}
// Assemble everything into an array of objects with options
NSMutableArray *savedPreferences = [[NSMutableArray alloc] init];
myModel *saveOptions = nil;
for (int i; i < [otherArray count]; i++)
{
saveOptions = [[myModel alloc] init];
[saveOptions setName:#"Some String"];
[saveOptions setNumber:i];
[savedPreferences addObject:saveOptions];
saveOptions = nil;
}
// Actually save those options into a file
NSData* saveData = [NSKeyedArchiver archivedDataWithRootObject:savedPreferences];
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
NSError *error = nil;
BOOL written = [saveData writeToFile:fileName options:0 error:&error];
if (!written)
{
NSLog(#"Error writing file: %#", [error localizedDescription]);
}
}
So now I try to load that data back into an array. This is where I think it's falling apart...
- (NSMutableArray *) loadOptions {
// Create file manager object
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSData *saveData = nil;
// Find user directory path
NSString *directoryPath = [NSString stringWithFormat:#"%#/Library/Application Support/MyAppDir/", NSHomeDirectory()]; // points to application data folder for user
// Assign file name
NSString *fileName = [NSString stringWithFormat:#"%#filename.stuff", directoryPath];
// Create options array
NSMutableArray *myOptions = nil;
// If the file exists, fill the array with options
if ([fileManager fileExistsAtPath:fileName])
{
saveData = [NSData dataWithContentsOfFile:fileName];
myOptions = [NSKeyedUnarchiver unarchiveObjectWithData:saveData];
}
NSLog(#"%lu", [myOptions count]); // This ALWAYS reports 0!
NSLog(#"%lu", [saveData length]); // This reports a value of 236;
return myOptions;
}
Could someone point me in the direction of where I'm going wrong? I'm throughly confused :-(
Thanks in advance!
You are missing the super calls in your encodeWithCoder: and initWithCoder: methods, but that's just a guess. Why not use NSUserDefaults for saving preferences?
You might also want to make sure that your objects are retained is set using the synthesized setter.
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:name forKey:#"name"];
[encoder encodeInteger:number forKey:#"number"];
}
- (id)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
self.name = [decoder decodeObjectForKey:#"name"];
self.number = [decoder decodeIntegerForKey:#"number"];
}
return self;
}
For your info, NSKeyedArchiver also has a method you can use directly to operate on files:
+ (BOOL)archiveRootObject:(id)rootObject toFile:(NSString *)path
and NSKeyedUnarchiver:
+ (id)unarchiveObjectWithFile:(NSString *)path

How do you save data with NSKeyedArchiver?

Hi, I am trying to save an object from a class I have created. It is called shot and Contains 5 variables I wish to save. Here is the .h file--- It cut off NSCoding and NSMutableCopying Protocals and my imports, but they are there.
#import <Foundation/Foundation.h>
#interface Shot : UIButton <NSCoding, NSMutableCopying> {
int x;
int y;
int location;
int quarter;
bool made;
int miss;
int make;
}
#property()int x;
#property()int y;
#property()int location;
#property()int quarter;
#property()bool made;
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
#end
**Here are the methods I made to save the data in my .m file---**
-(void)encodeWithCoder:(NSCoder *)aCoder {
[aCoder encodeInt:x forKey:#"x"];
[aCoder encodeInt:y forKey:#"y"];
[aCoder encodeInt:location forKey:#"location"];
[aCoder encodeInt:quarter forKey:#"quarter"];
[aCoder encodeBool:made forKey:#"made"];
}
-(id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super init]) {
x = [aDecoder decodeIntForKey:#"x"];
y = [aDecoder decodeIntForKey:#"y"];
location = [aDecoder decodeIntForKey:#"location"];
quarter = [aDecoder decodeIntForKey:#"quarter"];
made = [aDecoder decodeBoolForKey:#"made"];
}
return self;
}
-(id)mutableCopyWithZone:(NSZone *)zone {
Shot *newShot = [[Shot allocWithZone:zone]init];
[newShot set_x:x set_y:y set_quarter:quarter set_made:made set_location:location];
return newShot;
}
I can't seem to get my data to saved when I use these methods
-(NSString *)getPath {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentFolder = [paths objectAtIndex:0];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:documentFolder] == NO) {
[fm createDirectoryAtPath:documentFolder withIntermediateDirectories:YES attributes:nil error:nil];
}
return [documentFolder stringByAppendingFormat:#"iStatTrackInfo.archive"];
}
-(void)saveData {
NSString *path = [self getPath];
[NSKeyedArchiver archiveRootObject:shotArray toFile:path];
}
-(void)loadData {
NSString *path = [self getPath];
NSFileManager *fm = [[NSFileManager alloc]init];
if ([fm fileExistsAtPath:path] == YES) {
shotArray = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
return;
}
NSEnumerator *enumOne = [shotArray objectEnumerator];
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
shotShot.frame = CGRectMake(0, 0, 50, 50);
shotShot.backgroundColor = [UIColor whiteColor];
[self addSubview:shotShot];
}
}
I have set the save and load methods up to buttons, but my data still won't save. Please help!!!
I have finally solved the problem. Other than the above help, .archive isn't a file type. You can't save a .archive, you can only save a .arch or a .plist That was my main problem.
First, you are leaking memory like a sieve. Lots of alloc/init & mutableCopy, no releases. I'd suggest reading the memory management guide.
Next, your method names are not following the Cocoa conventions. These:
-(void)set_x:(int)my_x set_y:(int)my_y set_quarter:(int)my_quarter set_made:(bool)my_made set_location:(int)my_location;
-(void)set_miss_AND_set_make;
Should be something like:
-(void) resetMissAndMake; // or just missAndMake or activateMissAndMake
-(void) setX:(NSInteger)xValue y:(NSInteger)yValue quarter:(NSInteger)quarterValue location:(NSInteger)aLocation;
Also, getPath should just be path. Methods prefixed with get are both very rare and have a very specific meaning.
This is also nonsense:
Shot *shotObject = [[Shot alloc]init];
while (shotObject = [enumOne nextObject]) {
Shot *shotShot = [[Shot alloc]init];
shotShot = [shotObject mutableCopy];
There is no need for either of the [[Shot alloc]init] calls (those are both leaks).
Finally, your encoding methods are implemented incorrectly. As the documentation states, you need to call super as appropriate.
Specifically, your encodeWithCoder: must invoke super's implementation of same and initWithCoder: should call super's initWithCoder:, not init.