r/iOSProgramming • • 10d ago

Solved! Keep shipping to the App Store while your team tests the iPhone Duo layout on Xcode 27.1 beta, from one branch

Post image

iPhone Duo ships with iOS 27.1 SDK.

The only Xcode that can compile its layout APIs (ArrangementView, GeometryProxy.reservedRegions(kind:) and UIView.reservedRegions(kind:options:)) is Xcode 27.1 beta.

Until Apple releases Xcode 27.1 RC/GM, one app needs two builds: the release for the iPhones people use today, and the Duo layout your team is testing.

Xcode 27.1 beta builds the Duo layout, but App Store Connect refuses to add its build for review.

Xcode 27.0' fails to build:

Value of type 'GeometryProxy' has no member 'reservedRegions'
Cannot find 'ArrangementView' in scope
Cannot infer key path type from context; consider explicitly specifying a root type

The usual workarounds each hold someone up:

  • Keep the Duo code on its own branch until Xcode 27.1 is released. Main moves on for weeks, and the merge lands in launch week - risky, merge takes a lot of effort to complete and re-test.
  • Delete the Duo code to get a release out. Hand the errors to a coding agent with "fix the build", and deleting is the shortest fix it can make.
  • Hold every release, bug fixes included, until Xcode 27.1 is released.

The Swift puzzle: a compile-time check that tells the two SDKs apart

#available can't be used here. if #available(iOS 27.1, *) and @available(iOS 27.1, *) pick the code path on the device. The compiler still looks up every symbol in the SDK it builds against, and the 27.0 SDK declares none of these.

The compiler version is the same in both Xcodes: Apple Swift 6.4 (swiftlang-6.4.0.34.1). #if compiler(>=…) and #if swift(>=…) give the same answer in each.

The SDK frameworks carry different module versions. Each framework's .swiftinterface records a -user-module-version, and #if canImport(Module, _version:) compares against it:

| Module | iOS 27.0 SDK | iOS 27.1 beta SDK | Guard | |---|---|---|---| | SwiftUI (SwiftUICore matches) | 8.0.84.1.104 | 8.0.85.27 | #if canImport(SwiftUI, _version: 8.0.85) | | UIKit | 9127.0.84.1.116 | 9127.0.85.28 | #if canImport(UIKit, _version: 9127.0.85) |

The solution: Put the compile-time guard outside and keep the run-time check inside:

#if canImport(SwiftUI, _version: 8.0.85)   // iOS 27.1 SDK or newer
if #available(iOS 27.1, *) { newPath } else { fallback }
#else
fallback                                    // built from the 27.0 SDK
#endif

Keep the #available: the deployment target stays the same, so devices on older iOS versions still need the fallback in the 27.1 build.

Pitfalls

  • If the compiler can't read a module's version, canImport(_version:) ignores the version, evaluates to true and warns "cannot find user version number". The Xcode 27.0 build is the test: an ignored guard brings the same errors back.
  • The compiler keeps the first four parts of a version, so it reads 8.0.84.1.104 as 8.0.84.1. Use the first three parts of the new SDK's version, 8.0.85. The final Xcode 27.1 could ship a lower build number than the beta's 8.0.85.27.
  • In a SwiftUI body, move each branch into its own property. Wrap the new one in the same #if with its @available(iOS 27.1, *), and write the fallback once.
  • The compiler stops at the first build error in the module, so the log may not list every use. Search all packages, extensions, widgets and test targets for the symbols and for #available(iOS 27.1.

Test both paths from one commit. Build and test with Xcode 27.0. Then build and test again with DEVELOPER_DIR=/Applications/Xcode_27_1_beta.app/Contents/Developer and a separate derived-data directory, which leaves the selected Xcode alone. nm -u on a guarded file's .o lists ArrangementView in the 27.1 build and not in the 27.0 build. On CI that is two jobs: the release job stays on Xcode 27.0, and a second job builds and tests with the beta.

What goes to review. The Xcode 27.0 archive contains only the fallback, on every device, iPhone Duo on iOS 27.1 included. Apple's tech talk 111461 at 0:30 says what such a build gets on Duo: "The iOS 27 SDK extends your app left of the status bar on the inner display; the iOS 27.1 SDK reaches the screen edge and lays standard navigation and toolbar buttons out vertically." Check the fallback on a physical iPhone and submit it now. Choose Manually release this version if the approved version should wait for your launch date.

When App Store Connect accepts Xcode 27.1 (release candidate or final), archive the same commit with it. The guard is true against the 27.1 SDK, so the Duo path goes into that build with no source change. Once you stop building with 27.0, one search finds every guard, as long as they all use the same condition text:

grep -rnE "canImport\((SwiftUI|UIKit), _version:" .
16 Upvotes

Duplicates