Save States and Model - RITAccess/accessmath GitHub Wiki

Model

Note
Current fields are:

  • title
  • content
  • backgroundColor
  • fontColor
  • keywords - mapping lecture content to note content (different than user-tagging)
  • type - given by teacher, definition, equation, etc
  • location
  • external URL - supporting video URL to map note to specific external resources
  • modified date

Possible to instantiate the View within the Sprite node?

Save States

NSUserDefault saves the state of the given input. This is usually used with smaller and simpler pieces of information. https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSUserDefaults_Class/

NSUserDefault can save:

  • BOOL
  • Float
  • Integer
  • Object
  • Double
  • URLs

Example of saving and Array using NSUserDefaults: http://stackoverflow.com/questions/25617300/app-crashes-when-storing-nsarray-in-nsuserdefaults http://stackoverflow.com/questions/17522286/is-there-a-way-to-get-all-values-in-nsuserdefaults

NSData *dataSave = [NSKeyedArchiver archivedDataWithRootObject:yourArrayToBeSave]];
[[NSUserDefaults standardUserDefaults] setObject:dataSave forKey:@"array"];
[[NSUserDefaults standardUserDefaults] synchronize];

Saving Check Marks (via NSUserDefault)

Create an NSMutable array and initialize it using NSUserDefault.

@interface AssignmentsViewController (){
    NSMutableArray *SelectedRows;
}

In viewDidLoad method:

NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
SelectedRows = [NSMutableArray arrayWithArray:[userDef objectForKey:@"SelectedRows"]];

This array is used to store the NSNumber that has the same integer as indexPath.row. When the user selects a row, it stores that number, and when the row is deselected, the number is removed from the array so that the check mark will no longer be shown for that row.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ListPrototypeCell" forIndexPath:indexPath];
    AssignmentItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;

    NSNumber *obj = [NSNumber numberWithInteger:indexPath.row];
    if ([SelectedRows containsObject:obj])
    {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    }
    else
    {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {

    NSNumber *obj = [NSNumber numberWithInteger:indexPath.row];
    if ([SelectedRows containsObject:obj])
    {
        [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryNone;
        [SelectedRows removeObject:obj];
        //reloads the check marks at this point as opposed to using viewDidLoad method.
        [tableView reloadData]; 
    }else{
        [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
        [SelectedRows addObject:obj];
        [tableView reloadData];
    }

    NSUserDefaults *userDef = [NSUserDefaults standardUserDefaults];
    [userDef setObject:SelectedRows forKey:@"SelectedRows"];
    [userDef synchronize];

}

Retrieving the array: NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:@"array"]; NSArray *savedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];

NSUSerDefault would be simpler for objects like table cells or strings of text that would need to be stored and referred to later during or after the app has closed.

Saving Text Fields using NSUserDefault: This works very well to store only one set of information; akin to how autofill works. It will save the data being currently entered, however, if it is overwritten, the previous data will not be saved. The data will not disappear even if the application is closed.

https://www.youtube.com/watch?v=NavVADVU6fk

.h interface file:

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController {
    IBOutlet UITextField *field1;

    IBOutlet UILabel *loaded;
}

//Database Actions
- (IBAction) save:(id)sender;
- (IBAction) load:(id)sender;

//Keyboard Dismiss
- (IBAction) dismiss1:(id)sender;

@end

.m implementation file:

@implementation ViewController

- (IBAction) save:(id)sender{

    NSString *saveString = field1.text;
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:saveString forKey:@"saveString"];
    [defaults synchronize];

    loaded.text = @"Data Saved Successfully";
}

- (IBAction) load:(id)sender{

    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSString *loadString = [defaults objectForKey:@"saveString"];
    [field1 setText:loadString];
    [loaded setText:@"Data Loaded Successfully"];
}

- (IBAction)dismiss1:(id)sender{
    [sender resignFirstResponder];
}

- (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.
}

@end

Using a database to save information: http://www.appcoda.com/sqlite-database-ios-app-tutorial/

Using a database to save information helps keep all data in the same area as well as allowing for things to be changed dynamically. This would be particularly useful for students saving new notes that they put in themselves in the app. However, this may cause issues in areas that need to be saved such as the checkmarks depending on whether or not checkmarks are added to the database. NSUserDefaults or NSKeyedArchiver might be simpler for cases such as the assignments page.

Example of database save states:

- (IBAction)saveInfo:(id)sender {
    // Prepare the query string.    
    NSString *query = [NSString stringWithFormat:@"insert into peopleInfo values(null, '%@', '%@', %d)", self.txtFirstname.text, self.txtLastname.text, [self.txtAge.text intValue]];

    // Execute the query.
    [self.dbManager executeQuery:query];

    // If the query was successfully executed then pop the view controller.
    if (self.dbManager.affectedRows != 0) {
        NSLog(@"Query was executed successfully. Affected rows = %d", self.dbManager.affectedRows);

        // Pop the view controller.
        [self.navigationController popViewControllerAnimated:YES];
    }
    else{
        NSLog(@"Could not execute the query.");
    }
}

This piece of code inserts the new information into the sql database and saves where on the table it will go.

Using NSKeyedArchiver to save data:

This worked extremely well for the shuffling note card portion (coupled with the NSCoding). However, two problems have arised with its use via storyboard. Previously, the saved view would appear above the UITableView, and the user interactivity would be disabled for it. Now only a blank table view would appear whenever the app is reloaded. Update: all assignments are check marked and are saved as such and won’t change whatsoever; I have to rethink this code. NSUserDefaults worked very well for getting the zoom selection to work; still needs some fine tuning, but it is saving the last checkmark.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"ListPrototypeCell" forIndexPath:indexPath];
    PresetZoomItem *toDoItem = [self.toDoItems objectAtIndex:indexPath.row];
    cell.textLabel.text = toDoItem.itemName;
    if (indexPath.row == [[NSUserDefaults standardUserDefaults]integerForKey:@"Checked"]) {
        cell.accessoryType = UITableViewCellAccessoryCheckmark;
    } else {
        cell.accessoryType = UITableViewCellAccessoryNone;
    }
    return cell;
}

// the following two functions allow for only one row to be selected at a time
// gets rid of multiple selection issue
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath   *)indexPath
{
    [[NSUserDefaults standardUserDefaults] setInteger:indexPath.row forKey:@"Checked"];
    [[NSUserDefaults standardUserDefaults] synchronize];

    [tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
}

NSCoding

http://www.raywenderlich.com/1914/nscoding-tutorial-for-ios-how-to-save-your-app-data http://www.raywenderlich.com/63235/how-to-save-your-game-data-tutorial-part-1-of-2

These tutorials give in-depth walkthroughs on how NSCoding works to create a basic save functionality for the SpriteKit section.

It consists of several functions that form the basis for saving via NSCoding, the most common being:

  • -(instancetype) initWithCoder:(NSCoder *)decoder: "deserializes" the data; initializes a new instance with data

  • -(void) encodeWithCoder:(NSCoder *)encoder: "serializes" that data; allows the data to persist even when app has been closed

  • +(NSString*) filePath: creates the path where the data will be stored

  • +(instancetype) loadInstance: so long as decoded data is not nil, makes a NSData instance from it

  • +(instancetype) sharedData: allows for access to the same instance of saved data

  • -(void) save: gets data from class instance then saves it to file

*Note: Additional functions can be implemented depending on what the program requires, however, this is the basic framework for this particular use of NSCoding.

Example of NSCoding encoding and decoding:

//define kTitleKey       @"Title"
//define kRatingKey      @"Rating"

- (void) encodeWithCoder:(NSCoder *)encoder {
    //note that 'title' and 'rating' are both located in the .h file
    [encoder encodeObject:_title forKey:kTitleKey];
    [encoder encodeFloat:_rating forKey:kRatingKey];
}

- (id)initWithCoder:(NSCoder *)decoder {
    NSString *title = [decoder decodeObjectForKey:kTitleKey];
    float rating = [decoder decodeFloatForKey:kRatingKey];
    return [self initWithTitle:title rating:rating];
}

+ (NSString*)filePath {
    static NSString* filePath = nil;
    if (!filePath) {
       filePath = 
       [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject]
       stringByAppendingPathComponent:@"data"];
    }
    return filePath;
}

+ (instancetype)loadInstance {
      NSData* decodedData = [NSData dataWithContentsOfFile: [RWGameData filePath]];
      if (decodedData) {
        //With respect to the tutorial, RWGameData is the name of the file in which these methods are located.
        RWGameData* gameData = [NSKeyedUnarchiver unarchiveObjectWithData:decodedData];
        return gameData;
      }

      return [[RWGameData alloc] init];
}

+ (instancetype)sharedData {
     static id sharedInstance = nil;

     static dispatch_once_t onceToken;
     dispatch_once(&onceToken, ^{
        sharedInstance = [self loadInstance];
     });

     return sharedInstance;
}

- (void)save {
      NSData* encodedData = [NSKeyedArchiver archivedDataWithRootObject: self];
      [encodedData writeToFile:[RWGameData filePath] atomically:YES];
}

Current iOS Version for save states: 8.3