Conversation
Apple has required the iOS 26 SDK for App Store Connect uploads since April 28, 2026, so no shipping app can build a new SDK release with an older Xcode. Raise swift-tools-version to 6.2 (keeping the Swift 5 language mode), the iOS deployment target to 15, and macOS to 12, the oldest the 6.2 manifest supports. Drop the compiler checks that are now always true, the Swift < 5.7 StoreKit product workaround, and the Xcode 12 script. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With iOS 15, macOS 12 and watchOS 8 as the floor, every @available/#available check at or below them is always true. Drop them, their else branches (StoreKit 1 default on iOS 13/14, SecTrustEvaluate, action-sheet menus, pre-iOS 14 photo permission APIs), the now-unused action sheet helpers, and the matching test guards. Also guard keyboardDismissMode, which is unavailable on visionOS and broke that build. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
📚 Docs requiredWhat changed for customers: SuperwallKit 4.18.0 needs iOS 15+ (was iOS 13) and Xcode 26 / Swift 6.2 to build (was swift-tools 5.5). SwiftPM also needs macOS 12+ and watchOS 8+. On every supported OS, Why this needs docs: Customers set their deployment target and toolchain against these supported versions (rule 6), and the new floor is a prerequisite they must meet before upgrading (rule 4). The docs also still describe a StoreKit 1 fallback on older iOS versions, and that default has changed (rule 3). Coverage today:
Without a docs page this change also gets no changelog entry: Prompt for the docs agent. Run in
|
There was a problem hiding this comment.
Important
The code cleanup is thorough and correct — a full sweep of Sources/ finds no remaining @available/#available below the new floor. Two things to settle before merging: the version number chosen for a breaking deployment-target raise, and a dead-code leftover in the Mac Catalyst camera path.
Reviewed changes — both commits on chore/min-ios15-swift62, covering the manifest/toolchain bump and the 71-file availability cleanup.
- Toolchain floor —
swift-tools-version5.5 → 6.2 withswiftLanguageModes: [.v5]pinned, so strict concurrency stays off; the Superscript dependency moves to theexact:label form required by the 6.2 manifest API. - Platform floor — iOS 13 → 15, macOS 10.12 → 12, watchOS 6.2 → 8, kept in sync across
Package.swift,SuperwallKit.podspec,project.ymlandproject.pbxproj. - Compiler-gate removal —
#if compiler(...)checks below 6.2, theswift(<5.7)Any-boxing workaround inSK2StoreProduct, andxcode12.pywith its// ignore-xcode-12markers are gone;compiler(>=6.3.2)gates for the iOS 26.4 StoreKit APIs are correctly retained. - Availability cleanup — every
@available/#availableat or below the new floor, plus the fallback branches only reachable on older OSes: StoreKit 1 default on iOS 13/14, theSecTrustEvaluatereceipt path, pre-iOS-14 photo permissions, and the Test Mode action-sheet fallbacks forUIMenubuttons. - Small fixes —
interfaceTypehandles.visioninside the switch, andkeyboardDismissModein the debugger picker is now#if !os(visionOS)-guarded. - Release plumbing — 4.17.0 → 4.18.0 across
Constants.swift, the podspec andCHANGELOG.md, plus a new "Minimum Toolchain and Platforms" section inCLAUDE.md.
I verified the manifest parses (swift package dump-package reports ios 15.0 / macos 12.0 / watchos 8.0, swiftLanguageVersions: ["5"], toolsVersion 6.2.0) and that no CI workflow pins an Xcode below 26 — every macOS job is macos-26 with latest-stable.
⚠️ A breaking deployment-target raise is shipping as a minor version
4.17.0 → 4.18.0 for a change whose own CHANGELOG.md entry is filed under "### Breaking Changes". SwiftPM's default requirement is .upToNextMajor, so every integrator pinned at from: "4.x" resolves into this release automatically — and any of them still targeting iOS 13 or 14 gets a hard resolution failure (requires minimum platform version 15.0) from a package update they didn't ask for. A major tag is the mechanism that stops that. Repo precedent agrees: the last minimum-iOS raise shipped in 3.0.0, also under "### Breaking Changes".
Technical details
# Version number for the iOS 15 / Xcode 26 floor
## Affected sites
- `Sources/SuperwallKit/Misc/Constants.swift:21` — `4.18.0`
- `SuperwallKit.podspec:4` — `s.version = "4.18.0"`
- `CHANGELOG.md:5-9` — `## 4.18.0` with a `### Breaking Changes` heading
## Context already verified
- `master` and `develop` both carried `4.17.0` before this PR, so per `CLAUDE.md`
opening a new version section (rather than appending to a staged one) was correct.
The only question is which number.
- `CHANGELOG.md:1368` — "Sets the minimum iOS version to iOS 13." shipped in `3.0.0`.
## Required outcome
- A deliberate, recorded decision on major vs minor. If `5.0.0`, bump all three
files together per `CLAUDE.md`. If `4.18.0` stands, say in the changelog entry
why a minor was chosen so integrators reading it understand the resolution
failure they may hit.
## Open questions for the human
- Is there a separate v5 milestone this would collide with?
- Does the release process publish a migration note, and does the online
documentation's stated minimum get updated alongside the tag? The PR checklist
leaves "I have updated the SDK documentation as well as the online docs" unchecked.ℹ️ One iOS 13 fallback survived the sweep, outside the diff
Sources/SuperwallKit/Permissions/Handlers/Location/LocationPermissionDelegate.swift still declares locationManager(_:didChangeAuthorization:) — the pre-iOS 14 CoreLocation delegate selector — alongside its doc comments "Implements both iOS 14+ and iOS 13 delegate methods dynamically" and "iOS 13 and earlier delegate method". At an iOS 15 floor CLLocationManager only ever dispatches locationManagerDidChangeAuthorization(_:), so the older selector is unreachable. Worth noting that the file's header comment explains these @objc selectors are deliberately present in the binary's Objective-C metadata, so this is a considered removal rather than a blind delete.
Technical details
# Residual pre-iOS 14 CoreLocation delegate path
## Affected sites
- `Sources/SuperwallKit/Permissions/Handlers/Location/LocationPermissionDelegate.swift:11`
— class doc comment "Implements both iOS 14+ and iOS 13 delegate methods dynamically"
- `Sources/SuperwallKit/Permissions/Handlers/Location/LocationPermissionDelegate.swift:37-45`
— `/// iOS 13 and earlier delegate method` + the `#if !os(visionOS)` guarded
`@objc func locationManager(_:didChangeAuthorization:)`
- `Sources/SuperwallKit/Permissions/Handlers/Location/LocationPermissionDelegate.swift:49`
— comment "Try instance property first (iOS 14+)", whose "first" framing implies a
fallback that no longer needs to exist
## Required outcome
- Either drop the iOS 13 selector and realign the comments to the iOS 15 floor, or
leave it and record why (the header comment's `scan-privacy-signatures.sh` rationale
is about which selectors are *acceptable* to expose, not about which are still needed).
## Open questions for the human
- Does removing an `@objc` selector from the binary's metadata interact with the
privacy-signature scanning this file's header describes?ℹ️ Nitpicks
Tests/SuperwallKitTests/StoreKit/Products/StoreProduct/SK2PriceFormatRoundingTests.swift:23-25— the comment still says "SuperwallKit ships with an iOS 13 minimum so we guard at runtime" after the guard was removed.Tests/SuperwallKitTests/StoreKit/Products/SK2StoreProductCyclesTests.swift:17-19— "each test guards with#availablebefore calling it" no longer describes the tests.Sources/SuperwallKit/StoreKit/Transactions/Purchasing/PurchasingCoordinator.swift:69— "If on iOS 15+, try and get latest transaction using SK2" is now unconditional.Sources/SuperwallKit/Misc/Extensions/SystemInfo+NotificationName.swift:38— the dyld-crash workaround comment says "watchOS 7.0..<9.0"; the reachable range is now 8.0..<9.0.Tests/SuperwallKitTests/Paywall/View Controller/Web View/Message Handling/PaywallMessageHandlerDelegateMock.swift:29-39— a#if compiler(>=6.0)with a dead#elsebranch escaped the compiler-gate sweep, which the newCLAUDE.mdrule now forbids.
Claude Opus | 𝕏
Declare tvOS 15 so the unguarded StoreKit 2 declarations still compile for tvOS consumers, remove the unreachable `.unsupported` return and empty #if left in the camera permission handler, and build visionOS in CI through Package.swift (the xcodegen project is iOS-only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Re the version question in the Pullfrog review: 4.18.0 is intentional. Every App Store Connect upload already needs Xcode 26 / the iOS 26 SDK, so no shipping app can take this release without already meeting its minimums. Apps still on iOS 13/14 can stay on 4.17.x. The other review findings (tvOS floor, dead Catalyst camera code) are fixed in 8fc6fce, which also adds a visionOS build to |
Drop the pre-iOS 14 locationManager(_:didChangeAuthorization:) callback and its test (CLLocationManager only dispatches locationManagerDidChangeAuthorization(_:) on iOS 15+), and update the privacy-signature notes that listed it. Remove a test-only #if compiler(>=6.0) gate, refresh comments that still described iOS 13/14 or runtime #available guards, and explain in the changelog why the release is a minor and how iOS 13/14 apps can stay on 4.17.x. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Addressed the rest of the Pullfrog review in 6e264da:
All 1,090 tests pass. On the previous push, CI's Mac Catalyst and visionOS builds both passed. |
There was a problem hiding this comment.
ℹ️ No blocking issues — the prior review's camera dead-code comment is fully addressed and CI is green on all three platform jobs. One observation on the tvOS floor inline.
Reviewed changes — the single new commit 8fc6fce since the prior pullfrog review at 4136dc8, a 5-file response to review feedback.
- Collapsed the Mac Catalyst camera conditionals —
PermissionHandler+Camera.swiftnow has no#if targetEnvironment(macCatalyst)at all; the unreachablereturn .unsupportedand the empty#if/#endifpair are both gone, exactly as suggested. - Declared a tvOS 15 floor —
.tvOS(.v15)inPackage.swift, echoed in theCHANGELOG.mdbreaking-changes entry and theCLAUDE.mdtoolchain section. - Added a
build-visionosCI job —macos-26+latest-stable, a conditionalxcodebuild -downloadPlatform visionOS, thenrm -r SuperwallKit.xcodeprojso the build goes throughPackage.swift(the xcodegen project is iOS-only).Package.swiftwas also added to the workflow'spush:paths filter.
I confirmed build-visionos, build-maccatalyst and Package-SwiftLint all pass on 8fc6fce, which also proves the libcel xcframework behind the Superscript dependency ships visionOS slices.
Claude Opus | 𝕏
There was a problem hiding this comment.
ℹ️ No new issues in this delta — every remaining item from the previous review is addressed. One changelog-prose nitpick below.
Reviewed changes — the single new commit 6e264da since the prior pullfrog review at 8fc6fce, an 11-file cleanup pass responding to review feedback.
- Dropped the pre-iOS 14 CoreLocation selector —
LocationPermissionDelegate.locationManager(_:didChangeAuthorization:), its test, and the class/scan-privacy-signatures.shnotes that described two selectors now describe one. At the iOS 15 floorCLLocationManageronly ever dispatcheslocationManagerDidChangeAuthorization(_:), and no reference to the old selector survives anywhere inSources/,Tests/orscripts/. - Removed the last compiler gate below the floor —
PaywallMessageHandlerDelegateMock's#if compiler(>=6.0)and its dead#elsebranch are gone, keeping the@MainActorcompletion-handler signature. - Refreshed six stale comments —
AttributionFetcher(pre-14.5 IDFA/ATT clause),DebugPaywallPickerViewController("on iOS 14+"),SystemInfo+NotificationName("watchOS 7.0..<9.0" → "8.0..<9.0"),PurchasingCoordinator("If on iOS 15+"),SK2StoreProductCyclesTestsandSK2PriceFormatRoundingTests(both described#availableguards that no longer exist). - Recorded the minor-version decision in the changelog —
CHANGELOG.md:10now explains why 4.18.0 rather than 5.0.0 and tells iOS 13/14 apps to pin4.17.x. I verified both forms resolve the intended range:.upToNextMinor(from: "4.17.0")and~> 4.17.0each give4.17.0..<4.18.0.
MockLocationManager is still exercised by the five surviving location tests, so the test-file deletion left nothing dangling.
ℹ️ Nitpicks
CHANGELOG.md:10— "every App Store Connect upload already needs Xcode 26, so any app that can ship an update already meets the new Xcode requirement" argues for the toolchain bump, but the resolution failure described in the very next sentence comes from the deployment-target bump, which an Xcode 26 app can still fail. The escape-hatch guidance that follows is accurate and complete; only the lead-in reasoning skips a step.
Claude Opus | 𝕏
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
ℹ️ No blocking issues. The merge resolved cleanly. It did bring in Customer Center code that re-adds the kind of availability checks this PR sets out to remove.
Reviewed changes: the single new commit 542f143 since the prior pullfrog review at 6e264da, a merge of develop that brought in the Customer Center feature, which develop had staged under its own 4.18.0.
- Merged the two
4.18.0changelog sections: one## 4.18.0heading, "Breaking Changes" first, then the Customer Center Enhancements and Fixes. The Customer Center entry's "Requires iOS 15+." was dropped because the SDK floor now makes it redundant. - Kept the version consistent:
Constants.swift, the podspec andCHANGELOG.mdall read4.18.0, so nothing needed bumping again.
build-visionos and both SwiftLint jobs are green on 542f143, so the merged Customer Center code builds under the new manifest. run-tests and build-maccatalyst were still pending when I wrote this.
ℹ️ The merge brings back sub-floor availability checks via Customer Center
The branch no longer matches the PR description ("every @available/#available check at or below iOS 15 … removed") or the new CLAUDE.md rule. develop's Customer Center code adds about 70 @available(iOS 15.0, *) / #available(iOS 15.0, *) checks under Sources/, one @available(iOS 14.0, *), and 94 more in tests. None of these lines are in the PR diff because they live on the base branch, so they can't be anchored inline. At the new floor they're all no-ops, and DependencyContainer carries an Any? boxing workaround with the same shape as the swift(<5.7) hack this PR deleted from SK2StoreProduct.
Technical details
# Customer Center carries availability checks below the iOS 15 floor
## Affected sites
- `Sources/SuperwallKit/Dependencies/DependencyContainer.swift:51-71` — `private var _customerCenterManager: Any?` plus an `@available(iOS 15.0, *)` typed accessor that casts it back; the comment justifies it purely by availability, which the floor now guarantees
- `Sources/SuperwallKit/Superwall+CustomerCenter.swift:25,53,70` — `@available(iOS 15.0, *)` on the public `presentCustomerCenter` / `dismissCustomerCenter` / ObjC entry points
- `Sources/SuperwallKit/CustomerCenter/ViewModel/CustomerCenterDependencies.swift:87,96` — `if #available(iOS 15.0, *), let …` conditions
- `Sources/SuperwallKit/CustomerCenter/Logic/AppStoreVersionLookup.swift:76-79` — `if #available(iOS 15.0, *) { … }` followed by an unreachable `return nil`
- `Sources/SuperwallKit/CustomerCenter/Models/CustomerCenterConfiguration+Appearance.swift:61` — `@available(iOS 14.0, *)`
- ~30 other files under `Sources/SuperwallKit/CustomerCenter/**` with type-level `@available(iOS 15.0, *)`
- 11 files under `Tests/SuperwallKitTests/CustomerCenter/**` (94 annotations)
## Required outcome
- Either sweep these in this PR so the branch matches its description and the `CLAUDE.md` rule, or explicitly defer the sweep to a follow-up. Checks for iOS 16 and later (e.g. `AppStoreVersionLookup.swift:69`, `CountryCode.swift:23`, the iOS 17 ones in `CustomerCenterSheets.swift` / `CustomerCenterViewModel.swift`) stay.
## Suggested approach (optional)
- `DependencyContainer`: type the backing store as `CustomerCenterManager?` and drop the cast and the availability comment.
- Drop the `#available(iOS 15.0, *)` clause from compound conditions, and collapse `if #available(iOS 15.0, *) { X }; return nil` to `X`.
## Open questions for the human
- Sweep here, or in a follow-up? The Customer Center PR is fresh on `develop`, so a separate commit may be easier to review.ℹ️ Nitpicks
Sources/SuperwallKit/Config/Options/SuperwallOptions.swift:129-130: the public doc onstoreKitVersionstill says "StoreKit 2 by default if the app is running on iOS 15+, otherwise it will fallback to StoreKit 1". The default has been unconditionally StoreKit 2 since4136dc8. This comment predates this delta, but it's customer-facing and was missed by earlier sweeps.
Claude Opus | 𝕏
Also stop saying the SDK falls back to StoreKit 1 below iOS 15. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
✅ No new issues found. Both open items from earlier reviews are fixed, and the branch matches its description again.
Reviewed changes: the two commits since the prior pullfrog review at 542f143.
- Removed the Customer Center availability checks below iOS 15: every
@available(iOS 15.0, *)/#available(iOS 15.0, *)and the one@available(iOS 14.0, *)is gone fromSources/SuperwallKit/CustomerCenter/**,Superwall+CustomerCenter.swiftand the 11 Customer Center test files. A grep ofSources/andTests/for checks at or below the floor now only finds#available(watchOS 9, *), which is above it. - Simplified the code those checks forced:
DependencyContainer._customerCenterManageris typed asCustomerCenterManager?now, notAny?with a cast.currentStorefrontCountryCode()is a singlereturnwith no deadreturn nil. The two compoundif #available(...), let ...conditions inProductDisplayInfo.initkeep only their optional bindings. The@available(swift, obsoleted: 1.0)on the Objective-C entry point stays, which is correct. Checks for iOS 16 and 17 are unchanged. - Fixed the public
storeKitVersiondoc: it no longer says the SDK falls back to StoreKit 1. - Removed tvOS:
.tvOS(.v15)is out ofPackage.swiftand tvOS is out of the changelog entry.CLAUDE.mdnow says tvOS isn't supported and gives the reason.
At a37ca1f, build-maccatalyst and Package-SwiftLint pass. build-visionos and run-tests were still pending when I wrote this.
ℹ️ Nitpicks
Sources/SuperwallKit/Documentation.docc/CustomerCenter.md:241: the Limitations section still says "Requires iOS 15.0+. On earlier versions, presentation calls are unavailable at compile time." The Customer Center APIs have no availability attributes now, and the SDK can't be built for earlier versions at all, so the second sentence is stale.Sources/SuperwallKit/CustomerCenter/ViewModel/CustomerCenterDependencies.swift:282: "iOS 15 — the Customer Center's own floor" should now say the SDK's floor. The reasoning around it still holds.
Claude Opus | 𝕏

Changes in this pull request
Since April 28, 2026, Apple has required App Store Connect uploads to be built with the iOS 26 SDK, so any app picking up a new SDK release is already on Xcode 26. This PR raises the SDK's minimums to match, then removes the code that only existed to support older versions.
Minimums (commit 1)
swift-tools-versiongoes from 5.5 to 6.2 (Xcode 26+). The package pinsswiftLanguageModes: [.v5], so strict concurrency stays off; moving to Swift 6 language mode can be a separate change.Package.swift, the podspec,project.ymland the Xcode project.exact:, because.exact(...)isn't available in the 6.2 manifest.swift_versionsstays at5.5. In CocoaPods it sets the Swift language version, not the compiler, so setting it to6.2would force Swift 6 language mode on pod users.Dead code removed
#if compiler(...)checks for versions below 6.2, theswift(<5.7)workaround that stored the SK2ProductasAny,xcode12.pyand its// ignore-xcode-12markers. Thecompiler(>=6.3.2)checks for the iOS 26.4 StoreKit APIs stay. (commit 1)@available/#availablecheck at or below iOS 15 / Mac Catalyst 15 / macOS 12 / watchOS 8, across 71 files, along with the fallback branches that only ran on older OSes:storeKitVersionis now always StoreKit 2. Before, it was StoreKit 1 on iOS 13/14.SecTrustEvaluatefallback.UIMenubuttons are gone, along with their helpers.guard #available(...) else { return }early returns in the tests are gone.interfaceTypehandles.visioninside theswitch, which clears the existing "switch must be exhaustive" warning.keyboardDismissModein the debugger's paywall picker is now guarded with#if !os(visionOS). It was already breaking the visionOS build ondevelop.Release: version bumped to 4.18.0, with a CHANGELOG "Breaking Changes" entry and a toolchain section in
CLAUDE.md, including a rule not to reintroduce these checks.Usage data behind the decision (Superwall events, last 24h, iOS apps on SDK 4.10.6+, grouped by the
compilerVersiondevice attribute):Checklist
scripts/test.shtargets an "iPhone 16" simulator, which fails to launch because the test target defaults to iOS 27. That was already the case before this PR.)CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.swiftlintin the main directory and fixed any issues. (The 2 remaining warnings were already there.)🤖 Generated with Claude Code
The PR appears safe to merge, with both previous findings fully addressed and no new actionable failures identified.
Summary
Raises the SDK’s minimum toolchain and supported platform versions, removes compatibility paths that are now unreachable, and updates release documentation.
Reviews (2) · Last reviewed commit: "Address review: tvOS floor, dead Catalys..."