Post

Replies

Boosts

Views

Activity

Universal Purchase Questions (Mac and iOS)
I have a bunch of questions about universal purchases between iOS and Mac. I'm hoping I can get answers to these soon—or at least before March—but so far I don't think Apple has shared enough information to answer these definitively. I thought I'd ask them here anyway in case anyone can help.Will universal purchase have any OS requirements?As far as I can tell most of it will happen server side—all the appropriate info should just appear in the receipt like it normally would, and there aren't any changes to the receipt format or StoreKit. The only reason I can think of for an OS requirement would be if the App Store needs to be a particular version just to be able to download the app in the first place. Regardless, this seems like an incredibly important question to have answered before I can decide whether supporting universal purchase is an option.What does that mean for existing apps?Apple has made it clear the apps will need to have the same bundle ID. In my case I have an iOS app with the bundle ID com.company.AppName and a Mac app with the bundle ID com.company.mac.AppName. If I want to use universal purchase in the future, I'll change the bundle ID of the Mac app. Does that mean I'll effectively have two completely separate Mac apps? I'm assuming that my Mac users will have to re-download the app—it won't update automatically due to the new bundle ID. I'm also assuming that the receipts will be separate—the app with the new bundle ID wouldn't show any record of purchases tied to the old bundle ID. Is that correct? From what I gather this is how it has worked with iOS/tvOS universal purchases, so I think my assumptions are correct.Are there any concerns related to different App ID Prefixes?I doubt this will be a concern for most developers, but my iOS app is old enough that it has a unique App ID Prefix that is not the same as the Team ID. When I released my Mac app, the only option was to use my Team ID. Is there anything I need to watch out for if I switch the Mac app to use the same bundle ID as my iOS app? From what I can tell, using the same bundle ID means my Mac app will now have the same App ID Prefix as my iOS app. This has an effect on keychain access groups of course. Do I need to look out for anything else?
10
0
3.6k
Feb ’20
What is the best way to coordinate a price change with an app update?
I'm transitioning an app from paid upfront to free to download with In App Purchases. Those who previously purchased the app will have features unlocked that are not available to those who downloaded the app for free. Since the receipt does not include the purchase price, the only way to detect who paid for the app is based on the version number.Unfortuantely App Store Connect does not have any way to tie a price change to a version update, so I'm trying to figure out the safest way to coordinate this. Here's my tentative plan:Submit the new version for review and set it to "Manually release this version"Once the new version is approved, remove the app from saleRelease the new versionChange the price and make the app available againI'm open to any recommendations on how to ensure this works as well as possible. I have no idea how any of this will work—for example can I actually start the process of releasing an update while the app is removed from sale? If so, is it worth waiting a bit afterward, for that change to propagate, before I make the app available at the new price?
3
0
821
Feb ’20
How can I use conditional code for a new OS version while building with the current SDK?
While iOS 14 and macOS Big Sur 11 are in public beta, I'd like to add some conditional code to work around significant issues. I'm still building my app with the current SDKs because I plan to release updates before the fall, when the new OS versions are released. Some time back, the recommended way to handle this in Objective-C was to check if a class existed before using it: if (NSClassFromString(@"UINewClassName")) Or you could check if a class had been added to an existing method: if ([object respondsToSelector:@selector(newMethodName:)]) { Then at some point we got #available in Swift and @available in Objective-C, which is now the recommended way to handle this: if #available(iOS 14, *) if (@available(iOS 14.0, *)) This works great if you are building your app with the iOS 14 SDK, but from what I can tell the condition is simply ignored if you are building with an older SDK, at least in Swift. I could go back the old NSClassFromString or respondsToSelector: approach, but I would prefer to handle this in Swift if it's possible. I also have some cases where there's not really a new class or method to check for—it's just new behavior I want to accommodate. What's the best way to handle these cases?
3
0
6.4k
Jul ’20
When validating a receipt, what is the correct way to find the most recent transaction for an app with a single subscription?
I'm working on an app that uses subscriptions. It is relatively simple. There is a choice of a monthly or yearly subscription. They are part of the same subscription group, so only one subscription can be active at a time. My app sends the receipt data to my server, which uses the verifyReceipt endpoint to validate the receipt. Aside from checking that the receipt is valid, I’m mainly just interested in one thing: what is the expiration date for the most recent purchase or renewal? So far I’ve mainly worked in the sandbox. I have validated production receipts as well, but those do not contain in-app purchases or subscriptions since I have not yet released an app that includes these. As far as finding the latest purchase, I basically just check the first transaction in latest_receipt_info, which in my testing only ever contains one item. Overall things seem to be working well. While a subscription is active, my app shows the upcoming expiration or renewal date, and I've never noticed any problems with that. After the subscription expires, the app shows a notice explaining when it expired. I've recently noticed this date is not accurate. When this happens, latest_receipt_info contains a single transaction that is not the most recent transaction. I’ve never seen this problem while a subscription is active. Here are the questions I have right now: Will latest_receipt_info actually contain multiple items for an app with a single subscription group? The sandbox only ever returns a one-item array for me. When a subscription is active, it seems to be accurate. When a subscription has expired, it seems to be an effectively random purchase—it’s different on every refresh. If there may be multiple items in latest_receipt_info, are they in chronological order or is it important to check all of them to find the latest expiration date? The documentation for in_app - https://developer.apple.com/documentation/appstorereceipts/responsebody/receipt/in_app makes it clear that the items will not be in chronological order, but the documentation for latest_receipt_info - https://developer.apple.com/documentation/appstorereceipts/responsebody/latest_receipt_info simply says “an array that contains all in-app purchase transactions” which from what I understand is not even accurate. (Elsewhere I’ve heard it’s the 100 most recent transactions, but again I’ve only ever seen one transaction here.) In the sandbox I’ve found that the in_app array contains many transactions that are not included in latest_receipt_info. In some cases, the most recent transaction only appears in the in_app array. I’ve heard from others that latest_receipt_info sometimes contains recent transactions that are not in the in_app array. Do I need to check through all items in both arrays to find the latest expiration date? Or is it safe to trust that latest_receipt_info will contain the correct information in production? Previously my understanding was that I didn't have to worry about the in_app array for my app, but in the sandbox it seems to be the only way to get the latest expiration date. For my first two questions it seems safest to assume that latest_receipt_info may contain multiple items that may not be in chronological order. That's an easy fix. But unless I'm looking at something wrong, it won't address the problem I've run into. So that last bullet point is my biggest question right now. Bonus questions: In the sandbox, server-to-server notifications don’t ever seem to include grace_period_expires_date_ms. I’m checking for that value in unified_receipt.pending_renewal_info[0]. grace_period_expires_date_ms. Is the correct place to look? For an app with a single subscription group, would the pending_renewal_info array ever contain more than one item? I think I'm relatively safe assuming it would not. It's unclear how I would find the most relevant item if I'm wrong.
1
0
2.6k
Aug ’20
WidgetKit and Background App Refresh
The documentation for TimelineProvider suggests using Background App Refresh to keep widget data up to date - https://developer.apple.com/documentation/widgetkit/timelineprovider. This approach would work well for my app. However in my testing so far, it doesn't seem like viewing a widget on the home screen has the same effect on background app refresh that actually opening the app would. That seems like a problem—if the information is right there on the home screen, the app is going to be opened far less often. Has anyone else experimented with this, or does anyone know what the expected behavior is here?
1
0
2.9k
Sep ’20
WidgetKit and shared object instances
I’m trying to figure out the best way to handle some things in my widget. For example, I have a database that all of my widgets get their data from. Ideally I’d like to load and initialize my database once, then query the same database instance from each timeline provider. Is that possible? So far I've tried approaching this in a couple of different ways: I can’t seem to find any shared object in the widget extension. For example, in an app I can call UIApplication.shared from anywhere, or in a watchOS app there’s WKExtension.shared(). From there I can access my custom delegate, which is useful in certain situations. Does a WidgetKit extension have something like this that I'm missing? I thought I might be able to create an instance of the database in the WidgetBundle and then pass it through to each timeline provider. But it seems that a Widget can’t have initialization parameters, so I don’t see how I could pass that object in. I’m guessing this is by design—it seems like Apple wants widget extensions to be super lightweight and do work in the parent app whenever possible. But I’d like to be sure before I make some decisions on how to handle everything. (Especially since I’m also having trouble with doing the work through background app refresh - https://developer.apple.com/forums/thread/659373.)
1
0
1.1k
Sep ’20