When I set up a toolbar item group with multiple options and set the controlGroupStyle as .palette, and when one of the options are supposed to present a view controller, I get the following error
Attempt to present <UINavigationController: 0x101813200> on <ProjectName.HomeTabBarViewController: 0x10701bc00> (from <UINavigationController: 0x107821000>) which is already presenting <_UIContextMenuActionsOnlyViewController: 0x1035c19d0>.
So basically the context menu we see is under the hood a view controller that is being presented.
Is there a right way of fixing it, or is it maybe something that will be fixed by Apple?
This is how I set up the ToolbarItemGroup on SwiftUI and the view model ultimately presents another UINavigationController that has a UIHostingController as its view controller:
.toolbar {
ToolbarItemGroup(placement: .topBarTrailing) {
Button("ft_commons_edit".localised, systemImage: "pencil") {
viewModel.didTapEditAction()
}
Button("ft_commons_delete".localised, systemImage: "trash") {
viewModel.didTapDeleteAction()
}
} label: {
Image("edit-icon")
.resizable()
.frame(width: 24.0, height: 24.0)
}
}
.controlGroupStyle(.palette)
This is how the view looks like when presentation fails:
As a workaround, I found two options that work well. I’d like to share them and ask for recommendations, just to make sure they won’t cause any unexpected issues later on:
Option 1: Access the top most visible view controller and attempt presenting on it
This requires the following extension:
extension UIViewController {
func topMostViewController() -> UIViewController {
if let presentedViewController = self.presentedViewController {
return presentedViewController.topMostViewController()
} else if let navigationController = self as? UINavigationController,
let topViewController = navigationController.topViewController {
return topViewController.topMostViewController()
} else if let tabBarController = self as? UITabBarController,
let selectedViewController = tabBarController.selectedViewController {
return selectedViewController.topMostViewController()
} else {
return self
}
}
}
Then called as:
navigationController.topMostViewController().present(exerciseEditingNavController, animated: true)
Option 2: Call dismiss before attempting to present anything
This also works fine, even without any delay, the menu is first dismissed and presentation works fine afterwards, code example:
navigationController.dismiss(animated: true)
navigationController.present(exerciseEditingNavController, animated: true)
I feel like Option 1 would be the better choice, because if this context menu is ever no longer treated as a view controller and direct presentation starts working, Option 1 would still behave correctly by presenting from the topmost visible view controller. Option 2, on the other hand, could introduce a bug by dismissing an unrelated view controller if the context menu is no longer represented as a view controller at that point.
I would appreciate any advice from anyone who has experienced this, or from Apple developers.
Thanks
3
0
128