I have a couple of objects stored in the user's NSUserDefaults which I have to use more or less in every single ViewController of my app.
Currently, I basically have the same 3 variables declared, and in the viewDidLoad I initialise them like:
if(....){
chosenID = [[[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_xxxx"] integerValue];
chosenName = [[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_name"];
}else{
chosenID = [[[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_xxxx_2nd option"] integerValue];
...
}
I'm looking to clean up my code and optimize my code, and I was wondering what the right way to handle a case such like this was, to avoid having these 10-12 exact same lines of code at the start of every single ViewController.
Write an utility class. And create some class methods.
One method can be like,
+ (NSString *)choosenName {
return [[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_name"];
}
And call the method like,
chosenName = [Your_Utility_Class choosenName];
Yes you can achieve it globally by following simple method.Create NSObject class,please refer my example below.
.h File
//Setting up Session
+(void)SetEmail:(NSString*)value;
+(void)SetFirstName:(NSString*)value;
//Retrieve
+(NSString*)GetEmail;
+(NSString*)GetFirstName;
.m file
+(void)SetEmail:(NSString *)value{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"EMAILID"];
}
+(void)SetFirstName:(NSString *)value{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"FIRSTNAME"];
}
+(NSString*)GetEmail{
return [[NSUserDefaults standardUserDefaults] stringForKey:#"EMAILID"];
}
+(NSString*)GetFirstName{
return [[NSUserDefaults standardUserDefaults] stringForKey:#"FIRSTNAME"];
}
Now Move to Viewcontroller and access without alloc init as it is in Class method.I am setting up from result.
Setting up in Viewcontroller
[NSDefaultSession SetEmail:#"YourString"];
[NSDefaultSession SetFirstName:#"YourString"];
Now Getting Session from any ViewController
[NSDefaultSession GetEmail]
[NSDefaultSession GetFirstName]
Firstly you should put these values into an object, and secondly use dependency injection.
So first make a class Chosen (for want of a better name) and give it the properties id and name. Now the only thing that needs to worry about where the data is saved and loaded from is the 'Chosen' object, everything else will go through that.
Ok now the dependency injection. You want your VC dependencies to be obvious and clear, don't rely in singletons like NSUserDefaults hidden away inside them. So make .chosen a public property on each of the VC's that needs access to the object.
Init this object in application:application didFinishLaunchingWithOptions: and now inject that into your initial viewController. (ie, set the public property)
Now just pass along the object again by injection to each of the other viewController that needs access to it.
in my option:
create commonClass (sub class of NSObject class)
crate spare methods and use them where u need.
ex:
in ur vc1:
set the values for ur objects
chosenID = [[[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_xxxx"] integerValue];
chosenName = [[NSUserDefaults standardUserDefaults] valueForKey:#"chosen_name"];
in ur common class:
+(NSString *) chosenID{
NSUserDefaults *serverDefults=[NSUserDefaults standardUserDefaults];
NSString * chosenID =[serverDefults objectForKey:#"chosen_xxxx"];
if (chosenID.length==0) {
// do some actions
}
return chosenID;
}
in another VC2:
NSString *id =[commonClass chosenID];
This is the right way to address this issue. Common functionality is what sub-classing is all about - your view controllers are a specific type of view controller that needs access to these variables.
Create class BaseViewController, which is a sub-class of a UIViewController.
Give BaseViewController two public properties called chosenId and chosenName.
Add the init code you have to the viewDidLoad of BaseViewController.
Remove the init code from each of your existing view controllers.
Make any view controller that requires these variables of type BaseViewController intead of UIViewController.
Those variables are now magically (and consistently) available in all of your view controllers without any code duplication.
Related
In my case there are two ViewControllers. In my first view controller there is a label and when view load, it's text should display as 1. then there is a button, when it click,navigate to second view controller. In the second view , there is a stepper and label. If user tap + ,second view's label text change from 1 to 9,for the - also same(decrease the value). in the second view also there is a button.when it click, second view dismiss (from the first view to second I used presend Modally kind segue with over Current Context presentation.that means when dismiss this secondview, firstview does not load again,it exists in the background). so what I want is to send the second view's label text (after changed by the stepper), as first view's text and update the first view's label.(think if the second view's label text is 3, first view's label text should update from 1 to 3 ). I tried with NSUserdefaults.this is my code.
this is my second view controller
- (void)viewDidLoad {
[super viewDidLoad];
//set default value for adult label
NSUInteger defaultAdultVal = self.adultstepper.value;
self.adultcountLabel.text = [NSString stringWithFormat:#"%d", defaultAdultVal];
}
- (IBAction)adultcountAction:(UIStepper *)sender {
NSUInteger adultVal = sender.value;
self.adultcountLabel.text = [NSString stringWithFormat:#"%d", adultVal];
NSString *adultCount = [NSString stringWithFormat:#"%d", adultVal];
[[NSUserDefaults standardUserDefaults] setObject:adultCount forKey:#"adultcount"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (IBAction)DoneAction:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
and this is my second view controller
- (NSString *)testingAsign
{
NSString *adltCount = [[NSUserDefaults standardUserDefaults] objectForKey:#"adultcount"];
return adltCount;
}
I'm getting the value with this method in the first view, and I want to update first view's value but it didn't work.
There are many ways to this are
By using protocol-delegate - Perfect way
proper and perfect way it to create protocol for it in secondVC, and add one weak property as delegate, and while presenting secondVC.. assign firstVC as delegate of secondVC. Also, Implement that protocol in firstVC. Now when you are dismissing secondVC, call the method in protocol. And implemented method in firstVC get called.. so you get the value there.
By using NSNotification
You can add observer for notification in firstVC and postNotification from secondVC. But this is not proper way.. as firstVC continuously observes for notification. (Don't forget to remove observer.. once you dont require observation)
By using Global variable
You can add one global variable in appDelegate, and assign its value from secondVC. And access that value from firstVC. This is also not proper way. Because that variable always remain in memory.
Trying adding value from userdefault in view will appear in first view controller when second view controller dismiss after setting value in NSUserDefaults.
- (void) viewDidLoad:(BOOL)animated{
[super viewDidLoad:animated];
[[NSUserDefaults standardUserDefaults] #"1" forKey:#"adultcount"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
- (void) viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
myStaticLabel.text = testingAsign;
}
- (NSString *)testingAsign
{
NSString *adltCount = [[NSUserDefaults standardUserDefaults] objectForKey:#"adultcount"];
return adltCount;
}
this can be done with so many ways like using delegate methods coredata , nsnotifications and nsuserdefaults. from all these , one of the easiest way to pass data backward, we can easily use NSUserDefaults. this is a sample project of passing data backwark using nsuserdefaults. use this github project and give it a try. project url : pass data backward using NSUserDefaults in ios, objective C
You don't need NSUSerDefaults for this. If all you want to do is to be able to transfer data on segue, you need to use the prepareForSegue function. Check out https://stackoverflow.com/a/7865100/2465172
In which method are you updating in FirstViewController, ViewDidLoad or ViewWillAppear Method? Do [[NSUserDefaults standardUserDefaults] #"1" forKey:#"adultcount"];[[NSUserDefaults standardUserDefaults] synchronize]; in SecondViewController and when you dismiss SecondViewController,get updated value from NSUserDefaults in ViewWillAppear Method of FirstViewController.Hope it will work.
I have 2 views, a login view and a main view.
I use SWRevealViewController, and I want automatically display my menu at the startup of the app. I know how display my menu but I don't know how display it just once at startup.
I want to pass a simple String between my Login view and my Main view but without segue, and made a simple test :
if (myPreviousView == "LoginView")
{
// display my menu
}
Another method would be to use NSUserDefault to store your string, which than can be accessed from anywhere within the application.
So, you put your string into NSUserDefaults in your first view:
// Initialize the NSUserDefaults object and an array for data storage
NSUserDefaults *defsData = [NSUserDefaults standardUserDefaults];
NSMutableArray *myArray = [[NSMutableArray alloc] init];
// Add your string to the custom array
NSString *myString = #"My string.";
[myArray addObject:myString];
// Put the array back into UserDefaults for later use
[defsData setObject:myArray forKey:#"Key"]; // Key can be anything
[defsData synchronize];
Now, the array (and the string in it) is available anywhere. So, when you navigate to your second view controller, just initialize an NSUserDefaults and access the string:
NSUserDefaults* defsData = [NSUserDefaults standardUserDefaults];
NSArray *myArray = [defsData objectForKey:#"Key"];
NSLog("This is my stored string: %#", [myArray objectAtIndex:0]);
You can modify the init method of your second view controller to take a custom attribute when you subclass it. So, lets say you created a standard UIViewController (.h and .m files). You can modify the init method of this new class to your liking in the .h file:
- (instancetype)initWithString:(NSString *)string;
And then replace the standard init with the new one in the .m:
- (instancetype)initWithString:(NSString *)string {
}
So, when you call your view controller into existence, you just use this new init method and pass the string you wanted like this:
UIViewController *viewController = [[UIViewController alloc] initWithString:myString];
[self presentViewController:viewController animated:NO completion:nil];
This is a programmatical approach of course, but it should be applied to interface builder easily (unfortunately, as I never use interface builder, I don't know how exactly, but as I said, it should be fairly straightforward to anyone who uses it).
I want to initialize my singleton object which stores and manages the application settings over the entire class within my app. Also, the singleton instance should be initialized by loading the data from NSUserDefaults upon launch. However, I'm not fully sure where I should initialize the singleton upon launch.
In Cocoa app, I first wrote the singleton initialization code within applicationWillFinishLaunching:, taking parameters from NSUserDefaults. However, later I found that this doesn't work properly if I also write the singleton initialization code (taking no parameter!) within my initial view controller, set in storyboard, because the viewWillLoad:, viewDidLoad: etc. of the class of the view controller are called before the applicationWillFinishLaunching:.
So now I'm sure I should write the singleton initalization code within viewWillLoad: earlier than applicationWillFinishLaunching, but still not sure whether it is appropriate. Specifically, I know the NSApplicationMain is the first method to be called upon launch, but it seems that the next method is not anything within AppDelegate, at least if you use storyboard.
To summary, what I want to ask are the following:
What method from what class will be called after NSApplicationMain, if you use storyboard.
Where should I write my singleton initialization code within my app? I want to initialize it as soon as possible.
Does it differ between iOS and OS X app?
You should initialize it when it's first accessed. Something like this, maybe:
+ (instancetype)sharedInstance {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [[self alloc] init];
});
return _instance;
}
As a side note, if you're literally only using this class as an accessor to NSUserDefaults, you might want to consider using static methods instead.
+ (id)mySpecificDataPoint {
return [[NSUserDefaults standardUserDefaults] objectForKey:#"whatever"];
}
+ (void)setMySpecificDataPoint:(id)data {
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"whatever"];
}
Or maybe a more well-designed way might be to add a category to NSUserDefaults for this purpose.
#interface NSUserDefaults (MyData)
#property (nonatomic) NSString *someDataPoint;
#property (nonatomic) NSInteger somePrimitiveDataPoint;
#end
#implementation NSUserDefaults (MyData)
- (NSString *)someDataPoint {
return [self objectForKey:#"someDataPoint"];
}
- (void)setSomeDataPoint:(NSString *)someDataPoint {
[self setObject:someDataPoint forKey:#"someDataPoint"];
}
- (NSInteger)somePrimitiveDataPoint {
return [[self objectForKey:#"somePrimitiveDataPoint"] integerValue];
}
- (void)setSomePrimitiveDataPoint:(NSInteger)somePrimitiveDataPoint {
[self setObject:#(somePrimitiveDataPoint) forKey:#"somePrimitiveDataPoint"];
}
#end
You init the singleton when you have to use it. So as Daji Djan said: lazy wins. Just take attention that, you should not do a long-run process in your applicationWillFinishLaunching, it should return as soon as possible.
If the singleton is not mandatory during applicationWillFinishLaunching, you should call it in viewWillAppear of first view controller if you need to initialize it ASAP.
lazy always wins
if you can get away with it: as late as possible :) AND always do the minimum needed (but do as much as is reasonable to keep your code clean!)
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 8 years ago.
Improve this question
How to get sessions variables in iOS. For example I had five view controllers starting from login page. When logged in as a user I was able to get user id from my JSON data. I want this user id to be used in all the screens to get it done in a smoother way. What I did is taking a hidden variable and sending it to the next screen which is a tough thing when we have 100 screens. Is there any other way to set the session variables and access those all over the application?
What I had tried is keeping that NSString in Appdelegate but I was not able to uderstand how to do that.
You don't need a fancy class/singleton/NSUserdefaults to do this: Heres Why.
Session variables need to be anything.
The class itself does not need to maintain state
NSUserDefaults are not a session, they are persisted between app launches.
All you really need is a static class which has a NSMutableDictionary in it.
// Session.h
#interface Session : NSObject
+ (NSMutableDictionary *) sessionVariables;
#end
// Session.m
#import "Session.h"
static NSMutableDictionary *_session;
#interface Session
+ (NSMutableDictionary *) sessionVariables {
if (!_session) {
_session = [NSMutableDictionary alloc] init];
}
return _session;
}
So in every class you want session available all you need to do is import Session.h
and use the class like this [Session sessionVariables] setObject:#"something" forKey:#"someKey"]
and to get things out id someValue =[Session sessionVariables][#"something"];`
The one thing I do recommend is please DO NOT store user/password/sensitive data in any of the answers provided (Including mine), use the iOS Keychain for that.
Save your value in NSUserDefaults and also fetch it from that
for save:
[[NSUserDefaults standardUserDefaults] setObject:STRING_OBJECT_OF_YOUR_JSON_LOGIN_ID forKey:#"UserLoginIdSession"];
[[NSUserDefaults standardUserDefaults] synchronize];
for retrive:
NSString *str = [[NSUserDefaults standardUserDefaults] valueForKey:#"UserLoginIdSession"];
and when user log out from your app set this to #""
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"UserLoginIdSession"];
[[NSUserDefaults standardUserDefaults] synchronize];
You have lot of choices.
Save to NSUserDefaults
[[NSUserDefaults standardUserDefaults]setObject:#"12345" forKey:#"SessionKey"]; // Save session id
[[NSUserDefaults standardUserDefaults]objectForKey:#"SessionKey"];//Access in other view controllers
Keep it AppDelegate as a property, you can access AppDelegate in all the view controllers
Create a shared singleton class to share the data across multiple view controllers.
If you want to keep that information in AppDelegate you only have to create properties in that class (AppDelegate) declared in AppDelegate.h. You can access the appDelegate and its properties from any other part of the app:
sessionData = [(MyAppDelegate*)[[UIApplication sharedApplication] delegate] sessionProperty];
I use to define a macro to avoid that verbosity. In AppDelegate.h :
#define myAppDelegate ( (MyAppDelegate*)[[UIApplication sharedApplication] delegate] )
So I can access its methods from anywhere in the app:
sessionData = myAppDelegate.sessionProperty ;
Nevertheless, keeping session data in app delegate is not always a good practice. I much prefer to use a singleton to keep session info. For example:
sessionData = [[SessionManager sharedManager] sessionProperty] ;
To create a singleton, you only have to create a new class and redefine alloc and copy. And, obviously, define the method sharedManager.
First of all: Obviously you are parametrizing every view controller with the userID. Nothing is wrong with doing so and it keeps information encapsulated in a good way. Yes, this can be additional work. Yes, encapsulating data in a good way is additional work.
However, if you want to change it, it is one way to have a property of the application delegate. Keep in mind: This creates a dependency of every class that uses the userID to the application delegate. This reduces the ability of code reuse. Therefore it is a good idea to have an additional class Session. Create one at the login process and store it in the application delegate.
The whole process:
A. Create a class Session
#interface Session : NSObject
#property (readonly, copy) NSString *userID;
#end
#interface Session()
#property (readwrite,copy) NSString *userID;
#end
#implementation Session
- (id)initWithUserID:(NSString*)userID
{
self = [super init];
if (self)
{
self.userID = userID;
}
return self;
}
#end
B. Create an instance and store it in the application delegate
…
// login
NSString *userID = …;
self.session = [[Session alloc] initWithUserID:userID];
…
C. Using the user ID
To use that you have to import the headers of your application delegate class and the session class:
//
#import "AppDelegate.h" // To get the session instance
#import "Session.h" // To get the user ID
…
Session *currentSession = [UIApplication sharedApplication].delegate.session;
NSString* userID = currentSession.userID;
…
You can get rid of the dependency to AppDelegate, if you implement a shared instance of Session. Doing so the code be a little bit more straight forward:
//
#import "Session.h" // To get the user ID
…
Session *currentSession = [Session currentSession];
NSString* userID = currentSession.userID;
…
How to create such a shared instance (called singleton, even it is not) is explained on SO in many, many answers.
So one of the users in here managed to show me how to pass data from a child view controller to a parent view controller via a string.
So now the string is passed, BUT, i want that value to stay displayed on the firstViewController after the app is closed and re-opened.
The value is saved in with NSUserDefaults by the way and with an NSLog i am seeing on the conosole it is saved in the apps folder but that value isnt saved onto the UILabel display.
It only displays it when i put save but then i close and reopen, it dissappears but in an NsLog it is still inside the app but not on display UILabel.
How can i address this ?
On my appDelegate.h i have a
#property (strong, nonatomic) NSString *sharedString;
To pass the secondViewController data to the firstViewController.
In the save method on my secondViewController i have a function related to the
AppDelegate.h declaration which is:
AppDelegate *apiDelegate = [[UIApplication sharedApplication] delegate]
apiDelegate.sharedString = self.textFieldData.text;
And in my firstViewController i have a method which display the data from the second
viewController:
-(void) viewDidAppear:(BOOL)animated {
AppDelegate *apiDelegate = [[UIApplication sharedApplication] delegate]
self.DisplayData.text = appDelegate.sharedString;
[super viewDidAppear: NO];
Is there something wrong which isnt keeping the data intact after app closes or am
I missing something here ?
So one of the users in here managed to show me how to pass data from a
child view controller to a parent view controller via a string.
First you need to establish some hierarchy as to how you get a childViewController from a parentViewController. One way to pass data from childViewController to parentViewController is using a delegate. The other could be using the KVC/KVO protocol. https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/KeyValueObserving/KeyValueObserving.html
In this you can simply register an observer for the property defined in the childViewController and observe it's changes wherever you want (well, given the hierarchy is satisfied).
To save the value. You can simply save it using NSUserDefaults. I don't see any code in your post but you can simply define a key and save the value with NSUserDefaults using:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults setObject:sharedString forKey:#"sharedString"];
NSString *sharedStringFromDefaults = [defaults objectForKey:#"sharedString"];
Also,
AppDelegate *apiDelegate = [[UIApplication sharedApplication]
delegate]
Apple requires you to avoid such references in the application. It only constrains the app. Further, the sharedString is not required to be in the AppDelegate. Otherwise the AppDelegate will be filled with almost every other data structure you have shared in the app.
//add this code when you want to store string
[[NSUserDefaults standardUserDefaults] setObject:self.textFieldData.text forKey:#"sharedString"];
[[NSUserDefaults standardUserDefaults] synchronize];
//and when you want string than
self.DisplayData.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"sharedString"];