Post

Replies

Boosts

Views

Activity

Automatic build numbering
Dear Experts, What is the current best practice for build numbering? I ask because I am aware that when exporting an archived app for upload I am now asked if I want Xcode to manage the build number for me. In the past I have set a build number in the Info.plist, often based on the date, e.g. 2.4.20230218. Now, I am asked if I want that to be replaced by something like 2.4.7 (if I recall correctly). So far I've answered No. Previously I have searched for automatic build numbering and I've found web pages that suggest adding a script to the build process that automatically increments the build number in the info.plist, or similar. I've not tried to implement that myself. In some of my apps, I have code that needs to know the full version at compile time (e.g. app receipt validation). How would that interact with the new feature? Thanks, Phil.
1
1
1k
Jun ’25
Time zone for PHAsset creationDate
Dear Experts, PHAsset.creationDate is an NSDate, which does not have a timezone associated with it, right? Consider a photo viewer app. If I take a photo of the sunrise at 0600 local time while I am away, when I get home and view the photo in the app, I believe I want the timestamp shown with the photo to be 0600. Do you agree? But NSDate is just a time-point, and I don't think Foundation (or anything else in iOS) has a type that combines a time-point with a time zone. Nor does PHAsset have any other useful attributes - unless I were to determine the time zone from the location! Am I missing anything?
3
1
1.6k
Nov ’23
"Required Reason" API - stat()
I've just been looking at this list of APIs for which we will be soon be required to declare a "required reason" in the app's privacy manifest: https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api One of the listed functions is stat(). The rationale seems to be that a malicious app can use stat to get the timestamps of files outside the app container, thereby "fingerprinting" the device. The allowed reasons that we can declare are : To get timestamps that are displayed to the user. To get timestamps of files that are within the app's container. To get timestamps of files that the user has granted access to. I am concerned that this does not include many of the legitimate non-timestamp uses of stat(). For example, it can be used simply to test if a file exists, or to test whether a path refers to a file or a directory, or to check if two paths refer to the same file (e.g. via different symlinks), or to get the size of a file. Some of these things can be achieved in other ways; for example, I can check if a file exists by trying to open() it and checking for an error, and I can get the file size by opening it and calling lseek(SEEK_END). Maybe I can check if two paths are equivalent by using readlink() to form canonical paths for both and comparing them. But I bet there are other things that can't be done. I could probably fix all of my code to not call stat() for non-timestamp reasons in a few hours. It would be more difficult to fix the various open-source libraries that I use. What do you think we should all be doing?: "File a bug" asking for an additional reason for using stat(), i.e. to get non-timestamp information about files in the app's container. Deliberately mis-read allowed reason C617.1, "to access the timestamps of files inside the app container", as " to access the timestamps and other metadata of files inside the app container", and declare that in the privacy manifest. Change code to not call stat(). Any other suggestions? P.S. I guess that libc++ std::filesystem calls stat(). What is the status of using that? The std::filesystem functions that access file timestamps are not listed on the page linked above. If I call std::exists() to check if a file exists, and assuming that is implemented using stat(), will that trigger the new filter?
11
1
3.7k
Dec ’23
NSDateFormatter ignores user's preferred date format
NSString* fmt = [NSDateFormatter dateFormatFromTemplate: @"yyyy-MM-dd HH:mm:ss" options: 0 locale: [NSLocale currentLocale]]; My understanding is that this should rewrite the format string to something that reflects the users current settings. When I run it, it does seem to reflect the "24-Hour Time" setting under General -> Date & Time. But it doesn't reflect the "Date Format" setting under General -> Language & Region. It seems that the date format is always the default for the region set in General -> Language & Region -> Region. It also seems to insert an unexpected comma. Specifically: Region=US, fmt="MM/dd/yyyy, h:mm:ss a" Region=UK, fmt="dd/MM/yyyy, h:mm:ss a" irrespective of the "Date Format" setting. What's going on?
4
1
629
Nov ’23
Do I still need "iOS 17.0" support, now that my device is running 17.1.2?
I have been trying to tidy up my Mac's storage which is getting full. (See also my other question about "bridgeOS".) In Settings -> General -> Storage -> Developer there were numerous multi-gigabyte items for different versions of iOS. Most of them were for point releases that I no longer have on my devices, which auto-update. For example I think I had 17.0, 17.1.1 and 17.1.2; my dev iPad and iPhone are both now running 17.1.2, so I deleted the others. But now when I try to run, XCode complains that "iOS 17.0 is not installed". Does this mean that iOS 17.0 device support is required in addition to iOS 17.1.2, in order to run on a 17.1.2 device? This would make sense if it just said "iOS 17", rather than "iOS 17.0".
4
1
2.4k
Dec ’23
AppAttest generateAssertion returns DCErrorInvalidInput
I am seeing DCErrorInvalidInput returned from DCAppAttestService generateAssertion: in production. Can anyone suggest what might cause this, and what I should do in response? The documentation says of this error code: "An error code that indicates when your app provides data that isn’t formatted correctly.: The only input to the method is the key ID and the data hash. I generate the hash with CC_SHA256() and then put the bytes in an NSData. I don't think much can go wrong with that, though I can't see exactly what is being passed in my diagnostics. There is another error response, DCErrorInvalidKey which I handle separately. I am wondering if problems with the key ID are being reported as "invalid input" rather than "invalid key". I can see the key ID in my diagnostics and it looks legitimate, i.e. it's 32 random-looking bytes, base64-encoded. Suggestions anyone?
1
1
1k
Jan ’24
AppAttest attestKey returns invalid key error
Dear Experts, I have App Attest deployed in an app that is currently in TestFlight. Its works OK most of the time. For one particular user, however, attestKey fails with DCErrorInvalidKey for a new key that it has just created. I have some insight into what the app is doing because I send diagnostics to the server. It seems that for this user, the sequence of events is: Initially the app has no key ID saved. The user initiates an action that requires App Attest-signed communication with my server. The app calls generateKey which seems to succeed. The app fetches a challenge from the server. The app calls attestKey. attestKey returns DCErrorInvalidKey. The app doesn't save the key ID persistently, so next time the same thing happens. attestKey really shouldn't fail with the invalid key error for a key that it has just created, should it? What could be going on here?
7
2
2.6k
Jun ’24
DSA compliance phone verification fails
Has anyone successfully completed DSA compliance phone number verification using the "receive a phone call" option? It seems that Apple first tries to send an SMS. You then have the option to resend another SMS, to receive a phone call, or to upload documents. When I choose to receive a phone call, it seems that the Apple system calls me but it hangs up without reading out a code. Has anyone got this to work?
4
0
1.4k
Jun ’24
Does SubscriptionStoreView .storeButton(for:.policies) work?
I've added .storeButton(.visible, for:.policies) to my SubscriptionStoreView, and the buttons do appear, but when I tap on them I get a sheet that just says "Terms of Service Unavailable / Somethng went wrong. Try Again.". (similar for Privacy Policy). Is this expected in development? Will these start working correctly in production? (and, more importantly, in App Review?) The docs say that these use the values (i.e. URLs) set in App Store Connect, but that I can override those. This is a new app. Is that wrong, do I need to set the URLs explicitly? Edited to add: the console reports: Failed to fetch terms of service and privacy policy: Error Domain=NSURLErrorDomain Code=-1011 "(null)"
6
1
951
Jul ’24
Why I am seeing "Grace Period" events for my new subscription app?
Dear All, I've recently released a new app with a subscription. It has a 3-day free trial and a 1-year subscription, and I've enabled a grace period of 16 days, but the grace period is enabled only for paid-to-paid renewals. As it's a new app, no-one has reached the end of their subscription yet; there will not have been any paid-to-paid renewals. Yet I see a small number of "enter grace period" and "renewal from grace period" events reported in App Store Connect. Can anyone explain why this could be?
3
1
675
Sep ’24
"Too many redirects" on forum
Anyone else seeing this? I'm sometimes getting "too many redirects" errors from Safari when trying to view this forum: It goes away after a while, randomly. Also: this is hard to debug; the Network tab in the Safari Javascript Console only shows the final load of the error page, not the failure. Is there a way to see the actual redirect loop URLs?
3
1
589
Feb ’25
How to update in-app purchase prices using the app store connect API
Documentation for the App Store Connect API is poor, especially in comparison to the good documentation for the now-defunct XML-based "transporter" API. In the hope that it will be useful to others trying to do this in the future, here is how I was able to do a bulk update of my in-app purchases' prices using the API. Step 1: get the IDs for the IAPs, if you don't already know them: GET v1/apps/$app/inAppPurchasesV2 If you have a lot of IAPs, follow any links/next URL to get subsequent pages. The ids are in data/id . Step 2: get the current prices, if you don't already know them. I believe you need to do a separate request for each IAP (right?). GET v1/inAppPurchasePriceSchedules/$iap/manualPrices?include=inAppPurchasePricePoint,territory The price and currency are in included/attributes/customerPrice and included/attributes/currency (I generally only have one "manual" price). Step 3: look up the available price points: GET v2/inAppPurchases/$iap/pricePoints?filter[territory]=$territory The prices and IDs are in data/attributes/customerPrice and data/id. Note this query takes the specific IAP ID. I don't know why. Are the price points specific to the IAPs? Can I reuse a price point ID that I've looked up for one IAP with another IAP for the same app? Step 4: choose your new prices. Step 5: Submit the new prices: POST v1/inAppPurchasePriceSchedules { "data" : { "relationships" : { "baseTerritory" : { "data" : { "id" : "$territory", "type" : "territories" } }, "inAppPurchase" : { "data" : { "id" : "$iap", "type" : "inAppPurchases" } }, "manualPrices" : { "data" : [ { "id" : "$random_id", "type" : "inAppPurchasePrices" } ] } }, "type" : "inAppPurchasePriceSchedules" }, "included" : [ { "attributes" : { "startDate" : null, "endDate" : null }, "id" : "$random_id", "relationships" : { "inAppPurchasePricePoint" : { "data" : { "id" : "$price_point_id", "type" : "inAppPurchasePricePoints" } }, "inAppPurchaseV2" : { "data" : { "id" : "$iap", "type" : "inAppPurchases" } } }, "type" : "inAppPurchasePrices" } ] } In that, $iap is the IAP ID from step 1, $territory is probably a three-letter string like GBR, $random_id is a random identifier that you generate (using the same value in the two places) (I'm not sure what the scope of this is; do I have to check that I don't accidentally send the same value in the future, or does it only exist while this submission is processed?), and $price_point_id is the ID for the price point from step 3. I believe it is necessary to send a separate submission for each IAP (right?) That example makes the change immediately (start and end dates are both null). Note that if you want to schedule a future change, you need to include both the current period and price and the future period and price in the submission. I would like to thank @Efun whose posts in this thread: https://developer.apple.com/forums/thread/727159 helped a lot with understanding this.
0
2
2.1k
Jun ’23
Identifying "required reason" API call locations from app binary
Dear Experts, I've just received the exciting new email from App Store Connect telling me that I'm using a "required reason" API call and need to declare it in my privacy manifest. Of course this is easy to fix, I'll just add the code to my privacy manifest - but I thought I'd at least go through the motions of trying to work out what function I am calling and from where. First issue is that the email just tells me that the app "references one or more APIs that require reasons ... including NSPrivacyAcceeedAPICategoryFileTimestamp". Dear Apple, why on earth can't you actually tell me the specific function that I am calling? (FB13689896). So let's see if I can work out what has been detected. I look at the app binary: % objdump --syms App.app I think that is probably more or less what App Review must get from their scan, right? So I can see _stat in there but it doesn't know the corresponding source file. So I go to the build directory with the object files and extract symbols from them all individually, using objdump --syms. Provided that I've not enabled link-time optimisation that works and I can find ... zero calls to stat(). Which tells me that my C++ std::filesystem calls have not been detected! Interesting. So if you want to bypass this amazing new privacy technology, I guess that's the way to go. Anyway if there's a call to stat() in the binary but not in the object files, it must be coming from one of my .a files. That's a bit more difficult to track down as (1) my .a files are not in a convenient single directory, and (2) they may have calls to stat() in archive members that aren't needed and aren't included in this binary. So the question: is there some convenient way to take the binary and identify which object files or static library archive members resulted in which of its UND symbols?
12
2
4.1k
Apr ’24
Acceptable location purpose strings
Does anyone have recent experience of what App Review consider acceptable for location purpose strings these days? My map apps simply display a blue spot on the map showing your current location when you turn on the app's location button. That data doesn't leave the app; I'm not selling it to anyone, or doing anything nefarious. For years, I've had concise location purpose strings such as "Your location will be shown on the map". Now, App Review seem to find that inadequate. They say: "One or more purpose strings in the app do not sufficiently explain the use of protected resources. Purpose strings must clearly and completely describe the app's use of data and, in most cases, provide an example of how the data will be used. Next steps: Update the ... location purpose string to explain how the app will use the requested information and provide an example of how the data will be used. " I've just look at what Apple Maps uses as its purpose string, and it's just "Your location is used to show your position on the map, get directions, estimate travel times, and improve search results." I'm only doing the first of those things, so surely "Your location is used to show your position on the map" would get approved, right? Wrong! I have similar issues with the photos purpose string.
4
2
1.6k
Jun ’24