iOS 27 makes iPhone apps fully resizable in iPhone Mirroring and on iPad, requiring apps to drop deprecated screen/orientation APIs and adopt scene-based geometry, size classes, and new navigation bar minimization controls.
โข UIScene lifecycle is now mandatory โ apps without a UISceneDelegate will not launch when built against the iOS 27 SDK
โข iPhone apps are fully resizable in iPhone Mirroring on Mac and when running on iPad, making UIScreen.main and orientation-based layout actively harmful
โข New UINavigationItem.barMinimizationBehavior (.always/.never) controls interactive bar hiding on scroll
โข New prominentTabIdentifier on UITabBarController pins a tab visible even when the tab bar collapses; iPhone apps can now opt into sidebar layout via sidebar.preferredPlacement = .sidebar
โข iPhone apps are now fully resizable in iPhone Mirroring on Mac and on iPad, so apps still relying on UIScreen.main or interface orientation for layout will break or look wrong
โข UIScene lifecycle is now required when building with the latest SDK โ apps without a UISceneDelegate will fail to launch
โข New APIs like barMinimizationBehavior, prominentTabIdentifier, and sidebar.preferredPlacement let you modernize navigation and tab UI with minimal code
Shows the new barMinimizationBehavior API for scroll-away navigation bars and prominentTabIdentifier for persistent tab visibility, plus replaces UIScreen.main with scene-based geometry โ the three most common modernization tasks.
import UIKitโ// OLD: App Delegate lifecycle (no longer acceptable when building with iOS 27 SDK)โ@mainโclass AppDelegate: UIResponder, UIApplicationDelegate {+// MARK: - Scene Delegate (required in iOS 27; apps without this won't launch)+class SceneDelegate: UIResponder, UIWindowSceneDelegate {var window: UIWindow?โ func application(โ _ application: UIApplication,โ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?โ ) -> Bool {โ // OLD: Using UIScreen.main โ breaks in iPhone Mirroring & external displaysโ let screenBounds = UIScreen.main.boundsโ let scale = UIScreen.main.scaleโ print("Screen bounds: \(screenBounds), scale: \(scale)")+ func scene(+ _ scene: UIScene,+ willConnectTo session: UISceneSession,+ options connectionOptions: UIScene.ConnectionOptions+ ) {+ guard let windowScene = scene as? UIWindowScene else { return }+ let window = UIWindow(windowScene: windowScene)+ window.rootViewController = makeRootViewController()+ self.window = window+ window.makeKeyAndVisible()+ }โ window = UIWindow(frame: screenBounds)โ window?.rootViewController = makeLegacyRootViewController()โ window?.makeKeyAndVisible()โ return true+ // React to size changes instead of using UIScreen.main.bounds+ func windowScene(+ _ windowScene: UIWindowScene,+ didUpdate previousCoordinateSpace: UICoordinateSpace,+ interfaceOrientation previousInterfaceOrientation: UIInterfaceOrientation,+ traitCollection previousTraitCollection: UITraitCollection+ ) {+ // Use effectiveGeometry for available space โ never UIScreen.main+ let availableSize = windowScene.effectiveGeometry.systemMinimumLayoutMargins+ print("Scene geometry updated: \(availableSize)")}โ private func makeLegacyRootViewController() -> UIViewController {โ let feedVC = FeedViewControllerLegacy()+ private func makeRootViewController() -> UIViewController {+ // Tab 1: Feed+ let feedVC = FeedViewController()let feedNav = UINavigationController(rootViewController: feedVC)โ feedNav.tabBarItem = UITabBarItem(title: "Feed", image: UIImage(systemName: "house"), tag: 0)+ let feedTab = UITabBarItem(title: "Feed", image: UIImage(systemName: "house"), tag: 0)+ feedNav.tabBarItem = feedTab+ // Tab 2: Search+ let searchVC = UIViewController()+ searchVC.view.backgroundColor = .systemBackground+ searchVC.title = "Search"+ let searchNav = UINavigationController(rootViewController: searchVC)+ searchNav.tabBarItem = UITabBarItem(title: "Search", image: UIImage(systemName: "magnifyingglass"), tag: 1)+let tabController = UITabBarController()โ tabController.viewControllers = [feedNav]โ // No prominentTabIdentifier, no sidebar support+ tabController.viewControllers = [feedNav, searchNav]++ // iOS 27: Pin a prominent tab that stays visible even when bar collapses+ tabController.prominentTabIdentifier = feedTab.identifier++ // iOS 27: Opt iPhone app into sidebar layout when horizontal size class is regular+ tabController.sidebar.preferredPlacement = .sidebar+return tabController}}โclass FeedViewControllerLegacy: UIViewController {+// MARK: - Feed View Controller demonstrating barMinimizationBehavior+class FeedViewController: UIViewController {+ private let tableView = UITableView(frame: .zero, style: .plain)+override func viewDidLoad() {super.viewDidLoad()title = "Feed"โ // No barMinimizationBehavior โ navigation bar never slides away+ view.backgroundColor = .systemBackground++ // iOS 27: Force navigation bar to slide away on scroll+ navigationItem.barMinimizationBehavior = .always+ // If you manage safe area insets yourself, suppress automatic adjustment:+ // navigationItem.barMinimizationSafeAreaAdjustment = .never++ tableView.dataSource = self+ tableView.register(UITableViewCell.self, forCellReuseIdentifier: "cell")+ tableView.translatesAutoresizingMaskIntoConstraints = false+ view.addSubview(tableView)+ NSLayoutConstraint.activate([+ tableView.topAnchor.constraint(equalTo: view.topAnchor),+ tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),+ tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),+ tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)+ ])}โ override func viewWillTransition(โ to size: CGSize,โ with coordinator: UIViewControllerTransitionCoordinatorโ ) {โ super.viewWillTransition(to: size, with: coordinator)โ // OLD: Checking interface orientation for layout โ broken in resizable envsโ let orientation = UIApplication.shared.statusBarOrientationโ if orientation.isLandscape {โ print("Landscape layout")โ } else {โ print("Portrait layout")โ }+ // iOS 27: Use trait collection for scale โ never UIScreen.main.scale+ override func layoutSubviews() {+ // traitCollection.displayScale is automatically tracked;+ // layout is re-triggered when it changes (e.g. moving to external display)+ let scale = traitCollection.displayScale+ print("Display scale from trait collection: \(scale)")}+}++extension FeedViewController: UITableViewDataSource {+ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { 40 }+ func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {+ let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)+ cell.textLabel?.text = "Item \(indexPath.row + 1)"+ return cell+ }}
โข UIScreen.main references will return incorrect info when an app runs on an external display or in iPhone Mirroring โ replace with windowScene.screen or trait collection APIs โข User interface idiom is no longer reliable for layout decisions; use size classes instead โข Interface orientation is ignored in resizable environments โ always use size classes or view bounds โข The .automatic scroll edge effect style now has its own visuals; previously overridden .soft style no longer matches system defaults
Resizable iPhone apps on iPad and iPhone Mirroring on Mac require iOS/macOS 27. UIRequiresFullscreen discrete resizing is honored on iPhone starting iOS 27.
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.