I have a NSMutableArray called _objects stored in NSUserDefaults. I access _objects upon app launch and put them in a CollectionView with custom cell (see below). I let the user edit each item by first entering edit mode and then tapping the desired cell. The cell class then alters the visual appearance correspondingly and reveal a UITextField. Whenever the user presses done on the keyboard while entering data here, the ProjectsViewController (CollectionView viewcontroller) gets a notification from the custom class to resave the items. How can I save the new _objects then?
Specifically I need to edit a cell (done), edit the corresponding item in _objects and resave _objects. How can I achieve this?
My custom class:
ProjectCell.h:
#import <UIKit/UIKit.h>
#interface ProjectCell : UICollectionViewCell
#property (weak, nonatomic) IBOutlet UILabel *projectLabel;
#property (weak, nonatomic) IBOutlet UILabel *projectCount;
#property (weak, nonatomic) IBOutlet UITextField *projectTextField;
#property (weak, nonatomic) IBOutlet UILabel *editModeLabel;
#property (nonatomic, assign) BOOL editMode;;
-(void)editProject;
#end
ProjectCell.m:
#import "ProjectCell.h"
#import "QuartzCore/CALayer.h"
#implementation ProjectCell
#synthesize editMode;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
-(void)awakeFromNib {
// Custom initial code
[_projectTextField setDelegate:self];
[_projectTextField setReturnKeyType:UIReturnKeyDone];
[_projectTextField addTarget:self
action:#selector(editingFinished:)
forControlEvents:UIControlEventEditingDidEndOnExit];
}
- (IBAction)editingFinished:(id)sender
{
// Update the project cell visually
_projectLabel.text = _projectTextField.text;
// disable editMode and update the project cell
editMode = NO;
_projectTextField.hidden = YES;
_editModeLabel.hidden = YES;
// Reload the MasterViewController
[[NSNotificationCenter defaultCenter] postNotificationName:#"saveNewProjects" object:self];
}
-(void)editProject {
if (editMode == YES) {
// disable editMode and update the project cell
editMode = NO;
_projectTextField.hidden = YES;
_editModeLabel.hidden = YES;
} else if (editMode == NO) {
// Enable editMode and update the project cell
editMode = YES;
_projectTextField.hidden = NO;
_editModeLabel.hidden = NO;
}
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
ProjectsViewController.m
This method is called whenever the user presses "done" while editing a cell.
I tried to cycle through every item and "recreate" the NSMutableArray but I
don't think this is a very good approach.
- (void)resaveProjects:(NSNotification *)notification {
// Edit the edited projects and save all again
editedProjects = editedProjects - 1;
NSMutableArray *newObjects;
int objectsCount = _objects.count;
for (int i = 0; i < objectsCount; i++) {
// Initialize objects
if (!newObjects) {
newObjects = [[NSMutableArray alloc] init];
}
}
}
What do you think is the best approach for this and how do I proceed with that?
Thanks!
Edit:
Reading the _objects:
NSMutableArray *_objects;
_objects = [NSMutableArray arrayWithArray:[[NSUserDefaults standardUserDefaults] objectForKey:#"myProjects"]];
In iOS or Mac OS, when you read a collection object like a dictionary or array (from user defaults or a plist) the object is read in as immutable, even if the original object was mutable.
Thus, after reading in a dictionary from user defaults, you have to convert it to a mutable object.
I would suggest you read your object from user defaults and convert it to a mutable version:
In your header:
#property (nonatomic, strong) NSMutableDictionary *aDict;
And in your .m file, when you read the dictionary:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableDictionary *aDict = [[defaults objectForKey: #"aDictionaryKey"] mutableCopy];
Then, when the user does something that requires you to change the data:
aDict[key] = newValueFromUser;
[defaults setObject: aDict forKey: #"aDictionaryKey"];
Note that if your dictionary contains other collection objects (arrays or other dictionaries), those also become immutable when read back from a file. That means that you may have to walk through the "object graph" of your dictionary, converting each collection inside to a mutable version and saving it in it's enclosing collection.
I just found some posts online that claim that NSCoding preserves the mutability of objects. If that's the case then you might want to save your dictionary to an archive file rather than saving it to user defaults:
Writing your dictionary to a file:
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) lastObject]
NSString *path = [documentsDir stringByAppendingPathComponent: #"dictFile"];
[NSKeyedArchiver archiveRootObject: myDict toFile: path];
Reading it back:
NSString *documentsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) lastObject]
NSString *path = [documentsDir stringByAppendingPathComponent: #"dictFile"];
myDict = [NSKeyedUnarchiver unarchiveObjectWithFile: path];
Related
It seems if I do something like:
NSMutableArray *randomSelection = [[NSMutableArray alloc] init];
Then this needs to be in a function, and I can't modify it later using a different function.
I tried just instantiating it in the .h file,
#interface ViewController:
{
NSMutableArray *Values;
}
But then when I try to append to it during runtime, nothing happens. I try to append to it with this:
int intRSSI = [RSSI intValue];
NSString* myRSSI = [#(intRSSI) stringValue];
[Values addObject:myRSSI];
But the array remains empty when I do this.
How can I fix this?
The recommended way is to create a property;
// ViewController.h
#interface ViewController : UIViewController
{
}
#property (nonatomic, strong) NSMutableArray *values;
#end
Then override the getter for that property, to lazy-initialize it, i.e. the array will be allocated and initialized on first call of the NSMutableArray property's getter:
// ViewController.m
#interface ViewController ()
#end
#implementation ViewController
- (NSMutableArray *)values
{
if (!_values) {
_values = [[NSMutableArray alloc] init];
}
return _values;
}
- (void)viewDidLoad
{
[super viewDidLoad];
//int intRSSI = [RSSI intValue];
//NSString *myRSSI = [#(intRSSI) stringValue];
//[self.values addObject:myRSSI];
// Keep it simple:
[self.values addObject:RSSI];
}
#end
I have some UISwitches and I want to load their states from a .plist file. I've tried countless times but it seems not to be working. This is my code:
-(void) loadVariables {
NSMutableDictionary *dictionary;
NSString *rootPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES) objectAtIndex:0];
NSString *plistPath = [rootPath stringByAppendingPathComponent:#"Settings.plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
plistPath = [[NSBundle mainBundle] pathForResource:#"Settings" ofType:#"plist"];
NSLog(#"NOT FOUND%#",plistPath);
}
dictionary = [NSMutableDictionary dictionaryWithContentsOfFile:plistPath];
[[Settings sharedInstance] setMobileSwitch:[[dictionary objectForKey:#"mobileSwitch"]boolValue]];
[[Settings sharedInstance] setEmailSwitch:[[dictionary objectForKey:#"emailSwitch"]boolValue]];
[[Settings sharedInstance] setLocationSync:[[dictionary objectForKey:#"locationSync"]boolValue]];
[[Settings sharedInstance] setCaptureLocation:[[dictionary objectForKey:#"captureLocation"]boolValue]];
[self.mobileSwitchButton setOn:[[dictionary objectForKey:#"mobileSwitch"]boolValue]];
[self.emailSwitchButton setOn:[[dictionary objectForKey:#"emailSwitch"]boolValue]];
[self.locationSyncButton setOn:[[dictionary objectForKey:#"locationSync"]boolValue]];
[self.captureLocationButton setOn:[[dictionary objectForKey:#"captureLocation"]boolValue]];
}
For some reason when I call it in my viewDidLoad method like [self.mobileSwitchButton setOn:1]; it works. What am I doing wrong?
In NSLog my dictionary prints the following:
dictionary {
captureLocation = 0;
emailSwitch = 1;
locationSync = 1;
mobileSwitch = 0;
}
In my ViewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
[mobileSwitchButton addTarget:self action:#selector(setState:) forControlEvents:UIControlEventValueChanged];
[emailSwitchButton addTarget:self action:#selector(setState:) forControlEvents:UIControlEventValueChanged];
[locationSyncButton addTarget:self action:#selector(setState:) forControlEvents:UIControlEventValueChanged];
[captureLocationButton addTarget:self action:#selector(setState:) forControlEvents:UIControlEventValueChanged];
[[Settings sharedInstance] loadVariables];
}
In my .h file I also have this:
//Switches
#property (retain, nonatomic) IBOutlet UISwitch *mobileSwitchButton;
#property (retain, nonatomic) IBOutlet UISwitch *emailSwitchButton;
#property (retain, nonatomic) IBOutlet UISwitch *locationSyncButton;
#property (retain, nonatomic) IBOutlet UISwitch *captureLocationButton;
In my implementation file I also have this:
+(Settings *)sharedInstance {
static Settings *myInstance = nil;
// check to see if an instance already exists
if (nil == myInstance) {
myInstance = [[[self class] alloc] init];
}
// return the instance of this class
return myInstance;
}
I'm calling my loadVariables method in my ViewDidLoad method and I'm trying to change the state of my UISwitch in my loadVariables method. If I do place the code for changing the UISwitch state in the ViewDidLoad it works. But the code doesn't seem to execute from my [[Settings sharedInstance] loadVariables] method.
Have you tried setting the switches in viewWillAppear? That seems to me the most appropriate place.
Also, there seem to be two instances of your loadVariables method. The singleton cannot / should not contain the switches. But in viewDidLoad you call the singleton's method. This might be part of your problem. Get the values from your singleton and set the in the view controller.
Presumably dictionary is nil, or it doesn't contain any object for the key you are giving.
Do you execute this code when your views have been already created? If you do it prior to viewDidLoad call, your switches may be equal to nil, and therefore your code won't change their state.
Here is my mainAppDelegate.h:
#import <UIKit/UIKit.h>
#interface mainAppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#property NSMutableArray *toDoItems;
#end
and my mainAppDelegate.m:
#import "mainAppDelegate.h"
#implementation mainAppDelegate
- (void)applicationWillTerminate:(UIApplication *)application
{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *filePath = [documentsDirectory stringByAppendingPathComponent:#"toDoItems.plist"];
[self.toDoItems writeToFile:filePath atomically:TRUE];
}
#end
I have another file, XYZToDoListViewController.m with the code:
#import "XYZToDoListViewController.h"
#import "XYZToDoItem.h"
#import "XYZAddItemViewController.h"
#interface XYZToDoListViewController ()
#property NSMutableArray *toDoItems;
#end
#implementation XYZToDoListViewController
- (IBAction)unwindToList:(UIStoryboardSegue *)segue
{
XYZAddItemViewController *source = [segue sourceViewController];
XYZToDoItem *item = source.toDoItem;
if (item != nil) {
[self.toDoItems addObject:item];
[self.tableView reloadData];
}
}
- (IBAction)clearData:(UIBarButtonItem *)sender;
{
[self.toDoItems removeAllObjects];
[self.tableView reloadData];
}
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.toDoItems = [[NSMutableArray alloc] init];
self.navigationController.view.backgroundColor =
[UIColor colorWithPatternImage:[UIImage imageNamed:#"bg_full.png"]];
self.tableView.backgroundColor = [UIColor clearColor];
self.tableView.contentInset = UIEdgeInsetsMake(-35, 0, -35, 0);
}
#end
This is at least what I think is relevant. My basic framework is something I followed from this tutorial. So as you can see I have an NSMutableArray named .toDoItems. and I want it to remember the list when I exit the application (not just minimizing it; it does that already. I think I have the code which is saving the data, but what do I use to check if the file exists, and if so, display it?
And would this affect my clearData method when the app resumes as well?
As if i understood true you want to save your NSMutableArray even after restart your app.
So you may use this codes to save your NSMutableArray with NSUserDefaults.
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaultsPhoneId setObject:yourArray forKey:#"yourKey"];
[defaultsPhoneId synchronize];
And it is how you can take it back
yourArray=[[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"yourKey"]];
But dont forget to initialize it before save with NSUserDefaults.
By the way you may check yourArray anytime if it is loaded or not with
if([yourArray count]==0){
//Your array is empty so you have to call it with #"yourKey". (second codes part)
}
"what do I use to check if the file exists"
NSFileManager *fileManager = [NSFileManager defaultManager];
if ([fileManager fileExistsAtPath:your_file_path])
"..and if so, display it?"
Please read about UITableView and UITableViewDataSource
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableView_Class/Reference/Reference.html
"And would this affect my clearData method when the app resumes as well?"
clearData is a button action. It will be called only if user will tap
a button. It is not connected with application launch/stopping.
I am working on an app for a Senior Capstone and I am working with .plist for the first time. What I have so far is in my .h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
-(NSString *)dataFilePath;
-(IBAction) readPlist:(id)sender;
-(IBAction) writePlist:(id)sender;
#property (weak, nonatomic) IBOutlet UITextField *textBox;
#end
and in my .m I have:
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
-(NSString *) dataFilePath
{
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES );
NSString *documentDirectory = [path objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:#"JoesData.plist"];
}
- (IBAction)readPlist:(id)sender
{
NSString *filePath = [self dataFilePath];
if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
{
NSArray *array = [[NSArray alloc] initWithContentsOfFile:filePath];
NSLog(#"%#\n",array);
NSLog(#"%#\n", filePath);
}
}
- (IBAction)writePlist:(id)sender {
NSString *string = _textBox.text;
NSMutableArray *anArray = [[NSMutableArray alloc] init];
[anArray addObject:string];
[anArray writeToFile:[self dataFilePath] atomically:YES];
}
#end
so what this does is creates a .plist based upon what is in the text box that I have set up in my storyboard. My problem with this is that it will read and write it just fine, but it won't keep a running list of the things that are entered into the text box. Instead, it simply overwrites the previous .plist. Any thoughts on how to fix the overwriting problem?
Read the plist into memory, make a mutable copy of the array, and add the object to that array, instead of creating a new NSMutableArray every time.
An alternative option is to do the following.
To read the property-list data back into your program, first initialize an allocated NSData object by invoking initWithContentsOfFile: or initWithContentsOfURL: or call a corresponding class factory method such as dataWithContentsOfFile:. Then call the propertyListFromData:mutabilityOption:format:errorDescription: class method of NSPropertyListSerialization, passing in the data object.
Property List Programming Guide
I have a singleton with an NSMutableDictionary in it. I want to add an entry to that dictionary from one of my views. For a reason that I can't comprehend it's not working and I'm receiving the 'NSDictionary setObject:forKey: unrecognized selector sent to instance' error. This doesn't seem like it should be so hard but I can't find an answer to the problem.
So I've wired up a button in my .xib to call the createKey method and kablooey. I've also tested to ensure that the dictionary exists and it does.
Here's my singleton header:
#import <Foundation/Foundation.h>
#interface SharedAppData : NSObject <NSCoding>
{
NSMutableDictionary *apiKeyDictionary;
}
+ (SharedAppData *)sharedStore;
#property (nonatomic, copy) NSMutableDictionary *apiKeyDictionary;
-(BOOL)saveChanges;
#end
My singleton implementation (important bits)
#interface SharedAppData()
#end
#implementation SharedAppData
#synthesize apiKeyDictionary;
static SharedAppData *sharedStore = nil;
+(SharedAppData*)sharedStore {
#synchronized(self){
if(sharedStore == nil){
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *testFile = [documentsDirectory stringByAppendingPathComponent:#"testfile.sav"];
Boolean fileExists = [[NSFileManager defaultManager] fileExistsAtPath:testFile];
if(fileExists) {
sharedStore = [NSKeyedUnarchiver unarchiveObjectWithFile:testFile];
}
else{
sharedStore = [[super allocWithZone:NULL] init];
}
[sharedStore setSaveFile:testFile];
}
return sharedStore;
}
}
- (id)init {
if (self = [super init]) {
apiKeyDictionary = [[NSMutableDictionary alloc] init];
}
return self;
}
In my view controller header...
#import <UIKit/UIKit.h>
#import "SharedAppData.h"
#interface AddKeyViewController : UIViewController <UITextFieldDelegate>
{
UIButton *addKey;
}
#property (weak, nonatomic) IBOutlet UITextField *apiName;
#property (weak, nonatomic) IBOutlet UITextField *apiKey;
-(IBAction)createKey:(id)sender;
#end
View controller implementation:
#import "AddKeyViewController.h"
#import "SharedAppData.h"
#interface AddKeyViewController ()
#end
#implementation AddKeyViewController
#synthesize apiName, apiKey, toolbar;
-(IBAction)createKey:(id)sender {
NSString *name = [apiName text];
NSString *key = [apiKey text];
[[[SharedAppData sharedStore] apiKeyDictionary] setObject:key forKey:name];
}
#end
Your apiKeyDictionary property is set to copy. That will send the copy message to the NSMutableDictionary instance you create in your init method - returning not an NSMutableDictionary but an NSDictionary. Change it to strong or retain instead.
I suspect that the problem is with your property being "copy" rather than "strong"; when you set it, mutanle dictionsry gets copied into an immutable one.
Try changing your property to "strong".