There isn't a single line of Objective-C in any of the six iPhone Duo Tech Talks. Expected — Apple shows off new things in Swift and SwiftUI. The industry's reality is different: banking apps, messengers, everything written since the early 2010s that survived five redesigns carries an ObjC core. Eight years in iOS have shown me enough of these codebases to know that "we'll rewrite in Swift first, then adapt" is a plan that never happens. You'll be adapting what you have.
The good news: almost everything the Duo needs lives in UIKit, and UIKit still speaks Objective-C. This article covers what's available directly, what goes through UIKit counterparts, and the two places where a Swift shim is unavoidable. Signatures are from the September 2026 Tech Talks; until stable Xcode 27.1 ships, verify the spelling against the SDK headers.
Step 0: rebuild and audit#
The first move is language-agnostic: build the project with Xcode 27.1 against the iOS 27.1 SDK. Without it there's no edge-to-edge, no vertical bars, no new APIs — the app simply gets margins around the screen edges.
Then grep. Every hit on these patterns is a potential defect on the Duo:
[UIScreen mainScreen] → ambiguous on two screens
interfaceOrientation → the inner display ignores orientations
UI_USER_INTERFACE_IDIOM() → Duo is not a new idiom, idiom branches break
userInterfaceIdiom
safeAreaInsets.left * 2 → insets are asymmetric
hardcoded widths (390, 428, ...) → width changes with a hand movementIn old codebases [UIScreen mainScreen] bounds] shows up by the dozen — in frame math, in collection view configuration, in size caches. Replacements:
// Scale comes from the trait collection, not from the screen
CGFloat scale = self.traitCollection.displayScale;
// If you genuinely need the screen (rare) — through the window scene
UIScreen *screen = self.view.window.windowScene.screen;
// Layout size — your own view's bounds with safe area applied
CGRect content = UIEdgeInsetsInsetRect(self.view.bounds,
self.view.safeAreaInsets);Size classes instead of orientations — the old way, but for real#
The trait collection API hasn't changed since iOS 8 — what changed is the cost of ignoring it. An app locked to portrait will rotate on the Duo's inner display regardless of supportedInterfaceOrientations, so all the "show it differently in landscape" logic has to move to size classes:
- (void)traitCollectionDidChange:(UITraitCollection *)previous {
[super traitCollectionDidChange:previous];
if (previous.horizontalSizeClass == self.traitCollection.horizontalSizeClass) {
return;
}
BOOL isWide = self.traitCollection.horizontalSizeClass ==
UIUserInterfaceSizeClassRegular;
// compact — the Duo's outer screen (and every regular iPhone),
// regular — the inner screen (and iPad)
[self rebuildLayoutForWideMode:isWide];
}On iOS 17+ you can use registered observation (registerForTraitChanges:) instead of overriding traitCollectionDidChange: — it's exposed to Objective-C as well.
A separate audit item is Auto Layout versus manual frames. Constraints pinned to safeAreaLayoutGuide will survive the Duo untouched; manual frame math off self.view.frame.size.width with offsets won't. Migrate the latter first.
Bars: clear out the hand-rolled, annotate the system ones#
The rule from Tech Talk 111462 hits legacy code hardest: only system containers participate in the vertical layout — UINavigationController and UITabBarController. A custom UIToolbar added as a subview, a hand-built UIView "header" with buttons — all of that stays horizontal and will fight the system's vertical bar.
If your bars are system ones, annotating the new behavior is done entirely from Objective-C:
// Pin the primary action in the vertical bar
self.navigationItem.pinnedTrailingGroup =
[UIBarButtonItemGroup fixedGroupWithRepresentativeItem:nil
items:@[sendButton]];
// Custom back/close — the leading item; stop the system back button from supplementing it
self.navigationItem.leftItemsSupplementBackButton = NO;
// Secondary actions go to the overflow menu
self.navigationItem.additionalOverflowItems =
[UIDeferredMenuElement elementWithProvider:^(void (^completion)(NSArray *)) {
completion(@[self.shareAction, self.exportAction]);
}];
// Priorities: what moves to overflow first when space runs out
filterButton.visibilityPriority = UIBarButtonItemVisibilityPriorityLow;
// A badge instead of a text counter
inboxButton.badge = [UIBarButtonItemBadge countBadgeWithInteger:7];The vertical bar is narrow, and its elements are icons only. A text button like "Send all" or a segmented control won't squeeze in — those stay in the navigation bar. Per-controller opt-out is an override of preferredVerticalBarBehavior.
Scenes: a debt that's come due#
Plenty of ObjC apps still live on the AppDelegate-centric lifecycle, having postponed the scene migration "until better times". The Duo has scheduled those times: it's the first iPhone with multiple windows per app and Split View for everyone. The migration itself is standard (UIApplicationSceneManifest with UIApplicationSupportsMultipleScenes in Info.plist, UIWindowSceneDelegate instead of the AppDelegate wiring) — there are no Duo-specific keys.
There's one piece of new behavior, and it's sneaky: windows are created only on the inner display. On a closed device the scene request fails:
UISceneSessionActivationRequest *request =
[UISceneSessionActivationRequest requestWithRole:UIWindowSceneSessionRoleApplication];
[UIApplication.sharedApplication activateSceneSessionForRequest:request
errorHandler:^(NSError *error) {
// Closed device: no new windows — show it in the current window
[self openDocumentInCurrentWindow];
}];Any "open in new window" buttons in your UI should either use the system UIWindowScene activation affordance (it hides itself when windows are unavailable) or honestly handle the error.
Reserved regions and the hinge from Objective-C#
The geometry of the fold and the cameras is available at the UIView level:
NSArray<UIViewReservedRegion *> *folds =
[self.view reservedRegionsWithKind:UIViewReservedRegionKindDivision
options:0];
if (folds.count > 0) {
CGRect foldFrame = folds.firstObject.frame;
// For example: don't place a floating button inside this rectangle
}UIHingeInteraction belongs to the UIInteraction family, so it's ObjC-compatible too: [view addInteraction:]. But before reaching for the hinge angle, ask whether you need it at all: Apple flat-out forbids driving layout from the angle, and system components (sheets, alerts, menus, system bars) route around the fold without your involvement.
Where you do need Swift#
The honest boundary looks like this. The SwiftUI exclusives — onHingeChange, ArrangementView, .sceneAccessory with CameraCaptureAccessory — are unreachable from Objective-C, but the first two have full UIKit counterparts (UIHingeInteraction, UIArrangementViewController), and for most legacy apps those are enough.
A Swift shim becomes necessary in two cases. First — camera apps with explicit module management: AVCaptureDeviceDirectionCoordinator is isolated to the main actor and hands the device over as an AVCaptureDeviceDescriptor. You can technically poke at this from ObjC, but the Swift Concurrency actor model doesn't exist in ObjC, and it's safer to wrap camera coordination in a dedicated Swift type with an ObjC-compatible facade. Second — dual-display features like the teleprompter on the outer screen: CameraCaptureAccessory is declared through the SwiftUI scene accessory API.
The pattern is the same every time: a small @objc Swift class that encapsulates the new API and exposes a delegate or a block to the legacy code. HealthKit, WidgetKit and App Intents were survived exactly this way — the Duo invents nothing new here.
The plan for the quarter#
Boiled down to a list, adapting an ObjC app looks like this:
- Rebuild with the iOS 27.1 SDK, run it in the Duo simulator (Device Hub).
- Grep audit:
mainScreen, orientations, idioms, symmetric insets, hardcoded widths. - Migrate hand-rolled bars to system containers + annotate pinned/overflow.
- Scenes: the manifest,
UIWindowSceneDelegate, handling window-creation failure. - Targeted Swift shims for camera and dual-display, if the product needs them.
Items 1–2 are days, 3–4 are weeks and depend on accumulated debt, and item 5 isn't for everyone. Before October 23, when the device lands in users' hands, any team has time for the first two.



