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.
Related
I am writing a small iOS app that I hope would facilitate my field work. I am on OS X El Capitan 10.11.4, using Xcode 7.3 and writing the app for iOS 9.3.
Here is the whole idea: instead of plugging the data in to a piece of paper I want to write everything in to a basic form-like app to be used on an iPad, and then have the app transfer the data to a .csv file.
I have pretty much everything figured out (the structure of the app, the protocol to transfer and retrieve data etc. etc.), but there is one thing that is driving me crazy. Before I show you the code that I am using, here is the problem. When I click the Save button and later on go to inspect the saved data, I cannot get the value selected by the UIPickerView objects to be transferred to the .csv file. Everything else works fine. I have two UIPickerView objects. In one I have sex information, coded as M, F, and M/F; the other has information about age coded as Adu, Juv, Hat. What I would like is for the app to save the value that is selected, but I couldn't quite figure that out.
Any help is much appreciated. Thanks.
Here is the code that I am using for my ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UIPickerViewDataSource, UIPickerViewDelegate>
#property (strong, nonatomic) IBOutlet UIDatePicker *day;
#property (strong, nonatomic) IBOutlet UITextField *species;
#property (strong, nonatomic) IBOutlet UITextField *island;
#property (strong, nonatomic) IBOutlet UITextField *cay;
#property (strong, nonatomic) IBOutlet UITextField *pit;
#property (strong, nonatomic) IBOutlet UITextField *coordinatesN;
#property (strong, nonatomic) IBOutlet UITextField *coordinatesW;
#property (strong, nonatomic) IBOutlet UIPickerView *sex;
#property (strong, nonatomic) IBOutlet UIPickerView *age;
- (IBAction)saveInfo:(id)sender;
- (IBAction)retrieveInfo:(id)sender;
- (IBAction)retractKeyboard:(id)sender;
#property (strong, nonatomic) IBOutlet UITextView *resultView;
#end
And this is the code that I am using for my ViewConntroller.m
#import "ViewController.h"
#interface ViewController ()
{
NSArray *pickerDataSex;
NSArray *pickerDataAge;
}
#end
#implementation ViewController
#synthesize resultView;
#synthesize day;
#synthesize species;
#synthesize island;
#synthesize cay;
#synthesize pit;
#synthesize coordinatesW;
#synthesize coordinatesN;
#synthesize sex;
#synthesize age;
- (IBAction)retractKeyboard:(id)sender {
[self resignFirstResponder];
}
- (IBAction)saveInfo:(id)sender {
NSString *resultLine=[NSString stringWithFormat:#"%#,%#,%#,%#,%#,%#,%#,%#,%#\n",
self.day.date,
self.species.text,
self.island.text,
self.cay.text,
self.pit.text,
self.coordinatesN.text,
self.coordinatesW.text,
self.sex.dataSource,
self.age.dataSource];
NSString *docPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES )objectAtIndex:0];
//resultView.text = docPath;}
NSString *surveys=[docPath stringByAppendingPathComponent:#"results.csv"];
if (![[NSFileManager defaultManager] fileExistsAtPath:surveys]) {
[[NSFileManager defaultManager] createFileAtPath:surveys contents:nil attributes:nil];
}
NSFileHandle *fileHandle = [NSFileHandle fileHandleForUpdatingAtPath:surveys];
[fileHandle seekToEndOfFile];
[fileHandle writeData:[resultLine dataUsingEncoding:NSUTF8StringEncoding]];
[fileHandle closeFile];
self.species.text=#"";
self.island.text=#"";
self.cay.text=#"";
self.pit.text=#"";
self.coordinatesN.text=#"";
self.coordinatesW.text=#"";
NSLog(#"info saved");
}
- (IBAction)retrieveInfo:(id)sender {
NSString *docPath =[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES )objectAtIndex:0];
//resultView.text = docPath;
NSString *surveys=[docPath stringByAppendingPathComponent:#"results.csv"];
if ([[NSFileManager defaultManager] fileExistsAtPath: surveys]) {
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingAtPath:surveys];
NSString *surveyResults = [[NSString alloc]initWithData:[fileHandle availableData] encoding:NSUTF8StringEncoding];
[fileHandle closeFile];
self.resultView.text = surveyResults;
}
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
//Initialize picker data
pickerDataSex = #[#"M",#"F",#"M/F"];
pickerDataAge = #[#"Adu",#"Juv",#"Hat"];
//Connect data to picker
self.sex.dataSource = self;
self.sex.delegate = self;
self.age.dataSource = self;
self.age.delegate = self;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
//Number of columns of the data
- (NSInteger)numberOfComponentsInPickerView: (UIPickerView *)pickerView
{
if (pickerView==self.sex){
return 1;
} else if (pickerView==self.age){
return 1;
}
return 0;
}
//Number of rows of data
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
if (pickerView==self.sex){
return pickerDataSex.count;
} else if (pickerView==self.age){
return pickerDataAge.count;
}
return 0;
}
//The data to return for the row and component (column) that's being passed
-(NSString*)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
if (pickerView==self.sex){
return pickerDataSex[row];
} else if (pickerView==self.age){
return pickerDataAge[row];
}
return 0;
}
#end
You can get values from your UIPickerViews that way :
NSInteger selectedRowSex = [self.sex selectedRowInComponent:0];
NSInteger selectedRowAge = [self.age selectedRowInComponent:0];
NSString *selectedSex = [pickerDataSex objectAtIndex:selectedRowSex];
NSString *selectedAge = [pickerDataAge objectAtIndex:selectedRowAge];
Then
NSString *resultLine = [NSString stringWithFormat:#"%#,%#,%#,%#,%#,%#,%#,%#,%#\n",
self.day.date,
self.species.text,
self.island.text,
self.cay.text,
self.pit.text,
self.coordinatesN.text,
self.coordinatesW.text,
selectedSex,
selectedAge];
You have code that populates a your picker view, but have not shown any code that gets the selected value from the picker view.
The easiest way to handle that is to add code in your saveInfo method that calls selectedRowInComponent on your picker views to get the current value.
Alternately you can implement the pickerView:didSelectRow:inComponent delegate method and read the newly selected picker value each time it changes.
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];
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".