Under iPadsOS 26.0 and 26.1, if a view controller is presented with a presentation style of fullScreen
or pageSheet
, and the view controller is setup with a UISearchController
that has obscuresBackgroundDuringPresentation
set to true
, then when cancelling the search the view controller is being dismissed when it should not be.
To replicate, create a new iOS project based on Swift/Storyboard using Xcode 26.0 or Xcode 26.1. Update ViewController.swift with the following code:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
title = "Root"
navigationItem.rightBarButtonItems = [
UIBarButtonItem(title: "Full", primaryAction: .init(handler: { _ in
self.showModal(with: .fullScreen)
})),
UIBarButtonItem(title: "Page", primaryAction: .init(handler: { _ in
self.showModal(with: .pageSheet)
})),
UIBarButtonItem(title: "Form", primaryAction: .init(handler: { _ in
self.showModal(with: .formSheet)
})),
]
}
private func showModal(with style: UIModalPresentationStyle) {
let vc = ModalViewController()
let nc = UINavigationController(rootViewController: vc)
// This triggers the double dismiss bug when set to either pageSheet or fullScreen.
// If set to formSheet then it works fine.
// Bug is only on iPad with iPadOS 26.0 or 26.1 beta 2.
// Works fine on iPhone (any iOS) and iPadOS 18 as well as macOS 26.0 (not tested with other versions of macOS).
nc.modalPresentationStyle = style
self.present(nc, animated: true)
}
}
Then add a new file named ModalViewController.swift with the following code:
import UIKit
class ModalViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
title = "Modal"
view.backgroundColor = .systemBackground
setupSearch()
}
private func setupSearch() {
let sc = UISearchController(searchResultsController: UIViewController())
sc.delegate = self // Just for debugging - being set or not does not affect the bug
sc.obscuresBackgroundDuringPresentation = true // Critical to reproducing the bug
navigationItem.searchController = sc
navigationItem.preferredSearchBarPlacement = .stacked
}
// When the search is cancelled by tapping on the grayed out area below the search bar,
// this is called twice when it should only be called once. This happens only if the
// view controller is presented with a fullScreen or pageSheet presentation style.
// The end result is that the first call properly dismisses the search controller.
// The second call results in this view controller being dismissed when it should not be.
override func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
print("dismiss ViewController")
// Set breakpoint on the following line
super.dismiss(animated: flag, completion: completion)
}
}
extension ModalViewController: UISearchControllerDelegate {
func willDismissSearchController(_ searchController: UISearchController) {
print("willDissmissSearchController")
}
func didDismissSearchController(_ searchController: UISearchController) {
print("didDismissSearchController")
}
}
Build and run the app on a simulated or real iPad running iPadOS 26.0 or 26.1 (beta 2). A root window appears with 3 buttons in the navbar. Each button displays the same view controller but with a different modalPresentationStyle.
-
Tap the Form button. This displays a modal view controller with formSheet style. Tap on the search field. Then tap on the grayed out area of the view controller to cancel the search. This all works just fine. Dismiss the modal (drag it down).
-
Now tap either the Page or Full button. These display the same modal view controller with pageSheet or fullScreen style respectively. Tap on the search field. Then tap on the grayed out area of the view controller to cancel the search. This time, not only is the search cancelled, but the view controller is also dismissed. This is because the view controller’s
dismiss(animated:completion:)
method is being called twice.
See ViewController.swift for the code that presents the modal. See ModalViewController.swift for the code that sets up the search controller. Both contain lots of comments.
Besides the use of fullScreen
or pageSheet
presentation style to reproduce the bug, the search controller must also have its obscuresBackgroundDuringPresentation
property set to true
. It’s the tap on that obscured background to cancel the search that results in the double call to dismiss. With the breakpoint set in the overloaded dismiss(animated:completion:)
function, you can see the two stack traces that lead to the call to dismiss. When presented as a formSheet, the 2nd call to dismiss is not being made.
This issue does not affect iPadOS 18 nor any version of iOS on iPhones. Nor does it affect the app using Mac Catalyst on macOS 26.0 (untested with macOS 15 or 26.1).
In short, it is expected that cancelling the search in a presented view controller should not also result in the view controller being dismissed.
Tested with Xcode 26.1 beta 2 and Xcode 26.0. Tested with iPadOS 26.1 beta 2 (real and simulated) and iPadOS 26.0 (simulated).
A version of this post was submitted as FB20569327