I have an app at the appstore , and i would like to add it in-app purchase, a basic one ,in order to purchase more levels .
I know that the apple sdk is very hard to implement,
I know the mkstorekit is an easy one, but i cant find a guide from scratch to use it .
Whats the best way to do so, any other method ?any good tutorial ?
Thanks a lot.
First you need to initialize MKStoreKit. A sample initializiation code that you can add to your application:didFinishLaunchingWithOptions: is below In Objective-C
[[MKStoreKit sharedKit] startProductRequest];
[[NSNotificationCenter defaultCenter] addObserverForName:kMKStoreKitProductsAvailableNotification
object:nil
queue:[[NSOperationQueue alloc] init]
usingBlock:^(NSNotification *note) {
NSLog(#"Products available: %#", [[MKStoreKit sharedKit] availableProducts]);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kMKStoreKitProductPurchasedNotification
object:nil
queue:[[NSOperationQueue alloc] init]
usingBlock:^(NSNotification *note) {
NSLog(#"Purchased/Subscribed to product with id: %#", [note object]);
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kMKStoreKitRestoredPurchasesNotification
object:nil
queue:[[NSOperationQueue alloc] init]
usingBlock:^(NSNotification *note) {
NSLog(#"Restored Purchases");
}];
[[NSNotificationCenter defaultCenter] addObserverForName:kMKStoreKitRestoringPurchasesFailedNotification
object:nil
queue:[[NSOperationQueue alloc] init]
usingBlock:^(NSNotification *note) {
NSLog(#"Failed restoring purchases with error: %#", [note object]);
}];
You can check if a product was previously purchased using -isProductPurchased as shown below.
if([MKStoreManager isProductPurchased:productIdentifier]) {
//unlock it
}
You can check for a product's expiry date using -expiryDateForProduct as shown below.
if([MKStoreManager expiryDateForProduct:productIdentifier]) {
//unlock it
}
To purchase a feature or to subscribe to a auto-renewing subscription, just call
[[MKStoreKit sharedKit] initiatePaymentRequestForProductWithIdentifier:productIdentifier];
You can also find tutorials of mkstorekit here and here.
Related
I am writing SDK which will be used by watchOS apps. I need to catch the moment when the app is closed like at image. Or even when app cames from not running state to active state. For now I can only catch transition from inactive to active.
I already try this:
_applicationWillEnterForegroundObserver = [notificationCenter addObserverForName:#"UIApplicationWillEnterForegroundNotification" object:NULL queue:NULL usingBlock:^(NSNotification * _Nonnull note) {
[self willEnterForeground];
}];
_applicationDidBecomeActiveObserver = [notificationCenter addObserverForName: #"UIApplicationDidBecomeActiveNotification" object:NULL queue:NULL usingBlock:^(NSNotification * _Nonnull note) {
[self didBecomeActive];
}];
[notificationCenter addObserverForName:#"UIApplicationWillTerminateNotification" object:NULL queue:NULL usingBlock:^(NSNotification * _Nonnull note) {
[self willTerminate];
}];
_applicationWillResignActiveObserver = [notificationCenter addObserverForName:#"UIApplicationWillResignActiveNotification" object:NULL queue:NULL usingBlock:^(NSNotification * _Nonnull note) {
[self willResignActive];
}];
WillTerminate notification does not work at all. :( How Can I achieve this?
I have an instance in a view called "PayView" and at the meantime I am doing openURL to open a separate app and to pass some data to it. This second app process the information I send and gives the response back.
At Appdelegate.m I have this handleOpenUrl which receives the response sent by the second app. Once I receive the response back I would like to go back to my "PayView" and use the response received from the second app along with already existing values I have in the instance.
My problem is as soon as the response from the second app reaches the appdelegate and reached the section "return yes", my main app goes back to this "PayView" and does nothing.
So how do I use the response object I received from appdelegate at my PayView along with already existing instance values?
I thought about using a global variable to store the "payView" instance/object and initiate a new instance from appdelegate for "Payview" and use the global along with the response json from appdelegate. However, I found many forums advising against using global.
So, ignoring the global and crating a new instance for "payview" causes loss of all previously stored data.
I am not an experienced iOS programmer and just work momentarily on someone else code.So, hope I explained my issue/question.
It would be great if I could get some valuable inputs :)
My appdelegate.m look like this,
-(BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
if (!url) {
return NO;
}
// Unencode the URL's query string
NSString *queryString = [[url query] stringByRemovingPercentEncoding];
// Extract the JSON string from the query string
queryString = [queryString stringByReplacingOccurrencesOfString:#"response=" withString:#""];
// Convert the JSON string to an NSData object for serialization
NSData *queryData = [queryString dataUsingEncoding:NSUTF8StringEncoding];
// Serialize the JSON data into a dictionary
NSDictionary* jsonObject = [NSJSONSerialization JSONObjectWithData:queryData options:0 error:nil];
NSString *response = [jsonObject objectForKey:#"Result"]; //Get the response
UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:#"Result" message:response delegate:nil cancelButtonTitle:#"OK" otherButtonTitles: nil];
[alert1 show];
//PayView *pdv = [[PayViewController alloc] init];
//[pdv UseTransactionResult:jsonObject] ;
return YES;
}
and this is how I am calling open url from PayView
[[UIApplication sharedApplication]openURL:[NSURL URLWithString:[NSString stringWithFormat:#"mysecondapp://v2/data/?request=%#",requestEncodedString]] options:#{} completionHandler:^(BOOL success) {
if (success)
{
NSLog(#"Opened url");
}
}];
Edit 1:
Hi #ekscrypto, thanks for your valuable input. It is really helpful. I just have one problem with this. It is working just fine when I do the following in my PayView
Receiver:
[[NSNotificationCenter defaultCenter] addObserverForName:#"ActionIsComplete" object:nil queue:nil usingBlock:^(NSNotification *note){
//Completed Action
NSString *response = [note.userInfo objectForKey:#"Result"]; //Get the response
NSLog(response);
[self dismissViewControllerAnimated:YES completion:nil];
}];
However, when I try to do the same in the following method I get error "unrecognized selector sent to instance"
Receiver:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReceiveActionIsComplete:) name:#"ActionIsComplete" object:nil];
-(void)ReceiveActionIsComplete:(NSNotification *) notification
{
NSDictionary *jsonObject = (NSDictionary *)notification.userinfo;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ActionIsComplete" object:nil];
Status = [jsonObject objectForKey:#"Status"];
Result = [jsonObject objectForKey:#"Result"];
NSLog([NSString stringWithFormat:#"%#%#",#"Status is: ", Status]);
NSLog([NSString stringWithFormat:#"%#%#",#"Result is: ", Result]);
}
in both cases my sender at Appdelegate looks like this.
Sender:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ActionIsComplete" object:nil userInfo:jsonObject];
FYI: I tried sending the object in object instead of userInfo as well.
So what I am doing wrong? could you please help me.
Conclusion:
Sender: (AppDelegate.m)
[[NSNotificationCenter defaultCenter] postNotificationName:#"ActionIsComplete" object:nil userInfo:jsonObject];
Receiver: (PayView.m)
under - (void)viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(ReceiveActionIsComplete:) name:#"ActionIsComplete" object:nil];
and the function to receive the results
-(void)ReceiveActionIsComplete:(NSNotification *) notification
{
NSDictionary *jsonObject = (NSDictionary *)notification.userInfo;
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"ActionIsComplete" object:nil];
Status = [jsonObject objectForKey:#"Status"];
Result = [jsonObject objectForKey:#"Result"];
NSLog([NSString stringWithFormat:#"%#%#",#"Status is: ", Status]);
NSLog([NSString stringWithFormat:#"%#%#",#"Result is: ", Result]);
}
You could register a NotificationCenter observer in your PayView, and post a notification from your AppDelegate when the response is received. Via the notification object you can forward any information you need.
Let's assume that you define a struct with the information you want to pass along:
struct PaymentConfirmation {
let amount: Float
let confirmationNumber: String
}
In your PayView:
class PayView: UIView {
...
static let paymentProcessedNotification = NSNotification.Name("PayView.paymentProcessed")
let paymentProcessedObserver: NSObjectProtocol?
override func viewDidLoad() {
super.viewDidLoad()
paymentProcessedObserver = NotificationCenter.default.addObserver(
forName: PayView.paymentProcessedNotification,
object: nil,
queue: .main) { [unowned self] (notification) in
guard let confirmation = notification.object as PaymentConfirmation else { return }
self.paymentProcessed(confirmation)
}
}
deinit {
NotificationCenter.default.removeObserver(paymentProcessedObserver)
}
func paymentProcessed(_ confirmation: PaymentConfirmation) {
// do stuff
}
Then in your AppDelegate:
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
// do stuff required with the callback URL
let confirmation = PaymentConfirmation(
amount: 3.0,
confirmationNumber: "Pay830da08.8380zSomething")
NotificationCenter.default.post(
name: PayView.paymentProcessedNotification,
object: confirmation)
}
For more information, check out https://medium.com/ios-os-x-development/broadcasting-with-nsnotification-center-8bc0ccd2f5c3
I implemented MPMovieViewController in my app but it has a problem i.e.when i play video in player then first time it is failed to play but next time it will play successfully with the same URL. I am not able to understand this different behavior with the same URL. I copied code here which i used in my app.
NSURL *fileURL = [NSURL URLWithString:#"http://nordenmovil.com/urrea/InstalaciondelavaboURREAbaja.mp4"];
MPMoviePlayerViewController * controller = [[MPMoviePlayerViewController alloc]initWithContentURL:fileURL];
controller.moviePlayer.movieSourceType= MPMovieSourceTypeFile;
[controller.moviePlayer setControlStyle:MPMovieControlStyleDefault];
[controller.moviePlayer prepareToPlay];
[controller.moviePlayer play];
[self presentMoviePlayerViewControllerAnimated:controller];
I suggest to handle the error for see what happens with your URL video (this is a tip that I founded long time ago in this answer)
Add this for capture the end of play:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(handleMPMoviePlayerPlaybackDidFinish:)
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
and this for see what happens:
- (void)handleMPMoviePlayerPlaybackDidFinish:(NSNotification *)notification
{
NSDictionary *notificationUserInfo = [notification userInfo];
NSNumber *resultValue = [notificationUserInfo objectForKey:MPMoviePlayerPlaybackDidFinishReasonUserInfoKey];
MPMovieFinishReason reason = [resultValue intValue];
if (reason == MPMovieFinishReasonPlaybackError)
{
NSError *mediaPlayerError = [notificationUserInfo objectForKey:#"error"];
if (mediaPlayerError)
{
NSLog(#"playback failed with error description: %#", [mediaPlayerError localizedDescription]);
}
else
{
NSLog(#"playback failed without any given reason");
}
}
}
Don't forget to remove the notification handler:
[[NSNotificationCenter defaultCenter] removeObserver:self
name:MPMoviePlayerPlaybackDidFinishNotification
object:nil];
With this workaround you should know the error with your stream, I hope this helps!
Firstly, sorry if my english is not perfect, i'm not a native english person ;) !
I'm working on an Application which retrieves EKEvent From iCal and add them to my App.
The purpose of the application is a calendar so :
The user retrieve iCal's EKEvent from the calendar he wants to.
The EKEvent are saved into an Entity named "Event".
The user can edit, add, delete event from the application - the EKEvent associate will be modified in iCal too.
Issue : when the user modify something in iCal, it has to be modified into my application so the only way i found is to retrieve all EKEvent from iCal - when the app become active - and copy it into a BackUp Entity named "EventBackup". When all EKEvent from iCal are well retrieved and saved into the "EventBackup" entity i copy the entity into my main Entity "Event".
I'm doing it succesfully in async with
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, (unsigned long)NULL), ^(void) { });
But I have to keep using my application - so retrieving Event * from CoreData - while i'm doing the EventBackup... problem my application crash if i'm working on the CoreData.
Could you help on that way, or propose me something different than the way i'm doing.
Thanks a lot for helping me !
You should look at NSManagedObjectContext's performBlock: method. Specifically you should create a child context, make your changes in it on a background thread and then listen for the save notification to merge it into the parent context.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
NSManagedObjectContext *childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[childContext setParentContext:self.managedObjectContext];
[childContext performBlock:^{
//Do everything here...
NSError *error = nil;
[context save:&error];
if (error) {
NSLog(#"Error saving child context:%#", error.localizedDescription);
}
}];
Listen for the child context to save and then save the main context.
- (void)managedObjectContextDidSave:(NSNotification *)notification
{
if ([notification object] != self.managedObjectContext) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.managedObjectContext save:nil];
});
}
}
To be more clear about what i'm doing - using your code.
The methods in the block are working on the childContext's NSManagedObjectContext
I'm in the AppDelegate, that
- (void)methodToBeCalledEveryTimeTheAppBecomeActive
{
NSManagedObjectContext *contextParent = [[LavigneCoreData defaultManager] managedObjectContext];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(managedObjectContextDidSave:) name:NSManagedObjectContextDidSaveNotification object:nil];
childContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
[childContext setParentContext:contextParent];
[childContext performBlock:^{
[self copySpecialsEvents];
[self clearBackUpEntity];
[self getCalendarWhichHasBeenSelected];
[self copyNotesIntoBackUpEntity];
[self clearEntityEvent];
[self deepCopyFromBackUpEntityToEntity];
NSError *error = nil;
[contextParent save:&error];
if (error) {
NSLog(#"Error saving child context:%#", error.localizedDescription);
}
}];
}
i'm getting the error on the line : [childContext setParentContext:contextParent];
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Parent NSManagedObjectContext must use either NSPrivateQueueConcurrencyType or NSMainQueueConcurrencyType.'
EDIT :
i fixed this error by changing
_managedObjectContext = [[NSManagedObjectContext alloc] init];
with
_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
This code will call the method "defaultsChanged", when some value in UserDefaults changed
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center addObserver:self
selector:#selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
This Code will give me the VALUE that changed
- (void)defaultsChanged:(NSNotification *)notification {
// Get the user defaults
NSUserDefaults *defaults = (NSUserDefaults *)[notification object];
// Do something with it
NSLog(#"%#", [defaults objectForKey:#"nameOfThingIAmInterestedIn"]);
}
but how can I get the NAME of the key, that changed??
As others stated, there is no way to get the info about the changed key from the NSUserDefaultsDidChange Notification. But there is no need to duplicate any content and check for yourself, because there is Key Value Observing (KVO) which also works with the NSUserDefaults, if you need to specifically be notified of a certain property:
First, register for KVO instead of using the NotificationCenter:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults addObserver:self
forKeyPath:#"nameOfThingIAmInterestedIn"
options:NSKeyValueObservingOptionNew
context:NULL];
don't forget to remove the observation (e.g. in viewDidUnload or dealloc)
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults removeObserver:self forKeyPath:#"nameOfThingIAmInterestedIn"];
and finally implement this method to receive KVO notifications
-(void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
NSLog(#"KVO: %# changed property %# to value %#", object, keyPath, change);
}
There is no data provided in the notification's userInfo dictionary, so it looks like you're out of luck unless you want to keep another copy of the data stored in NSUserDefaults elsewhere and perform a diff on the two dictionaries.
Use custom notifications to determine what exactly happened, e.g.:
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:self.event, #"eventObject", nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"newEventCreated" object:nil userInfo:options];
If it is not an option with userDefaults, then just read all user defaults everytime you get your NSUserDefaultsDidChangeNotification notification and compair it with previous ones.
just add [[NSNotificationCenter defaultCenter] removeObserver:self name:NSUserDefaultsDidChangeNotification object:nil];
to your appDidBecomeActive method and then add
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(settingsChangedListener) name:NSUserDefaultsDidChangeNotification object:nil];
to your applicationDidEnterBackground
then use KVO observer as shown above when in the foreground
You can use dictionaryRepresentation to obtain the entire copy of NSUserDefaults as NSDictionary. Then it's a matter of comparing previous values and new values.
NSUserDefaults is not KVO compliant, the fact that it may fire some KVO notifications should not be relied upon and is a subject to change apparently.
For example:
- (void)setupUserDefaults {
self.userDefaults = [NSUserDefaults standardUserDefaults];
[self.userDefaults registerDefaults:#{ /* ... */ }];
self.userDefaultsDictionaryRepresentation = [self.userDefaults dictionaryRepresentation];
[notificationCenter addObserver:self
selector:#selector(userDefaultsDidChange:)
name:NSUserDefaultsDidChangeNotification
object:self.userDefaults];
}
- (void)userDefaultsDidChange:(NSNotification *)note {
NSDictionary *oldValues = self.userDefaultsDictionaryRepresentation;
NSDictionary *newValues = [self.userDefaults dictionaryRepresentation];
NSArray *allKeys = #[ /* list keys that you use */ ];
for(NSString *key in allKeys) {
id oldValue = oldValues[key];
id newValue = newValues[key];
if(![oldValue isEqual:newValue]) {
[self notifyObserversForKeyChange:key oldValue:oldValue newValue:newValue];
}
}
self.userDefaultsDictionaryRepresentation = newValues;
}