Stylianos Gakis
05/11/2026, 3:17 PM.navigationBarHidden(true) because that also will remove the built-in "back" arrow and the expected behavior of going back with a drag tooStylianos Gakis
05/11/2026, 3:22 PMMichael Krussel
05/11/2026, 5:20 PMnavigationBarBackButtonHidden and then manually add a back button to the toolbar for that screen.Stylianos Gakis
05/11/2026, 5:44 PMMichael Krussel
05/11/2026, 5:46 PMStylianos Gakis
05/11/2026, 5:48 PMStylianos Gakis
05/11/2026, 5:48 PMimport SwiftUI
import UIKit
final class SwipeBackToggleHostingController: UIViewController {
private let child: UIViewController
init(child: UIViewController) {
self.child = child
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
override func viewDidLoad() {
super.viewDidLoad()
addChild(child)
child.view.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(child.view)
NSLayoutConstraint.activate([
child.view.topAnchor.constraint(equalTo: view.topAnchor),
child.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
child.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
child.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
])
child.didMove(toParent: self)
}
// NavigationStack on iOS 18+/26 uses a private pan recognizer for swipe-back
// in addition to interactivePopGestureRecognizer, so toggle every
// UIPanGestureRecognizer up the nav controller's view chain.
func setSwipeBackEnabled(_ enabled: Bool) {
navigationController?.interactivePopGestureRecognizer?.isEnabled = enabled
var view: UIView? = navigationController?.view
while let current = view {
for gr in current.gestureRecognizers ?? [] where gr is UIPanGestureRecognizer {
gr.isEnabled = enabled
}
view = current.superview
}
}
}
Then on my horizontally scrollable items I applied a modifier
// Implemented by Swift. While a pointer is down on a marked region we
// disable the system swipe-back so Compose's horizontal scroll can win;
// on touch end we re-enable it.
interface IosSwipeBackController {
fun setSwipeBackEnabled(isEnabled: Boolean)
}
internal val LocalIosSwipeBackController = staticCompositionLocalOf<IosSwipeBackController?> { null }
@Composable
internal actual fun Modifier.blockSwipeBackOnIos(): Modifier {
val controller = LocalIosSwipeBackController.current ?: return this
return this.pointerInput(controller) {
awaitPointerEventScope {
while (true) {
awaitFirstDown(requireUnconsumed = false, pass = PointerEventPass.Initial)
controller.setSwipeBackEnabled(false)
try {
do {
val event = awaitPointerEvent(PointerEventPass.Initial)
if (event.changes.none { it.pressed }) break
} while (true)
} finally {
controller.setSwipeBackEnabled(true)
}
}
}
}
}
Which reports to that class when a gesture starts and temporarily disables this handling.Stylianos Gakis
05/11/2026, 5:48 PMAndrei Salavei
05/12/2026, 7:02 AMStylianos Gakis
05/12/2026, 3:09 PMStylianos Gakis
05/12/2026, 3:10 PMmake it recognize only edge swipesI specifically want to avoid doing that, since this is a pilot project to test embedding some compose into our iOS project. If by doing so it means that suddenly this screen is the "odd one out" with missing behavior etc then it's a bust. Might as well just do the UI in SwiftUI and consider sharing the rest of the code instead.
Andrei Salavei
05/12/2026, 3:38 PM