I'm having trouble with my In-App Purchases. Upon leaving the MasterViewController (where the IAP items are listed and bought) I come to the ViewController which in the viewDidLoad it checks for purchased items, but I also have a viewWillAppear checking for purchased items and unlocking those purchased items right when the ViewController is loaded.
The problem is, every time the ViewController is reloaded or refreshed, left and then gone back to, the viewWillAppear checks for purchased items and unlocks the items again, which in my case the purchased items come from javascript files, which happens to load the JS multiple times, cluttering the app with the same IAP item over and over again.
For instance, if you purchased 'Bookmarks', every time you reload the ViewController, you'll get another 'Bookmarks' tool, over and over again.
How do I get it so that the IAP is loaded only one time?
My viewWillAppear looks like this:
- (void)viewWillAppear:(BOOL)animated {
// Check To See If The Uniques IAP Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_UNIQUES_KEY] isEqualToString: #"YES"]){ // NEW CODE
// Code to show bookmarks
} else {
NSString *jsString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:#"Uniques" withExtension:#"js"] encoding:NSUTF8StringEncoding error:nil];
[viewWeb stringByEvaluatingJavaScriptFromString:jsString]; }
NSLog(#"Shows uniques?: %#", [[NSUserDefaults standardUserDefaults] objectForKey:SHOW_UNIQUES_KEY]);
// Check To See If The PL Numbers IAP Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_PORTAL_LEVEL_KEY] isEqualToString: #"YES"]){ // NEW CODE
// Code to show bookmarks
} else {
NSString *jsString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:#"PortalLevel" withExtension:#"js"] encoding:NSUTF8StringEncoding error:nil];
[viewWeb stringByEvaluatingJavaScriptFromString:jsString]; }
NSLog(#"Shows portal levels?: %#", [[NSUserDefaults standardUserDefaults] objectForKey:SHOW_PORTAL_LEVEL_KEY]);
// Check To See If The PN IAP Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_PORTALS_KEY] isEqualToString: #"YES"]){ // NEW CODE
// Code to show bookmarks
} else {
NSString *jsString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:#"PortalNames" withExtension:#"js"] encoding:NSUTF8StringEncoding error:nil];
[viewWeb stringByEvaluatingJavaScriptFromString:jsString]; }
NSLog(#"Shows portal names?: %#", [[NSUserDefaults standardUserDefaults] objectForKey:SHOW_PORTALS_KEY]);
// Check To See If The Bookmarks IAP Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_BOOKMARKS_KEY] isEqualToString: #"YES"]){ // NEW CODE
// Code to show bookmarks
} else {
NSString *jsString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:#"bookmarks" withExtension:#"js"] encoding:NSUTF8StringEncoding error:nil];
[viewWeb stringByEvaluatingJavaScriptFromString:jsString]; }
NSLog(#"Shows bookmarks?: %#", [[NSUserDefaults standardUserDefaults] objectForKey:SHOW_BOOKMARKS_KEY]);
// Check To See If The Poke A Bowl Ad Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_ADS_KEY] isEqualToString: #"No"]){ // NEW CODE
// Code to show ads
_PokeABowlAd.hidden = NO;
} else {
_PokeABowlAd.hidden = YES;
}
NSLog(#"Shows ads?: %#", [[NSUserDefaults standardUserDefaults] objectForKey:SHOW_ADS_KEY]);
}
I think what you should do is make it so that each one is called at most once. You should use NSUserDefaults to accomplish this. Do something like this:
Put this in your AppDelegate.m:
//One new key for each IAP - put at the top of the AppDelegate.m and VC.m
#define IAP_FIRST_USED #"IAP 1 Used"
...
#define IAP_LAST_USED #"IAP X Used"
and
// In applicationDidFinishLaunchingWithOptions, should be the first method listed
[[NSUserDefaults standardUserDefaults] setObject: #"No" forKey: IAP_FIRST_USED];
...
[[NSUserDefaults standardUserDefaults] setObject: #"No" forKey: IAP_LAST_USED];
[[NSUserDefaults standardUserDefaults] synchronize];
And then in the VC.m modify viewWillAppear to be like this (this is an example of what just the first IAP should look like):
// Check To See If The PL Numbers IAP Has Been Paid For
if (![[[NSUserDefaults standardUserDefaults] objectForKey:SHOW_PORTAL_LEVEL_KEY] isEqualToString: #"YES"]){ // NEW CODE
// NEW CODE BEGINS HERE
if ([[[NSUserDefaults standardUserDefaults] objectForKey:IAP_FIRST_USED] isEqualToString: #"No"]){
[[NSUserDefaults standardUserDefaults] setObject: #"Yes" forKey: IAP_FIRST_USED];
[[NSUserDefaults standardUserDefaults] synchronize];
// Code to show bookmarks
// End of code to show bookmarks
// NEW CODE ENDS HERE
}
} else {
NSString *jsString = [NSString stringWithContentsOfURL:[[NSBundle mainBundle] URLForResource:#"PortalLevel" withExtension:#"js"] encoding:NSUTF8StringEncoding error:nil];
[viewWeb stringByEvaluatingJavaScriptFromString:jsString];
}
Also remember to include the define statements in VC.m
Related
I am new to iOS i donot know how to save username and password in an app So that there is no need to fill login details in the app till then user do not logout the app. I save my username and password in NSUSerdefaults. I search on google. I found this could be done with KeyChainWrapper class . i tried that way but that will not give me useful result. AnyOne can me help out for this problem.He/She could be greatly appreciate for that.
Use the iOS keychain. Do Not use user defaults to store potentially important information such as logins and passwords.
Here's some documentation from Apple about its OS Keychain.
There's also this very handy, open sourced, wrapper around iOS Keychain called UICKeychainStore.
- (void)viewWillAppear:(BOOL)animated
{
if([[defaults objectForKey:#"remember"]isEqualToString:#"1"])
{
////for getting those values:
_txt_email.text = [defaults objectForKey:#"username"];
_txt_password.text = [defaults objectForKey:#"password"];
// Login code
}
[super viewWillAppear:animated];
}
- (IBAction)Login:(id)sender
{
//To store valuses:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if([[defaults objectForKey:#"remember"]isEqualToString:#"1"])
{
[defaults setObject:_txt_email.text forKey:#"username"];
[defaults setObject:_txt_password.text forKey:#"password"];
[defaults synchronize];
}
//Login here
}
easiest way is this, download this repo, and import it into your project:
https://github.com/soffes/sskeychain
Then do this:
[[NSUserDefaults standardUserDefaults] setObject:USERS_USERNAME forKey:#"com.yourapp.whateveryouwant"];
[[NSUserDefaults standardUserDefaults] synchronize];
[SSKeychain setPassword:USERS_PASSWORD forService:[[NSBundle mainBundle] bundleIdentifier] account:USERS_USERNAME];
Using the SSKeyChain repo, you can sync your User's user name to the password and store it securely
You can retrieve it and check it by doing this:
if ([[NSUserDefaults standardUserDefaults] valueForKey:#"com.yourapp.whateveryouwant"]) {
if ([SSKeychain passwordForService:[[NSBundle mainBundle] bundleIdentifier]
account:[[NSUserDefaults standardUserDefaults] valueForKey:#"com.yourapp.whateveryouwant"]]) {
//do work
}
}
try this:
call below method after successful log in and when user uses app next time push directly to the next view.
+(void)setFirstLogIn:(BOOL)flag
{
[[NSUserDefaults standardUserDefaults] setBool:flag forKey:#"FirstTime"];
[[NSUserDefaults standardUserDefaults]synchronize];
}
There are many ways to do that. If you are having more data to store after login, I would prefer to use CoreData or Sqlite. You can store create entity or table, and store you required data into that. Nexttime whenever you start app, Check it the values are stored or not.
Here the links which help you to start with basics for coredata and sqllite
CoreData Referral
http://www.raywenderlich.com/85578/first-core-data-app-using-swift
Sqlite Referral
http://www.theappguruz.com/blog/use-sqlite-database-swift
To store values:
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"username"];
[[NSUserDefaults standardUserDefaults] setObject:#"" forKey:#"password"];
[[NSUserDefaults standardUserDefaults] synchronize];
for getting those values:
[[NSUserDefaults standardUserDefaults] valueForKey: #"username"];
[[NSUserDefaults standardUserDefaults] valueForKey: #"password"];
NSUserDefaults will remove when your app will be removed from device. Otherwise it will store the values.
Use the class NSUserDefaults
[[NSUserDefaults standardUserDefaults] setObject:#"username" forKey:#"usersave"];
[[NSUserDefaults standardUserDefaults] setObject:#"usernamexxx" forKey:#"passsave"];
[[NSUserDefaults standardUserDefaults] synchronize];
Get them using:
NSString *password = [[NSUserDefaults standardUserDefaults] objectForKey:#"passsave"];
NSString *username = [[NSUserDefaults standardUserDefaults] objectForKey:#"usersave"];
The app's current behavior is, the user logged in once will not be logged out unless the user explicitly clicks on the logout.
I keep the user logged in, even if the user closes the app and opens it again.
When newer version of my app is released in appstore, I want to find out whether the user updated my app and opened it for the first time.
At that point I want to make them login again.
Is there a way to find out at the first time launch of the app after its been updated to latest version?
Create some kind of version #'s scheme. Note: You can enable Xcode to create backups and versions whenever you make substantial changes to the code.
There are a number of ways one could create a version constant, save it, and read it back.
When you update an app from the store, there is app data that persists from the previous installed version of the app, which you can read back to determine the version and, then update that persistent data to be ready for the next update cycle.
This answer was a very popular solution in another similar question.
Or, try something like #JitendraGandhi's ObjC answer below, or if you use Swift, try something like my port of #JitendraGandhi's ObjC example to Swift:
func hasAppBeenUpdatedSinceLastRun() -> Bool {
var bundleInfo = Bundle.main.infoDictionary!
if let currentVersion = bundleInfo["CFBundleShortVersionString"] as? String {
let userDefaults = UserDefaults.standard
if userDefaults.string(forKey: "currentVersion") == (currentVersion) {
return false
}
userDefaults.set(currentVersion, forKey: "currentVersion")
userDefaults.synchronize()
return true
}
return false;
}
You can save your currentversion to NSUserDefaults and use this method to check your version every time the app awakes:
#pragma mark - NSBundle Strings
- (NSString *)currentVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
}
if the currentversion is different from stored... its time to show the login!
Hope it helps you.
Use NSUserDefaults to store the CFBundleVersion. Then check against it every time the application is launched.
// Check if new version
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSString *currentAppVersion = [[[NSBundle mainBundle] infoDictionary] objectForKey:#"CFBundleVersion"];
if ([defaults objectForKey:#"savedAppVersionKey"] != nil) {
// Key exists
NSString *savedAppVersion = [defaults objectForKey:#"savedAppVersionKey"];
if ([currentAppVersion isEqualToString:savedAppVersion]) {
// Still running the same app version
// Do nothing
NSLog(#"App version: SAME");
}
else {
// The app version changed from the last launch
// Do something here
NSLog(#"App version: UPDATED");
}
}
// Set the key & synchronize
[defaults setObject:currentAppVersion forKey:#"savedAppVersionKey"];
If you want simple and easy solution, Use this function :
-(BOOL)isAppUpdated
{
NSDictionary *bundleInfo = [[NSBundle mainBundle] infoDictionary];
NSString *currentVersion = [bundleInfo objectForKey:#"CFBundleShortVersionString"];
if ([[[NSUserDefaults standardUserDefaults] objectForKey:#"currentVersion"] isEqualToString:currentVersion])
{
return NO ;
}
else
{
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:#"currentVersion"];
return YES ;
}
}
Following code will return NO / YES. You can call this method multiple times to know whether app was updated before this launch or not.
- (BOOL)launchedFirstTimeAfterUpdate
{
static NSString *lastVersion;
NSString *currentVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:#"CFBundleShortVersionString"];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *versionKeyName = #"lastLaunchedVersion";
lastVersion = [[NSUserDefaults standardUserDefaults] stringForKey:versionKeyName];
[[NSUserDefaults standardUserDefaults] setObject:currentVersion forKey:versionKeyName];
[[NSUserDefaults standardUserDefaults] synchronize];
});
if (!lastVersion.length)
{
// No last version means, launched first time
return NO;
}
if ([lastVersion compare:currentVersion options:NSNumericSearch] == NSOrderedAscending)
{
// Last version is less than current version
return YES;
}
return NO;
}
is there a way to run a code only once?Not for each usage.When the user download the app and runs it, the code will run only once and not anymore.
i tried to create a integer with initial 0 and save it to nsuerdefaults.
and made this special code in a if statement check, if its 0 enter the code but if its not 0 don't enter it, and in the if statement i incremented 0 and saved it so it won't ever again enter it.
but it didn't work out
NSInteger temp = [[NSUserDefaults standardUserDefaults] integerForKey:#"forOnce"];
if (temp==0) {
int k=0;
if (NSClassFromString(#"CTTelephonyNetworkInfo"))
{
CTTelephonyNetworkInfo *netInfo =[[CTTelephonyNetworkInfo alloc]init];
CTCarrier *carrier =[netInfo subscriberCellularProvider];
testUser[#"Carrier"]=[carrier carrierName];
//NSLog(#"carrier bu %#",[carrier carrierName]);
}
temp++;
[[NSUserDefaults standardUserDefaults] setInteger:k forKey:#"forOnce"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
the code was something like that
if(![[NSUserDefaults standardUserDefaults] boolForKey:#"EXECUTE_ALREADY"])
{
NSLog(#"ONCE");
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"EXECUTE_ALREADY"];
[[NSUserDefaults standardUserDefaults] synchronize];
}
The basic idea is same as yours. It works.
Try this method in AppDelegate.h
#property(nonatomic,assign)BOOL isFirst;
- (BOOL) isFirstRun
{
Defaults = [NSUserDefaults standardUserDefaults];
if ([Defaults objectForKey:#"isFirstRun"])
{
//if application running in second time
isFirst=NO;
NSLog(#"isSecondRun isfirst:%d",isFirst);
return NO;
}
//if application running in first time
isFirst=YES;
NSLog(#"isFirstRun isfirst:%d",isFirst);
[Defaults setObject:[NSDate date] forKey:#"isFirstRun"];
[[NSUserDefaults standardUserDefaults] synchronize];
return YES;
}
and check
NSLog(#"isfirst:%d",[self isFirstRun]);
if(isFirst==1)
{
//Do your operations.
}
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.
I am pasring json data from a url and taking one of teh values to NSUserdefaults to use it it application view .
Example user will enter an unique code given to him and this code will be appended to the requested URL , accoridng to the requested code the json value will be changed .
At present when i enter code it is fetching and saving NSuserdefaults and passing it to the label filed in view . But when i enter new code and fecth new data it is not updating in the view . If i restart the application and enter new code then it is showing new Value . Can somebody help me . here is the code
NSString *jsonUrlString = [[NSString alloc] initWithFormat:#"http://mywebsite.com/json/code.php?user=%#",_opcodeField.text];
NSURL *url = [NSURL URLWithString:[jsonUrlString stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
NSURLRequest *request = [[NSURLRequest alloc]initWithURL:url];
[_indicator startAnimating];
_indicator.hidesWhenStopped = YES;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
//-- JSON Parsing
NSMutableArray *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"settings = %#",result);
for (NSDictionary *dic in result)
{
[[NSUserDefaults standardUserDefaults] setObject:[dic objectForKey:#"footer"] forKey:#"op_footer"];
[[NSUserDefaults standardUserDefaults] synchronize];
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
View code is:
(void)viewDidLoad {
[super viewDidLoad];
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
How can I show new values without restarting application . Is there any code to be added in view ? I can see json is fetching correctly newvalues when user entered new code
Thank you.
Are you sure viewDidLoad is the best place to add this code?.
Try to add your code in some method which is called more often. For debugging I usually add some button just to call a NSLog to see what happens.
Try this if you want to load the text all the time you come to the viewcontroller.
- (void)viewWillAppear:(BOOL)animated {
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
}
Or you can try this if you want to load it to a label after getting the new code.
- (void)updateLabel {
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
}
Hope this helps.. :)
Why not move this line...
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
to here (unless of course this is in a different class)...
NSMutableArray *result = [NSJSONSerialization JSONObjectWithData:responseData options:NSJSONReadingMutableContainers error:nil];
NSLog(#"settings = %#",result);
for (NSDictionary *dic in result)
{
[[NSUserDefaults standardUserDefaults] setObject:[dic objectForKey:#"footer"] forKey:#"op_footer"];
[[NSUserDefaults standardUserDefaults] synchronize];
[_indicator performSelectorOnMainThread:#selector(stopAnimating) withObject:nil waitUntilDone:YES];
}
//Update it straight away
_Footer.text = [[NSUserDefaults standardUserDefaults] objectForKey:#"op_footer"];
Also bit confused why you are looping through dictionaries here? Is there more than one dictionary? If there is the last value will always be the one that gets stored in UserDefaults.
You are also stopping the animation in the loop as well?
The user defaults plist can take time to save and update. It doesn't happen instantly. Try triggering the call after a pause of some kind. Test with a button.
Edit: you can add an observer to the default so that you know it has updated - Cocoa - Notification on NSUserDefaults value change?