Repository navigation
Use iOS 15 and Swift 6.2 APIs now that they're the floor - #536
Conversation
Pins current behaviour before moving these onto iOS 15 and Swift 6.2 APIs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Core Data reads use the async perform, and a computed property reads the placement's date on the context's queue instead of after it. - The dev server probe uses URLSession's async data(for:). - Window lookups use UIWindowScene.keyWindow, and test mode reuses the existing top view controller helper instead of the deprecated windows list. - Superscript is imported internally so it stays out of the SDK's interface. - Drop Swift and compiler version branches below 6.2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No critical issues. One loose test assertion is flagged inline.
Reviewed changes
I reviewed both commits: the tests-first commit and the switch to iOS 15 / Swift 6.2 APIs.
- Async Core Data reads:
CoreDataStack.count(for:)and the newgetLastSavedPlacementDate(name:before:)return their values from the asyncNSManagedObjectContext.perform.createdAtis now read insideperforminstead of on the caller's thread, which fixes a real threading bug. - Dev server probe:
loadWithURLSessionnow callsURLSession.data(for:)and behaves the same as before (a 404 still comes back as a response, an ATS failure still has a readableNSError.code). It's also internal now so the tests can call it, and cancelling the task now cancels the request. - Window lookup:
activeWindowusesUIWindowScene.keyWindow. The test mode alerts usetopMostViewController, which still finds the presentation window thatGetPresentermakes key. internal import Superscript: the only two source files that import it are internal-only and nothingpublicor@inlinableexposes Superscript types.- Version reporting: removed the branches below 6.2. In the Swift 5 language mode,
currentSwiftVersionstill returns"5.10", the same value the old chain gave. - New tests: added computed-property tests on the in-memory stack,
URLProtocolstub tests for the loader, and a version floor test.
Claude Opus | 𝕏
| withExtendedLifetime(dependencyContainer) { | ||
| #expect(["5.10", "6.2"].contains(deviceHelper.currentSwiftVersion())) | ||
| let compilerVersion = deviceHelper.currentCompilerVersion() | ||
| #expect(compilerVersion.compare("6.2", options: .numeric) != .orderedAscending) |
There was a problem hiding this comment.
This assertion passes even when the function falls through to "Unknown". With .numeric, only runs of digits are compared as numbers, so "Unknown".compare("6.2") compares 'U' with '6' and returns .orderedDescending. So the test can't catch the #else branch it's meant to guard against. Also check that the value starts with a digit.
| #expect(compilerVersion.compare("6.2", options: .numeric) != .orderedAscending) | |
| #expect(compilerVersion.first?.isNumber == true) | |
| #expect(compilerVersion.compare("6.2", options: .numeric) != .orderedAscending) |
| purchaseSource: PurchaseSource | ||
| ) async -> PurchaseResult { | ||
| guard let viewController = topViewController() else { | ||
| guard let viewController = UIViewController.topMostViewController else { |
There was a problem hiding this comment.
If an app has multiple connected scenes, the first foreground-active scene can have a window but no key window while another scene has the key window. The new controller lookup uses that first scene’s window instead of the key window the old lookup would have used. The purchase drawer—or the restore drawer, which uses the same lookup—can then appear in the wrong scene or fail to appear.
Prompt To Fix With AI
This is a comment left during a code review.
Path: Sources/SuperwallKit/TestMode/TestModeTransactionHandler.swift
Line: 28
Comment:
**Wrong window for test drawers**
If an app has multiple connected scenes, the first foreground-active scene can have a window but no key window while another scene has the key window. The new controller lookup uses that first scene’s window instead of the key window the old lookup would have used. The purchase drawer—or the restore drawer, which uses the same lookup—can then appear in the wrong scene or fail to appear.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.The landscape check reads the active scene instead of UIApplication.windows, and a review request with no window of its own falls back to the active scene. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No new issues in this commit. The loose compiler-version assertion flagged in the last review is still open.
Reviewed changes
I reviewed dd25776, which moves the last two call sites off the app-wide window list and the scene-less review API.
- Landscape check:
UIWindow.isLandscapenow readsactiveWindowScene?.interfaceOrientationinstead of the first window in the deprecatedUIApplication.windows.activeWindowSceneis ordered foreground active, then foreground inactive, then any scene, so it's at least as accurate as picking an arbitrary first window. Its only caller isShimmerView. - Review request: if the paywall has no window,
requestReview(.inApp)now usesactiveWindowSceneinstead of callingSKStoreReviewController.requestReview()with no scene. It's also one branch for both platforms now instead of two copies behind#if os(visionOS). If there's no scene at all, nothing is requested buttrackReviewRequeststill fires. visionOS already behaved this way, and a foregrounded app always has a connected scene.
Claude Opus | 𝕏

Follow-up to #532, stacked on it. Now that the SDK needs iOS 15 and Swift 6.2, this swaps a few hand-rolled workarounds for the built-in APIs.
Changes
CoreDataStack.countand the last-placement lookup use iOS 15's asyncNSManagedObjectContext.performand return their value directly, removing five continuation wrappers inCoreDataManager. The last-placement lookup now returns the placement'screatedAtread insideperform. Before, it handed the managed object back and read the date on another thread, which Core Data doesn't allow.DevServerLocator.loadWithURLSessionusesURLSession.data(for:)instead of wrappingdataTask.activeWindowusesUIWindowScene.keyWindow. The test mode purchase and restore alerts use the existingUIViewController.topMostViewControllerinstead of their own copy built on the deprecatedUIApplication.windows.UIWindow.isLandscapereads the active scene instead of the first window inUIApplication.windows, which iOS 15 deprecated.SKStoreReviewController.requestReview(), which iOS 14 deprecated.internal import, so it's no longer part of SuperwallKit's interface.import Combineto two loading listeners that usedPublished.Publisherwithout importing it (this was already a warning).Tests
The tests went in first, in their own commit, and passed against the old code before any change:
CoreDataManagerComputedPropertyTests:getComputedPropertySincePlacementhad no tests. It now covers no placement, latest placement, other names, and skipping the placement currently being evaluated.DevServerLocatorURLSessionTests: runs the realURLSessionloader against a stubURLProtocolthat only answers a made-up host. Covers success, a 404 still coming back as a response, an empty body, and a connection error throwing.DeviceHelperTests.swiftAndCompilerVersions_matchTheToolchainFloorpins the values the version functions can still return.The existing ~20
countPlacement/countAudienceOccurrencestests cover the count path. The window lookups, landscape check, review request andinternal importcan't be unit tested because the test target has no host app, so for those the build is the check.Full suite: 1,319 of 1,320 passed. The one failure was
ProductsFetcherSK2Tests' non-existent-product test, which calls the real App Store; it passed on rerun.Checklist
CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.swiftlintin the main directory and fixed any issues.No changelog entry: nothing here changes behavior for apps.
🤖 Generated with Claude Code
The PR should not merge until test-mode purchases and restores reliably select the presenting window in multi-scene apps.
Findings
Fix with agent prompt
Summary
The PR replaces continuation-based Core Data and URLSession operations with async APIs, updates window and test-mode controller lookup, narrows Superscript imports, simplifies version reporting, and adds regression tests. The test-mode controller replacement needs attention in multi-scene apps.
Reviews (1) · Last reviewed commit: "Use iOS 15 and Swift 6.2 APIs now that t..."