iOS Swift:半屏弹出页跳转带导航栏编辑页的实现问题
问题描述
现有个人资料页面,导航栏rightBarButtonItem为菜单入口,点击按钮后通过以下代码弹出半屏样式的MenuSheetController:
let nextVc = storyboard.instantiateViewController(withIdentifier: "MenuSheetStoryboard") as! MenuSheetController if let sheet = nextVc.sheetPresentationController { sheet.detents = [.medium()] } self.present(nextVc, animated: true)
半屏菜单页内嵌展示包含多个菜单项的tableView,其中一个菜单项为「编辑资料」。点击该选项时需要跳转至EditProfileViewController,要求该页面和常规NavigationController栈内管理的页面一致,显示原生返回按钮,支持类似popViewController的栈式回退能力。
当前通过以下代码实现菜单页到编辑页的跳转:
let storyBoard : UIStoryboard = UIStoryboard(name: "Main", bundle:nil) let nextViewController = storyBoard.instantiateViewController(withIdentifier: "EditProfileStoryboard") as! EditProfileViewController self.present(nextViewController, animated: true)
该实现存在两个明确问题:
- 若以sheet形式弹出编辑页,用户可通过下拉手势直接关闭页面,不符合需求;同时编辑页的保存按钮需要实现点击后自动回退上一页的效果,和
popViewController交互逻辑一致,当前模态弹出的逻辑无法对齐该体验。 - 若修改为全屏样式弹出编辑页,页面没有默认配置返回入口,用户无法手动回到上一级页面。
解决方案
核心思路是不要直接在半屏菜单控制器上模态弹出编辑页,而是将「编辑资料」的点击事件回调给弹出菜单的个人资料页(该页面本身处于应用主导航栈中),先关闭半屏菜单,再通过导航栈push编辑页,完全对齐常规栈页面的交互逻辑。
实现步骤
- 给半屏菜单控制器添加点击事件回调
在MenuSheetController中添加闭包属性,用于向外传递菜单项点击事件:
class MenuSheetController: UIViewController { // 新增编辑资料点击回调 var onEditProfileSelected: (() -> Void)? // 其余原有代码... }
在tableView的didSelectRowAt代理方法中,匹配到「编辑资料」项时触发回调:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) // 按实际业务的菜单位置判断索引 if indexPath.row == 0 { onEditProfileSelected?() } }
- 在个人资料页弹出菜单时配置回调逻辑
在个人资料页弹出MenuSheetController的位置,实现回调逻辑:先dismiss半屏菜单,动画结束后push编辑页:
let nextVc = storyboard.instantiateViewController(withIdentifier: "MenuSheetStoryboard") as! MenuSheetController if let sheet = nextVc.sheetPresentationController { sheet.detents = [.medium()] } // 配置编辑项点击回调 nextVc.onEditProfileSelected = { [weak self] in guard let self = self else { return } // 先关闭半屏菜单 self.dismiss(animated: true) { // 菜单关闭完成后,从当前个人资料页push编辑页 let storyBoard = UIStoryboard(name: "Main", bundle: nil) guard let editProfileVc = storyBoard.instantiateViewController(withIdentifier: "EditProfileStoryboard") as? EditProfileViewController else { return } self.navigationController?.pushViewController(editProfileVc, animated: true) } } self.present(nextVc, animated: true)
- 编辑页实现常规栈页面的回退逻辑
编辑页不需要做特殊模态适配,保存按钮的回退逻辑和普通导航栈页面完全一致,直接调用pop方法即可:
@objc func handleSaveTap() { // 执行资料保存逻辑:接口请求、本地存储等 // 直接调用pop返回个人资料页,和常规栈页面交互完全一致 navigationController?.popViewController(animated: true) }
方案优势
- 编辑页完全处于应用主导航栈中,自动显示系统原生返回按钮,支持侧滑返回手势,不需要手动添加返回按钮、自定义导航栏
- 禁止下拉关闭的需求天然满足,push出来的页面不支持下拉手势dismiss
- 保存回退逻辑和普通栈页面完全统一,不需要区分present/dismiss和push/pop的逻辑差异
- 转场动画流畅自然,半屏菜单先下落,编辑页从右侧滑入,符合iOS系统交互规范
不推荐的备选方案
如果因特殊业务场景必须使用模态弹出,可以将编辑页嵌套在UINavigationController中全屏present,手动添加导航栏返回按钮,回退时调用dismiss方法。但该方案需要单独适配导航栏样式、返回按钮逻辑、侧滑返回手势,和应用内其他栈内页面的交互逻辑不统一,后续维护成本高,非必要不使用。
// 不推荐的实现示例 let storyBoard = UIStoryboard(name: "Main", bundle:nil) guard let nextViewController = storyBoard.instantiateViewController(withIdentifier: "EditProfileStoryboard") as? EditProfileViewController else { return } let nav = UINavigationController(rootViewController: nextViewController) nav.modalPresentationStyle = .fullScreen // 手动添加返回按钮 nextViewController.navigationItem.leftBarButtonItem = UIBarButtonItem( image: UIImage(systemName: "chevron.left"), style: .plain, target: self, action: #selector(handleBackTap) ) // 禁止下拉关闭 nav.isModalInPresentation = true self.present(nav, animated: true) @objc func handleBackTap() { dismiss(animated: true) }
内容的提问来源于stack exchange,提问作者Manu
相关产品推荐
相关产品推荐

