I have an iOS app that uses In App Purchase.
It uses a consumable type product which when purchased by a user allows him/her to upload an audio to the server.
Once the InAppPurchase is successful, the data is uploaded to the server.
How do we handle the case when the InAppPurchase is successful but the uploading fails?
We shouldn't make the user buy the product again so I'm storing whether the upload is successful or not in user defaults.
Here is the code
SKProduct *productToBuy = //product to buy.
if ([[[NSUserDefaults standardUserDefaults] objectForKey:productToBuy.productIdentifier] boolValue]) //if the user has already bought the product and it failed to upload, then just try to upload without buying..
{
NSLog(#"already purchased, uploading");
[self uploadRecording];
}
else //if not, then make the user purchase and then upload
{
[[CGInAppPurchaseManager sharedManager] buyProduct:productToBuy withCompletionBlock:^(SKPaymentTransaction *paymentTransaction, BOOL success) {
if (success)
{
[self uploadRecording];
}
else
{
if (paymentTransaction.error.code != SKErrorPaymentCancelled)
{
NSLog(#"Cancelled transaction");
}
}
}];
}
The uploadRecording code looks like this
- (void)uploadRecording
{
NSURL *urlOfAudio = //url of audio file
[self.model uploadAdOrDedicationWithTitle:title audioData:[NSData dataWithContentsOfURL:urlOfAudio] withCompletionBlock:^(id obj, NSInteger errCode, NSString *errorDescription) {
SKProduct *productToBuy = self.adParams[#"product"];
if (obj)
{
//if its successfully uploaded set false that the user has already bought a product with this identifier
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:NO] forKey:productToBuy.productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
}
else
{
//when there is an error set true that the user has already bought a product with this identifier
[[NSUserDefaults standardUserDefaults] setObject:[NSNumber numberWithBool:YES] forKey:productToBuy.productIdentifier];
[[NSUserDefaults standardUserDefaults] synchronize];
//show appropriate error message
//"An error occured while uploading your broadcast. You will not be made to purchase the product again the next time you try to upload a recording for the same product."
}
}];
}
Is this okay? Or is there a better way to handle this?
Ideally you should refund, but it isn't possible, so you are stuck with trying the minimize and remedy the problem the best way you can. Your solution is a remedy that does seem like the least you can do. Additionally, if there is a way to do some verification to check that the upload is likely to function ahead of the actual upload (using the Reachability API or better still connecting to your server for a quick availability check), then this would minimize the risk of this occurring.
Related
I want implement logout logic in my instagram simple app. For this i use SimpleAuth library. First time, when user launch app everything is ok. There is code in viewDidLoad:
-(void)viewDidLoad{
.. some code for interface management
[SimpleAuth authorize:#"instagram" options:#{#"scope":#[#"likes"]} completion:^(NSDictionary* responseObject, NSError *error) {
self.accessToken = responseObject[#"credentials"][#"token"];
[userDefaults setObject:self.accessToken forKey:#"accessToken"];
[userDefaults synchronize];
NSLog(#"access token is afterProceed %#", self.accessToken);
[self refresh];
}];
} else {
[self refresh];
}
Fine, for logout logic i made simple method:
-(void)logoutButtonClicked{
//Deleting token
self.accessToken = nil;
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:nil forKey:#"accessToken"];
[userDefaults synchronize];
}
After that, access.token suppose to be nil, and it is nil.
However, when i re-launch app (with access token set to nil), it just load again, and when i look at NSLog(#"access token is afterProceed %#", self.accessToken); there is an access token (used didn't enter in second time).
Why is that happening? How to implement logout logic to an app?
I believe it's because of your SimpleAuth Authorization request is still validated for the Account that is currently signed in to Instagram. You must remove the access token (like you've already done) along with making a Logout call to inactivate the current Account that has already been Authorized.
One way that you could do this is by creating a NSURLSessionTask with a URL that uses: https://www.instagram.com/accounts/logout/ to remove the current signed in user.
I am using AFNetworking 2.0 and Mantle in order to connect to an API and return a user account.
My plan is to call the function that gets the user data in the application:didFinishLaunchingWithOptions: method. I will then encode the data and save the user into NSUserDefaults
Is this the best way to approach this task? What alternatives are there? (I'd like to stay away from creating singletons)
UPDATE
Some code to maybe help show what I am thinking in my head:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *encodedUserData = [defaults objectForKey:#"currentUser"];
if (encodedUserData) {
self.currentUser = [NSKeyedUnarchiver unarchiveObjectWithData:encodedUserData];
} else {
NSLog(#"No current user");
// Show login ViewController
}
If you are going to get the user account every time user launches the app, then you don't need to store it in NSUserDefaults. Just use a singleton or static object to store it as your model object type.
static Account *_userAccount = nil;
+ (Account*)userAccount {
return _userAccount;
}
+ (void)setUserAccount:(Account*)account {
_userAccount = account;
}
You can use NSUserDefaults for this purpose and can access it through out application.
// to save data in userdefaults
[[NSUserDefaults standardUserDefaults] setObject:#"your object" forKey:#"your key"];
[[NSUserDefaults standardUserDefaults] synchronize];
// for getting saved data from User defaults
NSData *encodedUserData = [[NSUserDefaults standardUserDefaults] objectForKey:#"your key"];
if (encodedUserData) {
self.currentUser = [NSKeyedUnarchiver unarchiveObjectWithData:encodedUserData];
} else {
NSLog(#"No current user");
// Show login ViewController
}
Im digging into Apple's Touch ID, more precisely the Local Authenticator.
The documentation as of now is pretty sparse.
Its mainly just this:
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
NSString *myLocalizedReasonString = <#String explaining why app needs authentication#>;
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics
localizedReason:myLocalizedReasonString
reply:^(BOOL success, NSError *error) {
if (success) {
// User authenticated successfully, take appropriate action
} else {
// User did not authenticate successfully, look at error and take appropriate action
}
}];
} else {
// Could not evaluate policy; look at authError and present an appropriate message to user
}
as taken from https://developer.apple.com/documentation/localauthentication
The idea of using your fingerprint for authentication is nice. But I can add fingerprints in the device if I know the passcode. And its very easy to get the passcode, like you sit in the train next to ur victim and watch him/her enter the passcode.
I want to use the fingerprint as a way of secure authentication but want to be able to detect if new fingerprints were added since the last time I requested the fingerprint.
Apple is doing this for the AppStore. If you want to authenticate a transaction in the AppStore and have added a new Fingerprint since your last transaction, the AppStore requests your AppleId-Password. This is sane behaviour, because the phone might have been taken by someone else who knows the passcode and added his own fingerprint to buy something expensive.
My Question: Can I detect if a new fingerprint was added since the last time that I used Local Authenticator?
This is now possible in iOS9. The property evaluatedPolicyDomainState has been added to LAContext.
If the fingerprint database is modified (fingers were added or removed), the data returned by evaluatedPolicyDomainState will change. The nature of the changes cannot be determined but by comparing data of evaluatedPolicyDomainState after different evaluatePolicy calls you can detect that the set of fingerprints has been modified.
Note that this property is set only when evaluatePolicy is called and a succesful Touch ID authentication was performed, or when canEvaluatePolicy succeeds for a biometric policy.
As Keith stated, in iOS 9 it's possible. You should do it like this.
let context = LAContext()
context.canEvaluatePolicy(.DeviceOwnerAuthenticationWithBiometrics, error: nil)
if let domainState = context.evaluatedPolicyDomainState
where domainState == oldDomainState {
// Enrollment state the same
} else {
// Enrollment state changed
}
Every time you add or delete a fingerprint, the domain state changes. You need to call canEvaluatePolicy for evaluatedPolicyDomainStateto be updated.
In short; no.
In a bit more detail; the LocalAuthentication framework is a tightly-guarded black box. The information you get back from it is very limited. Your interaction with it goes something like this:
Ask it if it's able to authenticate for some type of policy (there is only 1 available at time of writing - Biometrics (Touch ID))
If it can, ask it to actually do it
The system takes over for the actual authentication
It lets you know if the authentication was successful or not (if not, it tells you why)
You have no concept of the actual authentication process (which finger was used, for example). This, of course, is by design. Apple does not want, nor need, to give you access to such information.
I would recommend to store the evaluatedPolicyDomainState value into keychain instead of storing it in NSUserDefault.
You can convert the data value of evaluatedPolicyDomainState into string, which is a 44 character string. Below is the code to convert the evaluatedPolicyDomainState data value into string -
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) {
if let domainState = context.evaluatedPolicyDomainState {
let bData = domainState.base64EncodedData()
if let decodedString = String(data: bData, encoding: .utf8) {
print("Decoded Value: \(decodedString)")
}
}
}
Now if the device owner made any change in Touch ID like adding a new finger Id; then this data value will be changed and you can take necessary steps to handle the change based on your project needs.
This is the solution to verify if a fingerprint was added or removed, and the difference between the Swift and ObjC solution is that canEvaluatePolicy just verify if something changes, while evaluatePolicy opens the modal verification.
Swift 5.2
let context = LAContext()
context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil)
let defaults = UserDefaults.standard
let oldDomainState = defaults.object(forKey: "domainTouchID") as? Data
if let domainState = context.evaluatedPolicyDomainState, domainState == oldDomainState {
// Enrollment state the same
print("nothing change")
} else {
// Enrollment state changed
print("domain state was changed")
}
// save the domain state for the next time
defaults.set(context.evaluatedPolicyDomainState, forKey: "domainTouchID")
Objective-C
- (void)evaluatedPolicyDomainState {
LAContext *context = [[LAContext alloc] init];
__block NSString *message;
// show the authentication UI with reason string
[context evaluatePolicy:LAPolicyDeviceOwnerAuthenticationWithBiometrics localizedReason:#"Unlock access to locked feature" reply:^(BOOL success, NSError *authenticationError) {
if (success) {
// load the last domain state from touch id
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *oldDomainState = [defaults objectForKey:#"domainTouchID"];
NSData *domainState = [context evaluatedPolicyDomainState];
// check for domain state changes
if ([oldDomainState isEqual:domainState]) {
message = #"nothing change";
} else {
message = #"domain state was changed";
}
// save the domain state that will be loaded next time
oldDomainState = [context evaluatedPolicyDomainState];
[defaults setObject:oldDomainState forKey:#"domainTouchID"];
[defaults synchronize];
} else {
message = [NSString stringWithFormat:#"evaluatePolicy: %#", authenticationError.localizedDescription];
}
[self printMessage:message inTextView:self.textView];
}];
}
I would like to add something,
-(BOOL)hasFingerPrintChanged
{
BOOL changed = NO;
LAContext *context = [[LAContext alloc] init];
[context canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:nil];
NSData *domainState = [context evaluatedPolicyDomainState];
// load the last domain state from touch id
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *oldDomainState = [defaults objectForKey:#"domainTouchID"];
if (oldDomainState)
{
// check for domain state changes
if ([oldDomainState isEqual:domainState])
{
NSLog(#"nothing changed.");
}
else
{
changed = YES;
NSLog(#"domain state was changed!");
NSString *message = #"Your Touch ID is invalidated, because you have added or removed finger(s).";
}
}
// save the domain state that will be loaded next time
[defaults setObject:domainState forKey:#"domainTouchID"];
[defaults synchronize];
return changed;
}
It's better to store user password etc in keychain.
I'm using https://github.com/reidmain/FDKeychain
I have one question near the end.
I am working from the belief/experience that seeding iCloud more than once is a bad idea and that if a user can do the wrong thing, he probably will sooner or later.
What I want to do:
A. When the user changes the app preference "Enable iCloud" from NO to YES, display AlertView asking (Yes or No) if the user wishes to seed the cloud with existing non-iCloud Data.
B. Ensure that the app seeds iCloud only once on an iCloud account, refraining to put up the AlertView once seeding is completed the first time.
My Method:
Following Apple's Docs concerning the proper use of NSUbiquitousKeyValueStore, I am using the following method in, - (void)application: dFLWOptions:
- (void)updateKVStoreItems:(NSNotification*)notification {
// Get the list of keys that changed.
NSDictionary* userInfo = [notification userInfo];
NSNumber* reasonForChange = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangeReasonKey];
NSInteger reason = -1;
// If a reason could not be determined, do not update anything.
if (!reasonForChange)
return;
// Update only for changes from the server.
reason = [reasonForChange integerValue];
if ((reason == NSUbiquitousKeyValueStoreServerChange) ||
(reason == NSUbiquitousKeyValueStoreInitialSyncChange)) { // 0 || 1
// If something is changing externally, get the changes
// and update the corresponding keys locally.
NSArray* changedKeys = [userInfo objectForKey:NSUbiquitousKeyValueStoreChangedKeysKey];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
// This loop assumes you are using the same key names in both
// the user defaults database and the iCloud key-value store
for (NSString* key in changedKeys) {//Only one key: #"iCloudSeeded" a BOOL
BOOL bValue = [store boolForKey:key];
id value = [store objectForKey:#"iCloudSeeded"];
[userDefaults setObject:value forKey:key];
}
}
}
Include the following code near the top of application: dFLWO:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(updateKVStoreItems:)
name:NSUbiquitousKeyValueStoreDidChangeExternallyNotification
object:store]; // add appDelegate as observer
After loading iCloud Store, then seed it with non-iCloud data ONLY if seeding has never been done
- (BOOL)loadiCloudStore {
if (_iCloudStore) {return YES;} // Don’t load iCloud store if it’s already loaded
NSDictionary *options =
#{
NSMigratePersistentStoresAutomaticallyOption:#YES
,NSInferMappingModelAutomaticallyOption:#YES
,NSPersistentStoreUbiquitousContentNameKey:#"MainStore"
};
NSError *error=nil;
_iCloudStore = [_coordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil URL:[self iCloudStoreURL] options:options error:&error];
if (_iCloudStore) {
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded =
[store boolForKey:#"iCloudSeeded"];//If the key was not found, this method returns NO.
if(!iCloudSeeded) // CONTROL IS HERE
[self confirmMergeWithiCloud]; // Accept one USER confirmation for seeding in AlertView ONCE world wide
return YES; // iCloud store loaded.
}
NSLog(#"** FAILED to configure the iCloud Store : %# **", error);
return NO;
}
Once the seeding is completed do the following to prevent any repeat seeding:
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloud];
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"]; // NEVER AGAIN
//[store synchronize];
}
}
}
Be sure to get a total iCloud reset before the above process using:
[NSPersistentStoreCoordinator
removeUbiquitousContentAndPersistentStoreAtURL:[_iCloudStore URL]
options:options
error:&error])
This is a very tidy solution to my problem, IMHO, but I can not quite get it done.
MY QUESTION:
How do I respond to the first notification to updateKVStoreItems: above? It is a notification with bad info. I says the value is TRUE, but I have never set it to TRUE. How do I set default values for a key in NSUbiquitousKeyValueStore?
I find that the first notification is of reason : NSUbiquitousKeyValueStoreInitialSyncChange
When that note comes in, bValue is YES. THIS IS MY PROBLEM. It is as if, iCloud/iOS assumes any new BOOL to be TRUE.
I need this value to be NO initially so that I can go ahead and follow the Apple Docs and set
the NSUserDefault to NO. And then Later when the seeding is done, to finally set the value: YES for the key:#"iCloudSeeded"
I find I can not penetrate the meaning of the following from Apple:
NSUbiquitousKeyValueStoreInitialSyncChange
Your attempt to write to key-value storage was discarded because an initial download from iCloud has not yet happened.
That is, before you can first write key-value data, the system must ensure that your app’s local, on-disk cache matches the truth in iCloud.
Initial downloads happen the first time a device is connected to an iCloud account, and when a user switches their primary iCloud account.
I don't quite understand the implications of number 2 below, which I found online:
NSUbiquitousKeyValueStoreInitialSyncChange – slightly more complicated, only happens under these circumstances:
1. You start the app and call synchronize
2. Before iOS has chance to pull down the latest values from iCloud you make some changes.
3. iOS gets the changes from iCloud.
If this problem was with NSUserDefaults and not NSUbiquitousKeyValueStore, I believe I would need to go to registerDefaults.
I am almost there,
How do I do this please!
Thanks for reading, Mark
The code was looking for both
A. NSUbiquitousKeyValueStoreInitialSyncChange and
B. NSUbiquitousKeyValueStoreServerChange
I was unable to figure out what to do with the notifications. I know see that I did not need to do anything with either. My app only needs to read and write, in order to solve the problem I laid out in my question header.
The app gets the current value with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
BOOL iCloudSeeded = [store boolForKey:#"iCloudSeeded"];
The app sets the value in the NSUbiquitousKeyValueStore with:
NSUbiquitousKeyValueStore* store = [NSUbiquitousKeyValueStore defaultStore];
[store setBool:YES forKey:#"iCloudSeeded"];
I believe I am correct in saying the following: Writing is done into memory. Very soon thereafter the data is put by the system onto disk.
From there it is taken and put into iCloud and is made available to the other devices running the same app on the same iCloud account. In the application I have described, no observer needs to be added, and
nothing else needs to be done. This is maybe an "unusual" use of NSUbiquitousKeyValueStore.
If you came here looking for a an more "usual" use, say when a user type something into a textview and it later
appears on a view of other devices running the same app, check out a simple demo I came across at :
https://github.com/cgreening/CMGCloudSyncTest
The better functioning (monitoring only) notification handler follows:
- (void)updateKVStoreItems:(NSNotification*)notification {
NSNumber *reason = notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey];
if(!reason) return;
// get the reason code
NSInteger reasonCode = [notification.userInfo[NSUbiquitousKeyValueStoreChangeReasonKey] intValue];
BOOL bValue;
NSUbiquitousKeyValueStore *store;
switch(reasonCode) {
case NSUbiquitousKeyValueStoreServerChange:{ // code 0, monitoring only
store = [NSUbiquitousKeyValueStore defaultStore];
bValue = [store boolForKey:#"iCloudSeeded"];
id value = [store objectForKey:#"iCloudSeeded"];
DLog(#"New value for iCloudSeeded=%d\nNo Action need be take.",bValue);
// For monitoring set in UserDefaults
[[NSUserDefaults standardUserDefaults] setObject:value forKey:#"iCloudSeeded"];
break;
}
case NSUbiquitousKeyValueStoreAccountChange: {// ignore, log
NSLog(#"NSUbiquitousKeyValueStoreAccountChange");
break;
}
case NSUbiquitousKeyValueStoreInitialSyncChange:{ // ignore, log
NSLog(#"NSUbiquitousKeyValueStoreInitialSyncChange");
break;
}
case NSUbiquitousKeyValueStoreQuotaViolationChange:{ // ignore, log
NSLog(#"Run out of space!");
break;
}
}
}
Adding 9/3/14
So sorry but I continued to have trouble using a BOOL, I switched to an NSString and now
all is well.
METHOD TO ENSURE THAT THE "MERGE" BUTTON FOR SEEDING ICOUD IS USED AT MOST ONCE DURING APP LIFETIME
Use NSString and not BOOL in KV_STORE. No need to add observer, except for learning
In Constants.h :
#define SEEDED_ICLOUD_MSG #"Have Seeded iCloud"
#define ICLOUD_SEEDED_KEY #"iCloudSeeded"
Before calling function to seed iCloud with non-iCloud data:
NSUbiquitousKeyValueStore* kvStore = [NSUbiquitousKeyValueStore defaultStore];
NSString* strMergeDataWithiCloudDone =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
NSComparisonResult *result = [strMergeDataWithiCloudDone compare:SEEDED_ICLOUD_MSG];
if(result != NSOrderedSame)
//put up UIAlert asking user if seeding is desired.
If user chooses YES : set Value for Key after the merge is done.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if (alertView == self.seedAlertView) {
if (buttonIndex == alertView.firstOtherButtonIndex) {
[self seediCloudwithNoniCloudData];
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
[store setObject:SEEDED_ICLOUD_MSG forKey:ICLOUD_SEEDED_KEY];
}
}
}
Thereafter on all devices, for all time, the code
NSUbiquitousKeyValueStore* kvStoretore = [NSUbiquitousKeyValueStore defaultStore];
NSString* msg =
[kvStore stringForKey:ICLOUD_SEEDED_KEY];
produces: msg == SEEDED_ICLOUD_MESSAGE
I am new to iOS development and have the following problem.
EXPLANATION:
I have an app which consists of several mini games. Each mini game is available through in app purchase. However the mini game availability is saved in a BOOL variable like this:
_miniGamesArray = [[NSMutableArray alloc] init];
NSMutableDictionary *mutDictMg0 = [NSMutableDictionary dictionaryWithCapacity:3];
[mutDictMg0 setValue:[self.gameAvailabilityMutableArray objectAtIndex:0] forKey:#"GameAvailable"];
[_miniGamesArray addObject:mutDictMg0];
PROBLEM:
Each time I start the app the game availability is checked from the self.gameAvailabilityMutableArray which is set to:
- (NSMutableArray *)gameAvailabilityMutableArray
{
if (!_gameAvailabilityMutableArray)
{
//[_gameAvailabilityMutableArray addObjectsFromArray:#[#1,#0,#0,#0 ,#0,#0,#0,#0]];
_gameAvailabilityMutableArray = [[NSMutableArray alloc] initWithArray:#[#1,#1,#1,#1 ,#1,#1,#1,#1]];
}
return _gameAvailabilityMutableArray;
}
When the customer buys a mini game I want the array to be set to (example):
#[#1,#1,#0,#0 ,#0,#0,#0,#0]]
TRIED SOLUTIONS:
I tried to implement the array by calling the iTunes server and writing the data. However, the time to recieve the request is greater then the app loading time. The second problem arrises if there is no internet connection, then the app crashes.
I also tried using .plist files. I don't know why but writing to the plist file doesn't change it's contest all the time! Some times it works , sometimes it doesn't... Sometimes the app loads the values correctly sometimes it mixes them with the last values.
QUESTION:
Is there a way to store permanent app data which is being checked when the app loads beside plists?
Thank you for your time.
save your data in NSUserDefaults.then use below conditions for app start first time or handle another conditions.
BOOL iSFirstTime = [[NSUserDefaults standardUserDefaults] boolForKey:#"AppStartFirstTime"];
if (!iSFirstTime) {
NSLog(#"Application start first time");
[[NSUserDefaults standardUserDefaults] setValue:#"ImgDownloaded" forKey:#"ProductIDDef"];
[[NSUserDefaults standardUserDefaults] synchronize];
[[NSUserDefaults standardUserDefaults] setBool:YES forKey:#"AppStartFirstTime"];
}else{
// NSLog(#"we are here");
if ([[[NSUserDefaults standardUserDefaults] valueForKey:#"ProductIDDef"] isEqualToString:#"ImgDownloaded"]) {
NSLog(#"All transaction and Image Downloading has been perform successfully");
}else{
//If any network problem or any thing else then handle here.
}
}
}
You can save the data in NSUserDefaults using the following code:
[[NSUserDefaults standardUserDefaults] setObject:_gameAvailabilityMutableArray forKey:#"gameArray"];
[[NSUserDefaults standardUserDefaults] synchronise];
and you can retrieve the array using
_gameAvailabilityMutableArray = [[NSUserDefaults standardUserDefaults] objectForKey:#"gameArray"];