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:
- 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.
- Tap "Push". This pushes a screen with a visible navigation bar.
- Tap "Push without header". This pushes a screen that hides the navigation bar.
- 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!