NSKeyedArchiver saving iOS - ios

I'm trying to save for the first time. Made my application, and now going to save data and load on startup and close. Looked into NSKeyedArchiver and added the two methods from the NSCoding to all my custom classes:
-(void)encodeWithCoder:(NSCoder *)aCoder{
// Encode Stuff
}
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init])
// Decode Stuff
return self;
}
But now i'm finding a problem, where and how do i need to call my saveFile and loadFile? My data is stored in 2 arrays in my FirstViewController. And i wanted to save the data with the AppDelegate's - (void)applicationWillTerminate:(UIApplication *)application and load data with - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions But how is this possible to reach the FirstViewController class with the Arrays in it to store from the AppDelegate method's?
Is the thinking right, or do i need to do it on an other way?
Kind Regards

Loading data should be "lazy". This means the data should be loaded the first instant that you actually need to read the data. Also, if it's a lot of data, you should be prepared to free it while your app is running so other apps can use the RAM, this means your app is more likely to still be running next time the user launches your app.
So, make a class that provides access to the data, and the first time anything needs data it checks if the internal NSCoding object is nil, and if it is then that is where you should load the data.
As for saving, you should save before terminating but more importantly you should also save within a second or so of any data being modified by the user. Your app should crash at any moment due to a software bug, or it could be terminated for some other reason, or the battery could simply run out.
Lets say your internal data storage is an NSMutableDictionary saved using NSKeyedArchiver. It has a value with the key #"value", with a "getter" and "setter" implemented like this:
- (NSString *)value
{
if (!self.data)
self.data = [NSKeyedUnarchiver unarchiveObjectWithFile:self.dataFile];
return self.data[#"value"];
}
- (void)setValue:(NSString *)value
{
if (!self.data)
self.data = [NSKeyedUnarchiver unarchiveObjectWithFile:self.dataFile];
self.data[#"value"] = value;
self.needsSave = YES;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self save];
});
}
- (void)save
{
if (!self.needsSave)
return;
[NSKeyedArchiver archiveRootObject:self.data toFile:self.dataFile];
self.needsSave = NO;
}
Finally, your class should also register for UIApplicationDidReceiveMemoryWarningNotification, UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, where you want to save to disk and free the RAM so other apps can use it:
- (id)init
{
if (!(self = [super init]))
return nil;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAndFreeMemory:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAndFreeMemory:) name:UIApplicationWillResignActiveNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(saveAndFreeMemory:) name:UIApplicationWillTerminateNotification object:nil];
return self;
}
- (void)saveAndFreeMemory:(NSNotification *)notif
{
[self save];
self.data = nil;
}

When a UIApplication launches in the normal way, you can always call [[UIApplication sharedApplication] delegate] from any object in the app, and you will thus obtain a reference to the application's delegate.
This is how view controller code generally gets ahold of the app delegate, you establish the connection in the view controllers in their respective -awakeFromNib methods -- it's generally less gotcha-prone to have the view controllers make the first contact with the app delegate, than having the app delegate reach out to the view controllers.
Have your app delegate decode the saved data into a model object hierarchy, have your view controllers connect with the app delegate and begin KV observing the model in -awakeFromNib.
Or just use Core Data.

Related

How to use a function from the view controller in the app delegate?

I have an app that is connected to a device by Bluetooth.
I want the app to send a command that indicates that the app is going to close in the app delegate method : (void)applicationWillTerminate:(UIApplication *)application {
One word: NSNotificationCenter
I'm not sure what you need to set the data to because you can't pass the data seamlessly via NSNotificationCenter; however you were going to figure that out in your UIApplicationDelegate anyway, so why can't you do it in the view controller directly.
In your case there is no need to do anything in your application delegate, because this notification allows your view controller to act as a mini app delegate (in that you can get termination status and so on).
Therefore...
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(TXdata:) name:UIApplicationWillTerminateNotification object:nil];
}
- (void)TXdata:(NSString *) data {
NSString *newData = data;
if (newData == nil) {
newData = ... // Figure out what your data should be here.
}
//do whatever with your data here.
}
I quote:
UIApplicationWillTerminateNotification
Posted when the app is about to terminate.
This notification is associated with the delegate applicationWillTerminate: method. This notification does not contain a userInfo dictionary.
You should create a class separate from the view controller and app delegate to handle the BLE communication. That way, the view controller and the app delegate can both have access and provide a better "separation of concerns" for your app. This new class might work well as a singleton.

Obj-C app doesn't load from start

I bult an iOS app and in -(void)viewDidLoad I parse data from web, and display it on load. But often when I open my app it displays old data(app loads fast) and I need to kill it and open it again, after that it parses data and shows new. Why is that happening?
It doesn't work that way because viewDidLoad is only called once, when the view is created. After backgrounding and returning, your view still exists.
If you want to reload your data whenever the app returns from the background, you need to either override applicationDidBecomeActive: in your UIApplicationDelegate implementation, or you need to listen for the appropriate notification:
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidBecomeActive:)
name:UIApplicationDidBecomeActiveNotification
object:nil];
}
- (void)applicationDidBecomeActive:(NSNotification *)notification
{
// reload your data here.
}
Don't forget to remove yourself as an observer when you no longer need the notification.

Access View Controller's properties from AppDelegate

I currently have an Xcode project in which I am using Storyboards. In my AppDelegate, I need to set some properties that are contained in the .h files of other View Controllers in response to notifications that the app receives.
How do I instantiate a object of those view controllers in the AppDelegate so that I can access and modify their properties?
There are ways for the app delegate to get a handle to the right vc and communicate with it, but the better design is to have the information flow the other way around, letting the view controllers ask for information and update their own properties.
To do this, when the app delegate receives a notification, have it post a corresponding NSNotification (via NSNotificationCenter). The view controllers who care about the change can add themselves as observers for this notification and get the information. How can they get it? A few ways:
The textbook way is to have a model on the application, probably a singleton that has properties relevant to the view controllers. Idea two is to effectively make your app delegate a model by giving it properties that the vcs can interrogate. Last idea, the userInfo param on postNotificationName:(NSString *)notificationName object:(id)notificationSender userInfo:(NSDictionary *)userInfo can convey information to the observer.
EDIT - NSNotificationCenter is pretty easy to use. It goes like this:
In AppDelegate.m, when you get an external notification:
// say you want a view controller to change a label text and its
// view's background color
NSDictionary *info = #{ #"text": #"hello", #"color": [UIColor redColor] };
[[NSNotificationCenter defaultCenter] postNotificationName:#"HiEverybody" object:self userInfo:info];
In SomeViewController.m, subscribe to the message:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(observedHi:)
name:#"HiEverybody"
object:nil];
}
// unsubscribe when we go away
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
// this method gets run when the notification is posted
// the notification's userInfo property contains the data that the app delegate provided
- (void)observedHi:(NSNotification *)notification {
NSDictionary *userInfo = notification.userInfo;
self.myLabel.text = userInfo[#"text"];
self.view.backgroundColor = userInfo[#"color"];
}

Tracking user usage time and time on UIViewController like Google Analytics

What is the best way to track App usage time and time a user spends viewing a Screen (or interacting with a UIView) for use within the App itself? Google Analytics seems to do a wonderful job, but the numbers I want to use inside the App itself to unlock items and areas of the App.
You could probably roll your own solution based on Core Data, or if your data is small you could even think of using NSDefaults.
Here's a good start. It involves having a base view controller which you should inherit from in each view controller you want to measure the time spent:
#interface BaseViewController : UIViewController
- (NSString *)screenKey;
+ (NSInteger)secondsInScreen:(NSString *)screenKey;
#end
The implementation simply measures the seconds between the appearance of the screen until it disappears. It's very important to notice the appDidEnterForeground and appDidEnterBackground notifications. When you send your app to the background or it comes back to the foreground, viewDidAppear and viewDidDisappear are not called.
#import "BaseViewController.h"
#implementation BaseViewController {
NSDate *_startDate;
}
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(appDidEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self startMeasuring];
}
- (void)viewDidDisappear:(BOOL)animated {
[super viewDidDisappear:animated];
[self stopMeasuring];
}
- (void)appDidEnterBackground:(NSNotification *)not {
[self stopMeasuring];
}
- (void)appDidEnterForeground:(NSNotification *)not {
[self startMeasuring];
}
- (void)startMeasuring {
_startDate = [NSDate date];
}
- (void)stopMeasuring {
NSInteger secondsInScreen = ABS([_startDate timeIntervalSinceNow]);
[self addSecondsToScreen:secondsInScreen];
}
- (NSString *)screenKey {
// Subclasses must override this method
return #"";
}
- (void)addSecondsToScreen:(NSInteger)seconds {
NSString *key = [self screenKey];
if (key.length > 0) {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:key];
NSInteger addedSeconds = s.integerValue + seconds;
[defaults setObject:[NSNumber numberWithInteger:addedSeconds] forKey:[self screenKey]];
[defaults synchronize];
}
}
+ (NSInteger)secondsInScreen:(NSString *)screenKey {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSNumber *s = [defaults objectForKey:screenKey];
return s.integerValue;
}
#end
Your subclasses must override screenKey retuning a unique identifier for each screen you want to measure. There's a static method in BaseViewController that allows you to get the sum of seconds spent in each screen.
This is a simple way of doing it. From a design point of view, it would be better to store this information in Core Data and to separate the view controller logic from the storage of that data (the static method secondsInScreen should probably be in another class). But it's a good, working start.
I think you would like to get app usage time to enable new features in the app or give some gifts for your users right?
To reach this, don't use any SDK to track audience like Google Analytics and Flurry for example. Both are for a different purpose you want to do.
A very very simple approach is to store locally using NSUserDefaults the session time of some user, or you can store more detailed information about this using CoreData or SQLite.
The iOS provide a lot of options to you do that, for example, each time the user start the session or open some screen, you can store some NSDate or mach_abosulte_time to save the time of user started the session/screen and you can get the offset time when the app goes to background or when the user closes the screen.
And if you want to store this remotely (server), you can create a service to send this time while the app is visible.
I hope this first insight can help you.

iOS: Refresh Data When App is Brought to Foreground

I'm getting data off the server via JSON and displaying it on Labels.
I've added that method in viewDidLoad.
I want to refresh the data when the user opens the app again. Currently, even if I kill the app in the simulator and start the app again, it doesn't refresh.
I tried the viewDidAppear method, but it isn't being executed for some reason.
-(void)viewDidAppear{
NSLog(#"Called viewDidAppear");
}
This is never called. I tried to minimize the app but it didn't work.
You can listen for notifications and respond appropriately. Try using these and decide what works for your intended workflow.
UIApplicationDidBecomeActiveNotification
UIApplicationWillEnterForegroundNotification
You can use respond to the notification like this.
[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidBecomeActiveNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// LOAD JSON
}];
I followed this tutorial - http://leejon.es/notifying-a-viewcontroller-with-uiapplicationdidbecomeactivenotification/
First, attach to the notification in the viewWillAppear method of the target view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( appActivated: )
name: UIApplicationDidBecomeActiveNotification
object: nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self ];
}
- (void)appActivated:(NSNotification *)note
{
[self update];
}
The viewDidAppear: method takes a bool parameter wether the view was displayed with an animation which you are missing. Also you have to call the implementation of the superclass:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
NSLog(#"Called viewDidAppear");
}
In your app delegate implementation, there is a method called:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
This method is called each time the app is launched, so I think it fits your needs. If you place your code here, it should work.
Also, be aware you should not perform a synchronous call here, because you will delay the app launch.
EDIT:
This method will be only called when the app launches. You could place your code inside a method, and call it from application didFinishLaunchingWithOptions, and then also call it from the method:
- (void)applicationWillEnterForeground:(UIApplication *)application;
This method will be called when the application enters the foreground, but not after the first launch, so beware.
I also think you should check the UIApplicationDelegate methods from apple developer page: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIApplicationDelegate_Protocol/Reference/Reference.html
Also, check out the application state changes:
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html

Resources