It seems that subscription status gives different results with XCode testing and Sandbox testing.
I am using StoreKit2 to implement an IAP of an autorenewable subscription. I want to determine whether the subscription has been cancelled, so that the UI reflects that the subscription will stop after the expiry date and not be renewed.
the 'willAutoRenew' property of the subscription status renewalInfo seems to do exactly what is required, and works fine in XCode testing. My setup is very similar to the StoreKit demo associated with the WWDC21 session available here:
https://developer.apple.com/documentation/storekit/in-app_purchase/implementing_a_store_in_your_app_using_the_storekit_api/
To demonstrate its use, add:
print(renewalInfo.willAutoRenew)
after line 79 of the SubscriptionsView in the demo project. When you run the app, and purchase a Standard Navigation assistance subscription, the console shows 'true'. If you then cancel the subscription in XCode (Debug:StoreKit:Manage Transactions), the console will show 'false' as expected
So far so good. My problem is that when I move to Sandbox testing, and cancel the subscription in another way (eg using the .manageSubscriptionsSheet view modifier, or in Settings:App Store:Sandbox Account), the willAutoRenew property remains true, even though the subscription is in fact cancelled (ie it disappears after the expiry date)
Does anyone know a workaround to determine cancellation status?
Post
Replies
Boosts
Views
Activity
I am using NWBrowser to detect SignalK servers on a network using the following Swift code:
let browser = NWBrowser(for: .bonjourWithTXTRecord(type: "_http._tcp", domain: nil), using: NWParameters())
browser.browseResultsChangedHandler = { results, changes in
print("Found \(results.count) results and \(changes.count) changes")
}
When this is run on a network with 5 devices then the output is often
Found 5 results and 5 changes
But, sometime it is:
Found 2 results and 2 changes
Found 5 results and 3 changes
indicating that the browseResultsChangedHandler is being called more than once.
So my question is how do I determine when the browsing process has finished (obviously without the knowledge that there are 5 devices)?
The depreciated NetServiceBrowser had a delegate method (netServiceBrowser(_:didFind:moreComing:) but I can't see an equivalent for NWBrowser.
The only method I can think of is to apply a short time out.
I have a package stored on iCloud Drive and want to list it as a dependency in another package. This worked fine when the package was stored on the Mac hard drive, but moving it to iCloud seems to cause a problem.
dependencies: [ .package(url: "file:///Users/bill/Library/Mobile Documents/com~apple~CloudDocs/Projects/Matrix", from: "0.0.1") ],
This results in an Xcode error message:
x-xcode-log://812C74F5-B7CA-4E15-A87C-E6C9586B0926 /Users/bill/Library/Mobile%20Documents/com~apple~CloudDocs/Projects/Matrix: The source control operation failed because the specified URL cannot be used. Make sure the URL is valid and try again.
Can anyone advise how to make the URL valid, or is storing the package on iCloud Drive not yet possible?
I have an app with IAP which uses a URLSession object to download files from a server. The download part of the code is:
let request = URLRequest(url: fromURL, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeoutInterval)
let (data, response) = try await downloadSession.data(for: request)
This code has been working without trouble for over a year with thousands of downloads.
Now I have a user with a new iPhone (iOS 17.3.1) which refuses to download, failing at the above code (judging by the high level logs).
My question is this: What sort of things should we be looking at in order to diagnose this issue?
So far we have done the following:
He has no general download issue (eg Safari works fine)
His network access is 'normal' and the problem persists when the network is changed (4G, wifi etc)
He runs a VPN (Nord) but the problem persists when this is off
He has no 3rd party AV software
His phone is not in lockdown mode
Any pointers would be appreciated! NB I have no physical access to his device (yet!)
My app has in-App subscriptions using StoreKit 2 and it works fine for 98% of devices. However, I have some customers whose devices don't seem to be receiving transactions (one with a iPhone 13 running 16.5), while his wife's identical phone IS receiving them. They both share a working Internet connection.
I appreciate that there is not a lot of detail here, but in principle, are there any iOS settings, other apps, network settings etc which could prevent transactions arriving?
I am trying to create a struct which contains a value along with an 'expiry time'. I want to use a struct rather than a class so it can be set as a @Published property in an Observable object. Here is the code:
struct MarineDatum {
var value: Double? {
didSet {
isNew = true
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 5, repeats: false, block: { timer in self.makeOld()})
}
}
var isNew: Bool
var timer: Timer?
init(value: Double) {
self.value = value
self.isNew = true
}
mutating func makeOld() {
self.isNew = false
}
}
This code does exactly what I want, were it to actually compile, but I get an 'Escaping closure captures mutating 'self' parameter' error in the timer block.
Can anyone think of a way around this without creating an external variable to represent the timer object?
I have a large number of protocol compliant types which I am processing using an array, something like this:
protocol Thing {
func multiply(_ n: Int) -> Int
}
struct One: Thing {
func multiply(_ n: Int) -> Int { n * 1 }
}
struct Two: Thing {
func multiply(_ n: Int) -> Int { n * 2 }
}
var thingArray: [Thing] = []
let name = "Two"
switch name {
case "One":
thingArray.append(One())
case "Two":
thingArray.append(Two())
...
default:
()
}
This code works fine, but the problem is that there will ultimately be a large number of compliant types so the switch block will become large.
My question is this: Is there a way in Swift of assigning the protocol based on the value of a variable? something like this:
thingArray.append(Struct(name)())
It would be nice to replace the entires switch block with a single line.
I am using Storekit 2 and SwiftUI in my App and am testing it with 'Larger Accessibility Sizes' on and the largest text size selected.
When I call the .purchase method of a Product object I get the Purchase confirmation screen followed by the prompt to enter my password as expected.
The problem is that with higher text sizes and small device screens, the password box is not visible, not can it be scrolled up:
There does not seem to be a way of accessing the password screen to control its layout. I have tried unsuccessfully wrapping the request in a view with the modifier:
dynamicTypeSize(...DynamicTypeSize.accessibility1).
Has anyone else seen this behaviour? Is it a bug that I should report?
I am trying to add a custom ProgressViewStyle to my project using the following code:
struct HorizontalProgressViewStyle: ProgressViewStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 8) {
ProgressView()
.progressViewStyle(.circular)
configuration.label
}.foregroundColor(.secondary)
}
}
This code complies and functions in a new Multiplatform app project. However, when I copy the file into my existing (large) project I get the errors shown in the attached image.
I have no idea how to start debugging this, and the project is too large to start eliminating other files/changing settings.
I am using Xcode 14.0.1 and the project's minimal deployments is iOS 15.0
Any thoughts would be appreciated!
I am using StoreKit2 to offer an auto-renewable subscription in an iOS app, and have observed inconsistent results from the currentEntitlement function during sandbox testing
To illustrate the issue I have created a function which prints out a timestamped entitlement, as well as subscription status using the following code.
@MainActor func printSubscriptionInfo() async {
let productId = "com.mycompany.autorenewablesubscription.subscription.id"
print()
print(Date.now)
// Current entitlement
if let currentEntitlement = await Transaction.currentEntitlement(for: productId) {
if case .verified(let transaction) = currentEntitlement {
print("Purchase date: \(transaction.purchaseDate)")
print("Expiry date: \(transaction.expirationDate!)")
} else {
print("Transaction not verified")
}
} else {
print("No entitlement for \(productId)")
}
// Subscription information
let product = (availableProducts.filter { $0.id == productId }).first!
guard let statuses = try? await product.subscription?.status, let status = statuses.first else {
print("Unable to get subscription status")
return
}
print("Subscription status: \(status.stateDescription)")
}
The following shows the output of this function at various time points after a purchase, as well as the associated server notifications received.
Purchase completed: at 12:23:20
Function output at 12:23:40:
Purchase date: 12:23:09
Expiry date: 12:26:09
Subscription status: subscribed
Server notifications:
12:23:13 - INTERACTIVE_RENEWAL
12:25:20 - INITIAL_BUY
Function output at 12:26:18:
No entitlement for com.mycompany.autorenewablesubscription.subscription.id
Subscription status: subscribed
Server notifications:
12:28:26 - INITIAL_BUY
Function output at 12:31:03
Purchase date: 12:29:09
Expiry date: 12:32:09
Subscription status: subscribed
The output of the function called after the first subscription renewal (ie at 12:26:09) shows no current entitlement, even though the subscription status is correct and there are no server notifications indicating expiry.
After the next expiry period has passed, the function returns correct results once more. Has anyone else noticed this behaviour?
I am trying to download app content from a password protected directory of a website served by Apache24.
The directory is protected using the following configuration segment:
<Directory "<directory path">
AuthType Basic
AuthName "Restricted Content"
AuthUserFile <password file path>.htpasswd
Require valid-user
</Directory>
Here is my swift code (running on latest betas of iOS15 or macOS12)
class Downloader: NSObject {
lazy var downloadSession: URLSession = {
// Setup configuration
let configuration = URLSessionConfiguration.default
configuration.allowsCellularAccess = true
configuration.timeoutIntervalForResource = 60
configuration.waitsForConnectivity = true
// Add authorisation header to handle credentials
let user = "*****"
let password = "******"
let userPasswordData = "\(user):\(password)".data(using: .utf8)
let base64EncodedCredential = userPasswordData!.base64EncodedString(options: Data.Base64EncodingOptions.init(rawValue: 0))
let authString = "Basic \(base64EncodedCredential)"
// Add authorisation header to configuration
//configuration.httpAdditionalHeaders = ["Authorization" : authString]
return URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}()
// Download file using async/await
func downloadAsync(subpath: String) async throws {
let request = URLRequest(url: URL(string: "https://<server>/")!)
let (data, response) = try await downloadSession.data(for: request)
guard (response as? HTTPURLResponse)?.statusCode == 200 else { throw HTTPError.withIdentifier((response as! HTTPURLResponse).statusCode) }
print(String(data: data, encoding: .utf8))
}
}
let downloader = Downloader()
Task.init {
do {
try await downloader.downloadAsync(subpath: "<filename>")
} catch {
print("Unable to download file")
}
}
As expected, if I run the code as is (with the authorisation header commented out) it does not download the file
As expected, if I then uncomment the authorisation line, and run it again, it DOES download the file
Here is the unexpected part (to me!):
If I re-comment out the authorisation line, and run it again it STILL downloads the file
This can be repeated for several minutes, before it finally refuses to download the file
The issue occurs on both iOS and macOS
There is a clear gap in my understanding here about what is going on, so my questions are:
What is causing this behaviour? A session cookie on the client, or something on the server?
Does it represented a security risk? (Could another client without credentials download the file shortly after a legitimate download)
If the answer to 2 is YES, how do I stop it?
Many thanks,
Bill Aylward