addObject for NSMutableArray not working - ios

#property (strong, nonatomic)NSMutableArray *billsArray;
- (void)viewDidLoad {
[super viewDidLoad];
self.billsUserDefault = [NSUserDefaults standardUserDefaults];
self.billsArray = [[NSMutableArray alloc] init];
self.billsArray = [self getBillsArray];
}
- (NSMutableArray *)getBillsArray {
NSMutableArray *billsArr = [self.billsUserDefault objectForKey:#"billArray"];
return billsArr;
}
- (void)AddOneBill:(Bill *)bill {
// add bill to array
[self.billsArray addObject:bill];
// store the new bill
[self.billsUserDefault setObject:self.billsArray forKey:#"billArray"];
[self.billsUserDefault synchronize];
// reload the table view
[self.billTableView reloadData];
}
The addObject method in addOneBill: method does not work.
I have googled the same problem, others also met this problem. The answers suggested to add [[NSMutableArray alloc] init] for mutable array. I did but not works.

In your getBillsArray method, add mutableCopy call to get mutable array for objectForKey. Because objects got from NSUserDefaults are not mutable. Modified code would look like this:
- (NSMutableArray *)getBillsArray {
NSMutableArray *billsArr = [[self.billsUserDefault objectForKey:#"billArray"] mutableCopy];
return billsArr;
}
EDIT:
You are adding custom objects in array and trying to save in NSUserDefaults which is not possible. You need to convert your object into NSDictionary object in which each key value pair will represent one variable of that object. You will have to recursively do this conversion step if your custom object also contain custom objects.

- (void)viewDidLoad {
[super viewDidLoad];
self.billsUserDefault = [NSUserDefaults standardUserDefaults];
self.billsArray = [[NSMutableArray alloc] init];
self.billsArray = [self getBillsArray];
}
In viewDidLoad you call getBillsArray. But what value in userDefauls for key - billArray you what to get at viewController start?
You set array for this key in addOneBill method.
So in viewDidLoad you creates mutableArray:
self.billsArray = [[NSMutableArray alloc] init];
and then set it to nil here:
self.billsArray = [self getBillsArray];

Try
NSMutableArray *billsArr = [[self.billsUserDefault objectForKey:#"billArray"] mutableCopy]; in getBillsArray

NSUserDefaults objects are not not mutable. You have to make them mutable.
try following code.
NSMutableArray *billsArr = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"billArray"]];

Another solution : For the explication why it does not work, see Salman's answer (Returned values from NSUserDefaults are not mutable)
- (void)viewDidLoad {
[super viewDidLoad];
self.billsUserDefault = [NSUserDefaults standardUserDefaults];
self.billsArray = [[NSMutableArray alloc] init];
[self.billsArray addObjectsFromArray: [self getBillsArray]];
}
- (NSArray *)getBillsArray {
NSArray *billsArr = [self.billsUserDefault objectForKey:#"billArray"];
return billsArr;
}
In order to save your 'Bill' objects into NSUserDefaults, if it's derived from NSObject, you can use NSKeyedArchiver & NSKeyedUnarchvier:
//encode
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:your_bill_array];
//then save to NSUserDefaults
//decode:
id result = [NSKeyedUnarchiver unarchiveObjectWithData:data_returned_from_NSUSerDefaults];

Related

This saved data doesn't appear to be accessible until relaunch iOS

I have a custom NSCoding class which stores and retrieves itself when necessary. However, it doesn't feed data to my table view until giving one entry to the array of custom Person objects inside it and restarting the app, then giving another. The first one disappears, however.
After that, it appears to load okay.
Here is the implementation of the class
#import "DataStorage.h"
#implementation DataStorage
#synthesize arrayOfPeople = _arrayOfPeople;
+ (DataStorage *)sharedInstance
{
static DataStorage *state = nil;
if ( !state )
{
NSData *data =[[NSUserDefaults standardUserDefaults] objectForKey:#"DataStorageBank"];
if (data)
{
state = [NSKeyedUnarchiver unarchiveObjectWithData:data];
}
else
{
state = [[DataStorage alloc] init];
}
}
return state;
}
- (instancetype)initWithCoder:(NSCoder *)decoder
{
self = [self init];
if (self) {
if ([decoder decodeObjectForKey:#"DataStoragePeopleArray"]) {
_arrayOfPeople = [[decoder decodeObjectForKey:#"DataStoragePeopleArray"] mutableCopy];
} else {
_arrayOfPeople = [[NSMutableArray alloc] init];
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[encoder encodeObject:_arrayOfPeople forKey:#"DataStoragePeopleArray"];
}
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
}
#end
I add objects to the _arrayOfPeople like so:
Person *person = [[Person alloc] initWithFirstName:firstName personSurname:surname personCompay:company personPosition:position personEmail:email personMobile:mobile personProduct:product];
[[DataStorage sharedInstance].arrayOfPeople addObject:person];
[[DataStorage sharedInstance] save];
And load them into the table view by this:
Person *personAtIndex = [[DataStorage sharedInstance].arrayOfPeople objectAtIndex:indexPath.row];
[_arrayOfPeople addObject:personAtIndex];
cell.textLabel.text = personAtIndex.firstName;
cell.detailTextLabel.text = personAtIndex.surname;
Loading them in to the table view is in the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
It looks like you only initialize _arrayOfPeople in initWithCoder:. However, if your data does not exist in user defaults already, you use state = [[DataStorage alloc] init] to initialize your shared instance. This does not call initWithCoder: so _arrayOfPeople is nil until after you save and load again, when it is finally initialized as [[NSMutableArray alloc] init]. To fix this, move _arrayOfPeople = [[NSMutableArray alloc] init] out of initWithCoder: and into init. (You could alternatively move it into sharedInstance, but it makes more sense in init since it is not specific to configuring the shared instance.)
Unrelated, but also make sure you synchronize.
- (void)save
{
NSData *appStateData = [NSKeyedArchiver archivedDataWithRootObject:self];
[[NSUserDefaults standardUserDefaults] setObject:appStateData forKey:#"DataStorageBank"];
[[NSUserDefaults standardUserDefaults] synchronize];
}

How to access an instance variable by an NSString generated at run-time (reflection)

I've got the following little code conundrum..
- (void) transmitArray {
NSString* arrayName = #"array1";
NSArray* array1 = [NSArray arrayWithObject:#"This is Array 1"];
NSArray* array2 = [NSArray arrayWithObject:#"This is Array 2"];
NSMutableArray* targetArray = [NSMutableArray arrayWithCapacity:1];
}
Is there a way to use the string "array1" to access the NSArray 'array1' so I can copy it into the target array.. And therefore, if the string read "array2", I'd be able to access the second array?
I read about using NSSelectorFromString to create a selector, and that this technique was called 'introspection', but other than that I'm stumped..
Hope someone can help!
Not really. If it were a instance variable of the class (generally known as a property) then you could use introspection to inspect the object and get/set the needed variables. You can can use the KVO methods
setValue:(id) ForKeyPath:(NSString *)
and
valueForKeyPath:(NSString *)
to access them
However you can't do that with local variables declared inside of an instance method (directly). I would suggest populating a dictionary with your arrays and then using it as a lookup table
NSMutableDictionary *arrays = [[NSMutableDictionary alloc] init];
NSArray *array1 = #[#"This is Array 1"];
[arrays setObject:array1 forKey:#"array1"];
NSArray *array2 = #[#"This is Array 2"];
[arrays setObject:array1 forKey:#"array2"];
//grab a copy of array1
NSArray *targetArray = [arrays[#"array1"] copy];
If you have array1 declared as a property you can do it like this:
#import "ViewController.h"
#interface ViewController ()
#property NSArray *array1;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.array1 = [NSArray arrayWithObjects:#"Hello", #"world!", nil];
NSArray *array2 = [self valueForKeyPath:#"array1"];
NSLog(#"%#", array2);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end

Saving data with NSUserDetaults and load after phone restart wont work

I managed to save an NSMutableArray in NSUserDefaults by first converting it to NSData - I dont deal with a lot of data and just want the data to be there after I switch off & on my phone - but the data does not show up in my table where I would display it. I write the NSUserDefaults back to my array upon loading. Maybe one of you has a hint...? Below the button action where I write to NSUserDefaults and the method viewDidLoad where I write NSUserDefaults to my original array (toDoitems)
- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
XYZAddToDoItemViewController *source = [segue sourceViewController];
XYZToDoItem *item = source.toDoItem;
if (item !=nil) {
[self.toDoitems addObject:item];
NSString *error;
NSData *data = [NSPropertyListSerialization dataFromPropertyList:self.toDoitems format:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"itemArray"];
[[NSUserDefaults standardUserDefaults] synchronize];
[self.tableView reloadData];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.toDoitems = [[NSMutableArray alloc] init];
self.toDoitems = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"itemArray"]];
}
Heres one way to do this
Add encoder decoder functions to your XYZToDoItem class
Something like this if say you had 2 strings in this class string1 and string2 :
(i havent compiled this code but you get the idea)
-(void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:self.string1 forKey:#"string1"];
[aCoder encodeObject:self.string2 forKey:#"string2"];
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (self)
{
self.string1 = [aDecoder decodeObjectForKey:#"string1"];
self.string2 = [aDecoder decodeObjectForKey:#"string2"];
}
return self;
}
Then when you are ready to save do the following
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.myDataArray];
[userDefaults setObject:data forKey:#"storageName"];
// To load the data from NSUserDefaults
NSData *myData = [[NSUserDefaults standardUserDefaults] objectForKey:#"storageName"];
NSArray *temp = (NSMutableArray*)[NSKeyedUnarchiver unarchiveObjectWithData:myData];
self.myDataArray = (NSMutableArray*)[temp mutableCopy];

Working with NSUserDefaults when saving own datatype

i had read this topic How to save My Data Type in NSUserDefault? and get from there this useful part of code:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:#"kMyObjectData"];
for saving data and this for reading
NSData *getData = [[NSData alloc] initWithData:[[NSUserDefaults standardUserDefaults] objectForKey:#"kMyObjectData"]];
MyObject *getObject;
[getData getBytes:&getObject];
its works very good when i save data in one ViewController and read it in other.
but when i whant to use it in the same class:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSUserDefaults *myDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:#"linkedinfo"];
NSData *getData = [[NSData alloc] initWithData:myDefaults];
LinkedContainer *getObject;
[getData getBytes:&getObject];
if (!myDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:#"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewDidFinish:)
name:#"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
something going wrong. in #selector:loginViewDidFinish i am saving my data to NSUserDefaults:
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData *myObjectData = [NSData dataWithBytes:(void *)&mLinkedInInfo length:sizeof(mLinkedInInfo)];
NSUserDefaults *lSave = [NSUserDefaults standardUserDefaults];
[lSave setObject:myObjectData forKey:#"linkedinfo"];
[lSave synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
the program always crashes when it comes to else part. If somebody knows what I am doing wrong please help me)
error message: Thread 1 : EXC_BAD_ACCESS(code=2,address 0x8) when compiling getObject.Consumer
In the vast majority of cases, this is not going to be a meaningful way to serialize your object into an NSData:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:#"kMyObjectData"];
The canonical way to do this would be for MyObject to adopt the NSCoding protocol. Based on the code you posted here, an adoption of NSCoding might look like this:
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super init])
{
mConsumer = [coder decodeObjectForKey: #"consumer"];
mToken = [coder decodeObjectForKey: #"token"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:mConsumer forKey: #"consumer"];
[coder encodeObject:mToken forKey:#"token"];
}
Once you had done that work, you would convert MyObject to and from NSData like this:
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myObject];
MyObject* myObject = (MyObject*)[NSKeyedUnarchiver unarchiveObjectWithData: data];
The code you have here is totally going to smash the stack and crash (because this line [getData getBytes:&getObject]; will cause the NSData to write bytes to the address of getObject, which is locally declared on the stack. Hence stack smashing.) Starting from your code, a working implementation might look something like this:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSData* dataFromDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:#"linkedinfo"];
LinkedContainer* getObject = (LinkedContainer*)[NSKeyedUnarchiver unarchiveObjectWithData: dataFromDefaults];
if (!dataFromDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:#"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(loginViewDidFinish:)
name:#"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData* objectData = [NSKeyedArchiver archivedDataWithRootObject: mLinkedInInfo];
[[NSUserDefaults standardUserDefaults] setObject: objectData forKey: #"linkedinfo"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
I agree with ipmcc 's answer, another viable option would be to add methods to your object to convert it to an NSDictionary. You could add methods to -initWithDictionary as well and should make instantiation very easy. Pull from dictionary in NSUserDefaults to use, convert to dictionary to save.
Here is an example of those 2 methods with generic data:
- (id)initWithDictionary:(NSDictionary *)dict
{
self = [super init];
// This check serves to make sure that a non-NSDictionary object
// passed into the model class doesn't break the parsing.
if(self && [dict isKindOfClass:[NSDictionary class]]) {
NSObject *receivedFences = [dict objectForKey:#"fences"];
NSMutableArray *parsedFences = [NSMutableArray array];
if ([receivedFences isKindOfClass:[NSArray class]]) {
for (NSDictionary *item in (NSArray *)receivedFences) {
if ([item isKindOfClass:[NSDictionary class]]) {
[parsedFences addObject:[Fences modelObjectWithDictionary:item]];
}
}
}
}
// More checks for specific objects here
return self;
}
- (NSDictionary *)dictionaryRepresentation
{
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
NSMutableArray *tempArrayForFences = [NSMutableArray array];
for (NSObject *subArrayObject in self.fences) {
if([subArrayObject respondsToSelector:#selector(dictionaryRepresentation)]) {
// This class is a model object
[tempArrayForFences addObject:[subArrayObject performSelector:#selector(dictionaryRepresentation)]];
} else {
// Generic object
[tempArrayForFences addObject:subArrayObject];
}
}
[mutableDict setValue:[NSArray arrayWithArray:tempArrayForFences] forKey:#"fences"];
return [NSDictionary dictionaryWithDictionary:mutableDict];
}
This is basically boilerplate code that is generated by a program I use called JSON Accelerator. It will read a JSON string returned by an API and generate object code for you. Not really a new concept, but makes created classes for API's very easy. And this bit of code works great for creating dictionaries to be saved to NSUserDefaults. Hope this helps.

iOS:Retrieve data from function input parameter

I want to know how to retrieve the values by giving input parameter to a function. I want to retrieve in this way here as below, but it doesn't retrieve it rather giving empty array value when printing. I can't use even ampersand like c in parameter.
NSMutableArray *firstArray = [[NSMutableArray alloc] init];
NSMutableArray *secondArray = [[NSMutableArray alloc] init];
MyClass *myClassObj = [[MyClass alloc] init];
[myClassObj getVals : firstArray : secondArray];
NSLog(#"firstArray: %#", firstArray); // empty
NSLog(#"secondArray: %#", secondArray); // empty
// function to retrieve
- (void) getVals :(NSMutableArray *) firstArray :(NSMutableArray *) secondArray
{
firstArray = [NSMutableArray arrayWithObjects:#"val1", #"val2", #"val3", #"val4", nil];
secondArray = [NSMutableArray arrayWithObjects:#"val11", #"val22", #"val33", #"val44", nil];
}
Try this:
- (void) getVals :(NSMutableArray *) firstArray :(NSMutableArray *) secondArray {
[firstArray addObjectsFromArray:#[ #"val1", #"val2", #"val3", #"val4" ]];
[secondArray addObjectsFromArray:#[ #"val11", #"val22", #"val33", #"val44" ]];
}
The code you have assigns a new array to the local parameter values instead of adding values to the passed in arrays.
Your other option could be:
NSMutableArray *firstArray = nil;
NSMutableArray *secondArray = nil;
MyClass *myClassObj = [[MyClass alloc] init];
[myClassObj getVals : &firstArray : &secondArray];
- (void) getVals :(NSMutableArray **) firstArray :(NSMutableArray **) secondArray {
*firstArray = [NSMutableArray arrayWithObjects:#"val1", #"val2", #"val3", #"val4", nil];
*secondArray = [NSMutableArray arrayWithObjects:#"val11", #"val22", #"val33", #"val44", nil];
}
You are overriding the object reference with new object. U should rather use
[firstArray addObject:#"val1"]

Resources