Back button briefly jumps to the leading edge of the navigation bar during interactive pop from a screen with a hidden navigation bar (iPhone Duo)

On the iPhone Duo's narrow display, the navigation bar's back button sits in the trailing column below the status bar. During an interactive swipe-back from a screen whose navigation bar is hidden to a screen whose navigation bar is visible, the back button briefly renders in the leading (top-left) position of the bar for one or more frames. In those same frames the trailing-column button is blank. It then jumps back to the trailing column. The result is a visible flicker.

Steps to reproduce:

  1. Open the attached code in Xcode 27.1 and run it on the iPhone Duo simulator (iOS 27.1), using the narrow display in portrait.
  2. Tap "Push". This pushes a screen with a visible navigation bar.
  3. Tap "Push without header". This pushes a screen that hides the navigation bar.
  4. Slowly swipe back from the leading edge to return to the previous screen.

Expected: the back button of the revealed navigation bar stays in the trailing column for the whole transition, the same as with a programmatic pop.

Actual: for one or more frames during the gesture, the back button renders at the leading edge of the navigation bar (top-left), and the trailing column is blank. Then it returns to the trailing column.

Repro:

import UIKit

@main
final class AppDelegate: UIResponder, UIApplicationDelegate {}

final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
  var window: UIWindow?

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options: UIScene.ConnectionOptions) {
    guard let windowScene = scene as? UIWindowScene else { return }
    let window = UIWindow(windowScene: windowScene)
    let nav = SwipeableNavigationController(rootViewController: ScreenVC(title: "header 0", showsHeader: true))
    window.rootViewController = nav
    window.makeKeyAndVisible()
    self.window = window

    // Auto sequence when launched with `-auto YES`: push header, push no header, pop.
    guard UserDefaults.standard.bool(forKey: "auto") else { return }
    DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
      nav.pushViewController(ScreenVC(title: "header 0", showsHeader: true), animated: true)
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
      nav.pushViewController(ScreenVC(title: "no header", showsHeader: false), animated: true)
    }
    DispatchQueue.main.asyncAfter(deadline: .now() + 4.5) {
      nav.popViewController(animated: true)
    }
  }
}

final class ScreenVC: UIViewController {
  private let showsHeader: Bool

  init(title: String, showsHeader: Bool) {
    self.showsHeader = showsHeader
    super.init(nibName: nil, bundle: nil)
    self.title = title
  }

  required init?(coder: NSCoder) { fatalError() }

  override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    navigationController?.setNavigationBarHidden(!showsHeader, animated: animated)
  }

  override func viewDidLoad() {
    super.viewDidLoad()
    view.backgroundColor = .systemGroupedBackground
    let push = UIButton(configuration: .plain(), primaryAction: UIAction(title: "Push") { [weak self] _ in
      self?.navigationController?.pushViewController(ScreenVC(title: "header 0", showsHeader: true), animated: true)
    })
    let pushNoHeader = UIButton(configuration: .plain(), primaryAction: UIAction(title: "Push without header") { [weak self] _ in
      self?.navigationController?.pushViewController(ScreenVC(title: "no header", showsHeader: false), animated: true)
    })
    let back = UIButton(configuration: .plain(), primaryAction: UIAction(title: "Go back") { [weak self] _ in
      self?.navigationController?.popViewController(animated: true)
    })
    let stack = UIStackView(arrangedSubviews: [push, pushNoHeader, back])
    stack.axis = .vertical
    stack.translatesAutoresizingMaskIntoConstraints = false
    view.addSubview(stack)
    NSLayoutConstraint.activate([
      stack.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 10),
      stack.centerXAnchor.constraint(equalTo: view.centerXAnchor),
    ])
  }
}

final class SwipeableNavigationController: UINavigationController, UIGestureRecognizerDelegate {
  override func viewDidLoad() {
    super.viewDidLoad()
    interactivePopGestureRecognizer?.delegate = self
    if #available(iOS 26.0, *) { interactiveContentPopGestureRecognizer?.delegate = self }
  }

  func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    viewControllers.count > 1
  }
}

Thank you in advance!

Feedback Assistant Report ID: FB24972918

Back button briefly jumps to the leading edge of the navigation bar during interactive pop from a screen with a hidden navigation bar (iPhone Duo)
 
 
Q