Post

Replies

Boosts

Views

Activity

Calling AppTransaction in a SpriteKit app
Hello 👋! I'm trying to switch the business model in my app from premium to freemium, gracefully so that existing users aren't bothered. I'd like to provide newly-paywalled content for original paid users. For this to work, I need to use originalAppVersion in AppTransaction, and support iOS 16 at minimum. I've asked about originalAppVersion earlier here. I've upped to iOS 16 already, but I don't exactly know how to call AppTransaction.shared to get originalAppVersion. The issue lies in the fact that my app is based on SpriteKit, so the operative areas I have available to call AppTransaction are ostensibly limited to AppDelegate and GameViewController. I'm using the code from Supporting business model changes by using the app transaction, but if I place it in either GameViewController or AppDelegate, for example in application(_:didFinishLaunchingWithOptions:) or viewDidLoad(), I get an error concerning async/await. Now, I understand the gist of it: these are not asynchronous methods. So I'm trying to understand how to do it perhaps outside of these methods. How do I call AppTransaction.shared (and fetch originalAppVersion) in a SpriteKit based app?
0
0
89
1w
AVAudioPlayer/SKAudioNode audio no longer plays after interruption
Hi 👋! We have a SpriteKit-based app where we play AVAudio sounds in three different ways: Effects (incl. UI sounds) with AVAudioPlayer. Long looping tracks with AVAudioPlayer. Short animation effects on the timeline of SpriteKit's SKScene files (effectively SKAudioNode nodes). We've found that when you exit the app or otherwise interrupt audio plays, future audio plays often fail. For example, there's a WebKit-based video trailer inside the app, and if you play it, our looping background music track (2.) will stop playing, and won't resume as you close the trailer (return from WebKit). This is probably due to us not manually restarting the track (so may well be easily fixed). Periodically played AVAudioPlayer audio (1.) are not affected. However, the more concerning thing is that the audio tracks on SKScene file timelines (3.) will no longer play. My hypothesis is that AVAudioEngine gets interrupted, and needs to be restarted for those AVAudioNode elements to regain functionality. Thing is, we don't deal with AVAudioEngine at all currently in the app, meaning it is never initiated to begin with. Obviously things return to normal when you remove the app from short-term memory and restart it. However, it seems many of our users aren't doing this, and often report audio failing presumably due to some interruption in the past without the app ever being cleared from memory. Any idea why timeline-run SKAudioNodes would fail like this? Should the app react to app backgrounding/foregrounding regarding audio? Any help would be very much appreciated ✌️!
0
0
49
May ’25
Business model change confusion: premium -> freemium (originalAppVersion)
Hi 👋! I want to switch the business model in my app from premium to freemium and do it gracefully for existing users. Essentially, I wish to provide newly-paywalled content for free to existing paid users (people who bought the original app). It seems clear that I should be using appTransaction's originalAppVersion property to check against purchases made in a previous version of the app, per the documentation. However, there seems to be broad confusion over whether originalAppVersion returns the version number or the build number and how to test for it. Examples of confusion can be found here, here and here. This lack of clarity seems especially dangerous due to the difficulty in testing these values. In the sandbox originalAppVersion returns 1.0 by default, so whether you design for version number or build number, you'll always return a positive as long as your value is more than 1. There is a real risk to unknowingly either never identify previous premium users or accidentally identify everyone as premium (essentially giving away your app for free). For example, my app's current version number is 1.4.0 and build number is 18, so 1.4.0 (18). As this is a major change, for this new update I might as well go for version number 2.0.0, and let's say I release the app with build number 5, so 2.0.0 (5). If I expect originalAppVersion to return the version number, I would match it against 2, because anything before 2.0.0 needs to be marked as premium. However, if I expect the build number, I should check against 19 and respectively bump up my build number: 5 -> 19. In the standard version/build "v.v.v (b)" format, does originalAppVersion return app version or app build? If it indeed does return build, and not version, I guess I'll start all of my future build numbers from 100 just in case: 2.0.0 (100). The only way I imagine I can test this is to print the value on the visual interface in a live version of the app, and ask a random user 🤷‍♂️.
3
0
318
Mar ’25
Trouble decoding array objects via NSKeyedArchiver / NSSecureCoding
Hiya 👋! While loading some data, I'm having issues decoding arrays of basic types, specifically Int and Bool – they return nil. My app has existing data (live on the App Store for years) that is saved and loaded using NSKeyedArchiver. I'm updating to support a newer iOS version, which requires me now to adhere to NSSecureCoding. As I understand, NSSecureCoding needs strict type definitions, and it will return nil if things are ambiguous (for security reasons). Essentially as I load data, it all works when I use strict types, like Int and Bool, because the methods themselves are strict: decodeInteger(forKey:) and decodeBool(forKey:). However, if I want to decode something more complex, like NSDate or basic type arrays (in my case [Int], [[[Int]]], [Bool] and [[Bool]]), I assume I have to decode an object: decodeObject(of:forKey:). While I was able to make NSDate work by forcing the type (decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date), getting arrays to decode is proving difficult. They always return nil. I've now tried forcing the type to different arrays, including NSArray, and listing array types. I also tried decodeArrayOfObjects(ofClass:forKey:), but no luck. Example: Here are some data items I might have: var id: Int? var isModified: Bool? var modifyDate: Date? var myIntegers: [Int]? var myEmbedIntegers: [[[Int]]]? var myBooleans: [Bool]? var myEmbedBooleans: [[Bool]]? Here's how I encode them: encode(id!, forKey: "id") encode(isModified!, forKey: "isModified") encode(modifyDate!, forKey: "modifyDate") encode(myIntegers!, forKey: "myIntegers") encode(myEmbedIntegers!, forKey: "myEmbedIntegers") encode(myBooleans!, forKey: "myBooleans") encode(myEmbedBooleans!, forKey: "myEmbedBooleans") This is how Int, Bool and Date are apparently successfully decoded (where Date is forced to search for NSDate type): decodeInteger(forKey: "id") decodeBool(forKey: "isModified") decodeObject(of: NSDate.self, forKey: "modifyDate")! as Date Here are attempts with Int (same with Bool) arrays, which are erroneously decoded (these all either get compiler errors or return nil): decodeObject(forKey: "myIntegers") as! [Int] // This used to work before NSSecureCoding, but now returns nil. decodeObject(of: [NSArray.self], forKey: "myIntegers") as! [Int] // Returns nil. decodeObject(of: [Int.self], forKey: "myIntegers") // Compiler error about value type conversion. decodeArrayOfObjects(ofClass: Int.self, forKey: "myIntegers") // Compiler error complaining that Int doesn't conform to NSSecureCoding. decodeArrayOfObjects(ofClass: NSArray.self, forKey: "myIntegers") as! [Int] // Returns nil. The funny thing is I don't even remotely need security. My data is for song compositions in an entertainment app, so it's strictly loaded and saved to device by my own code without networking, hashing or anything else being involved. Nevertheless I'm now stuck on this 😥. How do I decode arrays (without returning nil)?
4
0
441
Dec ’24