iOS - NSUserDefaults Mutable Array not displaying when app is re-opened - ios

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
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
}
#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;
}
NSUserDefaults *defaults= [NSUserDefaults standardUserDefaults];
if([[[defaults dictionaryRepresentation] allKeys] containsObject:#"toDoItems"]){
NSLog(#"toDoItems found");
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
} else {
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.
What am I doing wrong? When I add items into my to-do list, terminate the application, then relaunch the app the data i previously entered does not display. There are no errors or warnings on the project, or crashes.

applicationWillTerminate: isn't called I presume, put that userdefault synchronize code somewhere else.
By the way, when you restart debugger, the process is just killed, not "terminated".
That is why I usually call synchronize at every change of data, to make sure data is saved in case of a crash, or simply when I restart debugger.

On a totally different side note, I'd like to point out to you that you can change this
if([[[defaults dictionaryRepresentation] allKeys] containsObject:#"toDoItems"]){
NSLog(#"toDoItems found");
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
} else {
self.toDoItems = [[NSMutableArray alloc] init];
}
To that
self.toDoItems = [[NSMutableArray alloc]initWithArray:[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"]];
Because if you try to get an object that is not set from user defaults (or any dictionary), you get nil back. And if you init your mutable array with a nil array you will have a valid, yet empty, mutable array.
Since it didn't fit as a comment I wrote it as an answer, though of course it has nothing to do with your original problem

[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[[NSUserDefaults standardUserDefaults]objectForKey:#"loadItems"]
The 2 don't have the same key. You set #"toDoItems" and try to get #"loadItems".

You are not persisting the data. Right now, when you create a item, it lives in memory, but it is never written to the disk, so when the app is terminated, the memory that contained the item is released. You need to write the items to the disk so they can be used after the app is terminated. Either you can archive the items, or you can use Core Data. Don't use NSUserDefaults, which is for user setting.Here is a tutorial for archiving, you can find the Core Data guide on the Apple developer website.

You'll have an exceptionally difficult time trying to simulate applicationWillTerminate being called.
I suggest you save your data when it changes (preferred) or at least on something a bit more predictable applicationWillEnterForeground: or applicationWillResignActive:.
- (void) applicationWillResignActive:(UIApplication *)application
{
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
}
As Emilie Lessard pointed out, you must use the same key to get the right data.
[[NSUserDefaults standardUserDefaults]objectForKey:#"toDoItems"];

I suppose you don't save the list on the mainAppDelegate class. So, you need to save your data on the class where the list is being formed and delete the invocation your mainAppDelegate. Maybe you're saving a nil value when your app terminate.
NSUserDefaults *defaults=[NSUserDefaults standardUserDefaults];
[defaults setObject:self.toDoItems forKey:#"toDoItems"];
[defaults synchronize];
And later you need to invoke it with the same key #"toDoItems".

Related

How to update NSKeyedUnarchiver unarchiveObjectWithData to NSKeyedUnarchiver unarchivedObjectOfClass:[fromData:error:

My app currently uses this deprecated function:
id unarchivedObject=[NSKeyedUnarchiver unarchiveObjectWithData:codedData];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns TRUE when reading existing user data.
}
To update, I've converted to this:
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[NSDictionary class] fromData:codedData error:nil];
if([unarchivedObject isKindOfClass:[NSDictionary class]]){
// currently returns FALSE when reading existing user data.
}
The data was originally encoded like this:
-(void)encodeWithCoder:(NSCoder*)encoder{
[encoder encodeObject:text forKey:#"text"];
}
-(instancetype)initWithCoder:(NSCoder*)decoder{
if(self=[super init]){
text=[decoder decodeObjectForKey:#"text"];
}
What could be causing the IF statement to return FALSE using the newer code?
Please note that I am concerned primarily with reading existing data stored prior to deprecating the Archiving functions. Simply changing to the newer functions does not resolve the issue.
Interesting question! I've been supporting iOS 10.0 so I haven't encountered such issue until I saw this. I was tinkering for an hour and I successfully found the issue.
What could be causing the IF statement to return FALSE using the newer
code?
It's because your unarchivedObject object is nil!
If you use the parameter error in the new method, you would see an error like this:
Error Domain=NSCocoaErrorDomain Code=4864 "This decoder will only
decode classes that adopt NSSecureCoding. Class 'QTPerson' does not
adopt it." UserInfo={NSDebugDescription=This decoder will only decode
classes that adopt NSSecureCoding. Class 'QTPerson' does not adopt it.
But how do we get the correct value for this unarchivedObject and not nil? It would take a couple of steps.
First off, make your model/class conform to <NSCoding, NSSecureCoding>
Example:
QTPerson.h
#import <Foundation/Foundation.h>
#class QTPerson;
NS_ASSUME_NONNULL_BEGIN
#pragma mark - Object interfaces
#interface QTPerson : NSObject <NSCoding, NSSecureCoding>
#property (nonatomic, copy) NSString *text;
#end
NS_ASSUME_NONNULL_END
And then implement the protocol methods:
QTPerson.m
#import "QTPerson.h"
#implementation QTPerson
+ (BOOL)supportsSecureCoding {
return YES;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_text forKey:#"text"];
}
- (instancetype)initWithCoder:(NSCoder *)coder {
self = [super init];
if (self) {
_text = [coder decodeObjectOfClass:[NSString class] forKey:#"text"];
}
return self;
}
#end
And then when archiving an object, you would want to pass YES to the parameter requiringSecureCoding, like so:
QTPerson *person = [[QTPerson alloc] init];
person.text = #"Glenn";
NSData *codedData1 = [NSKeyedArchiver archivedDataWithRootObject:person requiringSecureCoding:YES error:nil];
[[NSUserDefaults standardUserDefaults] setValue:codedData1 forKey:#"boom"];
Lastly, when unarchiving, just do what you did correctly, like so:
NSData *codedData = [[NSUserDefaults standardUserDefaults] dataForKey:#"boom"];
NSError *er;
id unarchivedObject=[NSKeyedUnarchiver unarchivedObjectOfClass:[QTPerson class] fromData:codedData error:&er];
if([unarchivedObject isKindOfClass:[QTPerson class]]){
NSLog(#"TRUE!");
} else {
NSLog(#"FALSE!");
}
Voila! You'll get nonnull object unarchivedObject, hence the TRUE/YES value you're looking for!

store key/value in NSDictionary/NSCache which retains data till the app is terminated

I have an app where i want to create a temporary cache which stores key and value.I have done the following
My code is : IN appDelegate.h
#property (strong, nonatomic) NSMutableDictionary *articleCache;
In appDelegate.m
#synthesize articleCache;
and i am calling it in viewController.m
here i need to store the data so that it is cleared only when the app is terminated and is accessible anywhere in the app otherwise.
every time i visit an article i add it to the array so that next time i wont have to fetch it from the network thereby speed up the process.
the Problem is when i set the temp NSMutableDictionary the content gets added but for checkCache.articleCache i get nil.
#define DELEGATE ((AppDelegate*)[[UIApplication sharedApplication]delegate])
this is my viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
//[self loadfeeds];
[self.activityIndi startAnimating];
AppDelegate *checkCache = DELEGATE;
NSString *link = self.webUrl;
//check if the article is already opened and cached before
if([[checkCache.articleCache allKeys] containsObject:link])
{
NSLog(#"Key Exists");
NSString *contents = [checkCache.articleCache valueForKey:link];
[self loadDataOnView:contents];
}
else
{
NSOperationQueue* aQueue = [[NSOperationQueue alloc] init];
[aQueue addOperationWithBlock:^{
NSLog(#"Key not Exists");
[self startParsing];
}];
}
}
In parser method at the end i do the following i.e to store the article..
but if i add it directly to the checkCache.articleCache nothing is added what should i do?? but it gets added to temp.. do i access the articleCache incorrectly??
AppDelegate *checkCache = DELEGATE;
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
[checkCache.articleCache setObject:Content forKey:url];
[temp setObject:Content forKey:url];
So how can i solve it??
or Suggest me how can i use NSCache for the same problem. thanks a lot.
It might be a silly question but i m quite new to ios thanks.
In App delegate:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
self.articleCache = [NSMutableDictionary new];
return YES;
}
When you have to set the object in cache.
AppDelegate *checkCache = DELEGATE;
[checkCache.articleCache setObject:obj forKey:#"Key1"];
To get the object back:
AppDelegate *checkCache = DELEGATE;
id obj = [checkCache.articleCache objectForKey:#"Key1"];
Though there are better ways to get this done.

Store Singleton Array for Use After App Re-Launches?

I need to share an NSMutabaleArray between multiple ViewControllers, so I created a singleton. But when I re-launch my app, the array seems to clear itself which causes problems within my app. This is my singleton:
.h:
#import <Foundation/Foundation.h>
#interface playlistArray : NSObject {
NSMutableArray *playlistSongsArray;
}
#property (nonatomic, retain) NSMutableArray *playlistSongsArray;
+ (id)sharedArray;
#end
.m
#import "playlistArray.h"
#implementation playlistArray
#synthesize playlistSongsArray;
#pragma mark Singleton Methods
+ (id)sharedArray {
static playlistArray *sharedArray = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedArray = [[self alloc] init];
});
return sharedArray;
}
- (id)init {
if (self = [super init]) {
self.playlistSongsArray = [[NSMutableArray alloc] init];
}
return self;
}
- (void)dealloc {
// Should never be called, but just here for clarity really.
}
#end
I thought about storing it in NSUserDefaults - is this the correct route? If so, how would I do this?
Any help would be much appreciated, thanks!
I am not sure creating a separate singleton class for just passing an array to different view controllers is a great idea from code structure point of view. Much more cleaner approach would be to pass playlistSongsArray directly whenever you create a new instance of a view controller that may need it.
However, if you still want to use singleton in your implementation for some reason, I'd change playlistArray class to something like PlaylistManager (notice that the common convention is to capitalise first letters of each word in class names) — by doing this, you don't constraint yourself with array-only implementation and you can use it to share other playlist information between your view controllers.
As for persistency between app launches, it really depends on what kind of data you store in your array. For example, you can use NSUserDefaults if your array stores relatively small number of NSStrings (or other <NSCoding>-compliant classes). Other most common options are NSKeyed​Archiver and Core Data. You can learn more about data persistency on iOS from Apple documentation or great online tutorials like this one on NSHipster.
I thought about storing it in NSUserDefaults - is this the correct route?
NSUserDefaults is certainly an option you have.
You can add the following two methods into your class and call it when appropriate (i.e., savePlayList when you modify the array;loadPlaylist` in the singleton init method):
- (void)savePlaylist {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:self.playlistSongsArray forKey:#"playlistSongs"];
[defaults synchronize];
}
- (void)loadPlaylist {
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
self.playlistSongsArray = [[defaults arrayForKey:#"playlistSongs"] mutableCopy];
}
Have a look at this post for a discussion about different approaches you have.
You get singleton array blank when you relaunch the app because all memory allocated to the array is removed. These singleton classes are allocated once during the lifecycle of the app.
So the best answer to fetch the data every time you open the app is to save the data into the disk. To do so there are few ways
Database using Sqlite
plist
NSUSerDefaults
You can go for userdefaults or plist if you want to save the app.
Below is the example of saving the array into userdefault
Saving the array into userdefaults:
// Get the standardUserDefaults object, store your UITableView data array against a key, synchronize the defaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:array forKey:#"singletonArray"];
[userDefaults synchronize];
Retrieving the array:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *singletonArray = [userDefaults objectForKey:#"singletonArray"];
Hope it will help you. Happy Coding :)

Save and Load Data on Today Extensions (iOS 8)

Is it possible to save and load data on Today Extension using NSUserDefaults?
After closing the Notification Center, the widget behaves like an app which is terminated, so any data results lost. How could I solve this issue?
This is my code:
NSUserDefaults *defaults;
- (void)viewDidLoad {
[super viewDidLoad];
defaults = [NSUserDefaults standardUserDefaults];
NSArray *loadStrings = [defaults stringArrayForKey:#"savedStrings"];
if ([loadStrings objectAtIndex:0] != nil) {
[display setText:[NSString stringWithFormat:#"%#", [loadStrings objectAtIndex:0]]];
}
if ([loadStrings objectAtIndex:1] != nil) {
calculatorMemory = [NSString stringWithFormat:#"%#", [loadStrings objectAtIndex:1]].doubleValue;
}
}
- (IBAction)saveData:(id)sender {
NSString *displayString;
NSString *memoryString;
NSArray *saveStrings = [[NSArray alloc] initWithObjects: displayString, memoryString, nil];
defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:saveStrings forKey:#"savedStrings"];
[defaults synchronize];
}
You need to use app group identifier instead of com.*
For instance:
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.company.appgroup"];
Don't forget to synchronise when you store data
[shared synchronize];
You need to add the App Group stuff detailed under here and then if it actually worked (pretty iffy under beta) it should allow you to share NSUserDefault data like normal between the host and widget.
Edit: Normal NSUserDefaults does not work. Apple has implemented a new method. To use, simply redefine your NSUserDefaults instance like this:
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"com.you.app.container"];
For anyone wondering how in the world do you save and get values then look at this code.
In your regular app add this to save whatever you like in your *.m file.
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.yourcompanyname.TodayExtensionSharingDefaults"];
//save dic
[shared setObject:dictionary2 forKey:#"dicForTodayWidget"];
//save array
[shared setObject:tempArray2 forKey:#"arrayForTodayWidget"];
//save some value
[shared setObject:#"1234" forKey:#"myValForTodayWidget"];
[shared synchronize];
In your today widget under TodayViewController.m in viewDidLoad add this.
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.yourcompanyname.TodayExtensionSharingDefaults"];
//get dic
NSMutableDictionary *dictionary = [shared objectForKey:#"dicForTodayWidget"];
You first need the App Groups set up for both targets (application and the extension).
Then, use the
NSUserDefaults *shared = [[NSUserDefaults alloc]initWithSuiteName:#"group.company.myapp"];
to obtain the defaults object which you can read from/write to as usual.
If you want to be notified of changes to the defaults, use the NSUserDefaultsDidChangeNotification in your widget (or app).
For a step-by-step tutorial explaining all this, take a look at this blog post.
#edukulele
Today Extension and Main app run on two processes. Today Extension can't receive NSUserDefaultsDidChangeNotifications. I tried use MMWormhole. It is very good.

How to save data in iOS

I'm making a game and when I close the app (close at multitask manager), all my data is gone! So, My question is very simple: How do I save the data?
Let's say you want to save score and level, which are both properties of an object called dataHolder.
DataHolder can be created as a singleton, so you don't have to worry too much about from where you access it (its sharedInstance actually):
It's code would look a bit like this:
DataHolder.h
#import <Foundation/Foundation.h>
#interface DataHolder : NSObject
+ (DataHolder *)sharedInstance;
#property (assign) int level;
#property (assign) int score;
-(void) saveData;
-(void) loadData;
#end
DataHolder.m
NSString * const kLevel = #"kLevel";
NSString * const kScore = #"kScore";
#implementation DataHolder
- (id) init
{
self = [super init];
if (self)
{
_level = 0;
_score = 0;
}
return self;
}
+ (DataHolder *)sharedInstance
{
static MDataHolder *_sharedInstance = nil;
static dispatch_once_t onceSecurePredicate;
dispatch_once(&onceSecurePredicate,^
{
_sharedInstance = [[self alloc] init];
});
return _sharedInstance;
}
//in this example you are saving data to NSUserDefault's
//you could save it also to a file or to some more complex
//data structure: depends on what you need, really
-(void)saveData
{
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:self.score] forKey:kScore];
[[NSUserDefaults standardUserDefaults]
setObject:[NSNumber numberWithInt:self.level] forKey:kLevel];
[[NSUserDefaults standardUserDefaults] synchronize];
}
-(void)loadData
{
if ([[NSUserDefaults standardUserDefaults] objectForKey:kScore])
{
self.score = [(NSNumber *)[[NSUserDefaults standardUserDefaults]
objectForKey:kScore] intValue];
self.level = [(NSNumber *)[[NSUserDefaults standardUserDefaults]
objectForKey:kLevel] intValue];
}
else
{
self.level = 0;
self.score = 0;
}
}
#end
Don't forget to #import "DataHolder.h" where you need it, or simply put it in ...-Prefix.pch.
You could perform actual loading and saving in appDelegate methods:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[DataHolder sharedInstance] saveData];
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
[[DataHolder sharedInstance] loadData];
}
You can access your score and level data from anywhere with [DataHolder sharedInstance].score and [DataHolder sharedInstance].level.
This might seem like an overkill for a simple task but it sure helps to keep things tidy and it can help you to avoid keeping all the data in appDelegate (which is usually the quick & dirty path to solution).
[NSUserDefaults standardUserDefaults] is good for small amounts of data like user settings and preferences. Typically you use this to enable users to save various bits of data that define global values such as character preferences, weapons preferences, whatever, etc.
For larger amounts of data like game level details or achievements or weapons inventory, etc. You will want to use something like Core Data. This is a more formal database that can be easily migrated as your data schema changes. See the docs here:
Core Data and Core Data Programming Guide
You can save data in CoreData, SqlLite or NSUserDefaults
Update
Realm is also an option and very easy to implement.
There are few ways to save data in ios.
UserDefaults - great way to save a small amount of data.
Keychain - safe location to safe high sensible data like login data and passwords.
Sqlite Database - If your application have a huge amount of structured data
CoreData - based on an object graph that describes the objects that should be saved
Saving Files - Of course you can also directly save all types of files to the file system. However, you can just access the file system within the app container due to security reasons.
Using Realm database - best alternative to Core Data and SQLite db
NSKeyedArchiver and NSKeyedUnarchiver - better way for small amount of objects. // i think so
ref - http://www.thomashanning.com/data-persistence-ios/
Swift 3
Use UserDefaults
UserDefaults.standard.set(„some value”, „key”)
let value = UserDefaults.standard.string(„key”)
You can even persist array using this
I recommend using Realm for more generalized solutions.

Resources