I'm learning Objective C & Xcode, by doing my first App.
First a user has to sign in. And then he can do several things, like joining a group or changing his data (username, email,...).
The Login is finished and it works fine.
To the question:
Is it possible to set a variable which I can reach from every View Controller?
I tried it with the segues, but I think it's very hard to define this in every View Controller.
I'm searching for a global variable which I can reach from everywhere in the App, is this possible?
Or is there an other method to solve this?
Thank you for the help!
Emanuel
I would personally advise you to use the Singleton pattern.
I would not recommend putting everything into AppDelegate, this class is not meant for that.
Instead, what you can do is create a dedicated class (with a name like "ApplicationState", or whatever suits you), and put the properties you need in its header file, and having your singleton management code in the .m (and the prototype in the header)
If you need the singleton management code, here it is:
+ (ApplicationState*)sharedInstance
{
static ApplicationState* sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Then, if you have in the header:
#property (nonatomic, strong) NSObject* object;
+ (ApplicationState*)sharedInstance;
You will be able to get this variable from anywhere by including the ApplicationState header file, and call:
[[ApplicationState sharedInstance] object];
You can use NSUserdefault if its simple .
Save:
[[NSUserDefaults standardUserDefaults]setObject:username forKey:#"userName"];
[[NSUserDefaults standardUserDefaults] synchronize];
NSLog(#"username saved = %#", username);
Read:
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:#"userName"];
NSLog(#"username retrieved = %#", username);
You can use AppDelegate as well.
Define a property in your AppDelegate.h,
#property (nonatomic, strong) NSString *userName;
And then in your view controller, after importing AppDelegate.h,
AppDelegate *appDel = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSLog(#"%#", appDel.userName);
You can put them in plist or you can create Objective-C class initialize your variables within and import header in .pch file, then this class with data will available in every ViewController
Related
In my app, when the user launched the app, I create an instance of a class in my AppDelegate and call a method in the class which compares all of the user's iOS contacts to find which ones are using my app, and puts those contacts into an NSMutableArray
AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
...
GetContactClass *contact = [[GetContactClass alloc] init];
[contact getAllContacts];
...
}
GetContactClass.h
#property (retain, nonatomic) NSMutableArray *appContacts;
At the end of the getAllContacts method, I NSLog out appContacts and it works fine.
However, later in the app I try to set an NSMutableArray in a ViewController to equal appContacts, but I get a (null) array.
ViewController.m
self.searchableContacts = [[NSMutableArray alloc] init];
GetContactClass *contact = [[GetContactClass alloc] init];
self.searchableContacts = contact.appContacts;
What am I doing wrong here?
You're creating an entirely new instance which hasn't been asked to collect all contacts, so it hasn't stored them. By the look of the code the instance which has stored them has been destroyed. As you're running this on the main thread you might as well just ask the new instance to get contacts and delete the code for the old one. It's better however to run the contacts collection on a background thread and keep the result till you need it, in a retained instance variable.
If you want use this data in all app you must use a Pattern Design Singleton:
In objective-c:
#implementation Settings
+ (id)sharedInstance {
static Settings *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
Other way is change retain to strong but this is a bad idea and put this property in appDelegate and call all times APPDelegate get property.
Ok, strange thing occurred and I guess answer is quite simple, but I fail to figure out what's going on.
Situation is next:
I have an NSObject class called Constants.
Constants.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <GooglePlus/GooglePlus.h>
#interface Constants : NSObject
+(Constants*)shared;
#property GTLPlusPerson* googlePlusUser;
#property int profileType;
#property NSString *userName, *userLastName, *userEmail, *userGoogleId,*userProfilePicture;
#end
Constants.m
#import "Constants.h"
#implementation Constants
#synthesize profileType, userProfilePicture, userLastName,userName,userGoogleId,userEmail;
static Constants *constants = nil;
+ (Constants*)shared {
if (nil == constants) {
constants = [[Constants alloc] init];
}
return constants;
}
I use this class in order to save some static variables that I will use throughout the app.
Now, If I try and declare one of the variables like
[Constants shared].userName = #"name";
from an NSObject class method (Which I call from a ViewController), I fail to do so.
But If I declare Constant variables directly from ViewController (after viewDidLoad for example) everything works fine.
Here is the Class I try to declare variables from, but I fail (It also has singleton in it, that might be the source of the problem, but im not sure why would it)
#implementation GoogleLogin
static GoogleLogin* gLogin = nil;
+(GoogleLogin*)shared
{
if (nil == gLogin){
gLogin = [[[self class]alloc]init];
}
return gLogin;
}
-(void)getProfile
{
GTLServicePlus* plusService = [[GTLServicePlus alloc] init];
plusService.retryEnabled = YES;
[plusService setAuthorizer:[GPPSignIn sharedInstance].authentication];
GTLQueryPlus *query = [GTLQueryPlus queryForPeopleGetWithUserId:#"me"];
plusService.apiVersion=#"v1";
[plusService executeQuery:query
completionHandler:^(GTLServiceTicket *ticket,
GTLPlusPerson *person,
NSError *error) {
if (error){
NSLog(#"Error while fetching user profile: %#", error);
}else{
NSLog(#"User profile information fetched OK");
[Constants shared].googlePlusUser = person;
[Constants shared].profileType = 1;
[Constants shared].userName = person.name.givenName;
[Constants shared].userLastName = person.name.familyName;
[Constants shared].userEmail = [GPPSignIn sharedInstance].authentication.userEmail;
[Constants shared].userGoogleId = person.identifier;
[Constants shared].userProfilePicture = person.image.url;
NSLog(#"%# %# %# %# %# ",person.name.givenName,person.name.familyName,[GPPSignIn sharedInstance].authentication.userEmail,person.identifier,person.image.url);
}
}];
}
and this is how I call those methods, from my ViewController:
- (IBAction)signupWithGoogle:(UIButton *)sender {
//if i call this method here, on button click, it will finish all the steps needed, except setting constant variables
[[GoogleLogin shared] googleLoginFromViewController:self];
//if I uncomment next line, username will be declared and I will be able to access it later
//[Constants shared].userName = #"Petar";
}
Can anybody figure out why is this happening and what should I do to change that?
When you define a property is strongly suggested to declare the attributes to use with it. I guess the compiler should complain about this with a message like
No 'assign', 'retain', or 'copy' attribute is specified - 'assign' is
assumed
So, use the following instead (copy semantics is fine for mutable classes).
#property (nonatomic, copy) NSString *myString;
You should also specify if the property should be accessed in a atomic or nonatomic way. If you don't specify it, the former will be applied.
Then, you are using a singleton pattern. The suggested way is to use GCD like so.
+ (ConstantsManager*)sharedManager {
static ConstantsManager *sharedManager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedManager = [[[self class] alloc] init];
});
return sharedManager;
}
Well you did not set your property attributes on the singleton class.
For example,
#property (nonatomic, strong, readonly) ...
Have you tried moving the property assignments out of the completionHandler? It may be that your properties are being assigned on a background thread and your view controller is not catching the assignment. An easy way to check is to override the setters and getters and put breakpoints in them to see what order they are being accessed.
1) Remove the #synthesize because it's not needed (properties will be synthesized as _property automatically)
2) Override setter & getter
-(void)setProfileType:(NSInteger)profileType {
_profileType = profileType;
}
-(NSInteger)profileType {
return _profileType;
}
3) Place breakpoints within these methods and see if the getter is being called before the setter. Alternatively, if simply moving the assignments out of the completionHandler fixes it you know you have some concurrency issues.
I suggest reading up on atomic/nonatomic properties, #synthesize and Objective-C concurrency.
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 :)
hi i am new to ios dev ,I am trying to set a value for nsstring from delegate class and access from other class ,the value i get is null.i dont know what mistake i am doing?
//token class header file
#interface TokenClass : NSObject
{
NSString *tokenValue;
}
#property (nonatomic,strong) NSString *tokenValue;
//token class main file
#implementation TokenClass
#synthesize tokenValue;
#end
//App Delegate
TokenClass *token = [[TokenClass alloc]init];
[token setTokenValue:#"as"];
when i access tokenvalue in some other classs i get null value.
can any one point me what mistake i am doing?Am i using # property correctly?
There are a lot of ways to achieve what you want:
1. Usually I am using NSUserDefaults to save small amount of data which I will need even the user closed the app. There are a lot of information how to use it. See my answer here.
2. In your UIViewController class (e.x. your rootViewController) create #property which will hold your TokenClass. Then you will get tokenValue by self.tokenClass.tokenValue
3. The other way is create a singleton class which will be available during the whole run loop of your application. A Singleton candidate must satisfy three requirements:
controls concurrent access to a shared resource.
access to the resource will be requested from multiple, disparate
parts of the system.
there can be only one object.
+(TokenClass*) sharedTokenClass {
static dispatch_once_t pred;
static TokenClass *_sharedTokenClass = nil;
dispatch_once(&pred, ^{
_sharedTokenClass = [[TokenClass alloc] init];
});
return _sharedTokenClass;
}
You will use it it from any place you want by
[TokenClass sharedTokenClass]tokenValue];
If I were you, I would use the first variant.
PS. I strongly recommend you to read some memory management articles to get the point of object's lifecycle.
You need to use Singleton class to expose variables or objects to the entire project or create global variables. Create sharedInstance of TokenClass class and create property which can be accessed anywhere
in your .h file
//token class header file
#interface TokenClass : NSObject
#property (nonatomic,strong) NSString *tokenValue;
//create static method
+ (id)sharedInstance;
in .m file
#import "TokenClass.h"
#implementation TokenClass
#pragma mark Singleton Methods
+ (id)sharedInstance {
static TokenClass *sharedInstance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedInstance = [[self alloc] init];
});
return sharedInstance;
}
- (id)init {
if (self = [super init]) {
tokenValue = [[NSString alloc] init];
}
return self;
}
#end
now in your appDelegate
#import TokenClass.h
#implementation AppDelegate
in `didFinishLaunchingWithOptions:`
[TokenClass sharedInstance] setTokenValue:#"as"];
in any class you can get value using
NSLog(#"tokenValue = %#", [[SingletonClass sharedInstance] tokenValue]);
I made an IOS Application with a login function. I use the ASIHTTPRequest for this to check if the user exists in a MySQL database. All works fine but i want to pass the userid trough other viewcontrollers or retrieve it on other parts of the app.
Can someone push me in the right direction how to do this in the best and most secure way?
This is my the way i do a POST HTTP request to a PHP script:
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:#"*SOME_URL*"]];
In a callback i can retrieve the users ID, but i want to store this ID in the memory of the app for further use.
Thanks.
You can store userId in the AppDelegate.
Declare a userId property in AppDelegate.h with the following code:
#property (nonatomic, strong) NSString *userId
And then synthesize it in AppDelegate.m with the following code:
#synthesize userId;
You can then use userId from anywhere in your app. Add an import for the AppDelegate in the class you want to use userId, as follows:
#import "AppDelegate.h"
To retrieve the userId use the following code:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
NSString *userId = [appDelegate userId];
And to set the userId use the following code:
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate setUserId:YouValueRetrievedByInternetOrWhateverYouWant];
Have a session class that works as a singleton and can be accessed with a static method.
To do this you can add a static method
+ (Session *)sharedInstace {
static Session *session;
if (!session)
session = [Session new]; // there are more proper ways to instantiate a singleton class, not gonna get into it now
return session.
}
On that class .h you should add a NSString (or a class of your choice or whatever you want) and you can access it from wherever with [Session sharedInstance].userID