iOS开发:SideMenuController转场后无法更新中心控制器UI求助
问题原因分析与解决方案
你遇到的这个UI更新延迟问题,主要是因为SideMenuController的转场动画事务阻塞了视图层级的即时更新,具体原因和解决办法如下:
核心原因
当SideMenuController执行侧边菜单的展示动画时,UIKit会开启一个动画事务来管理整个转场过程。你的代理方法sideMenuControllerDidReveal虽然在动画完成后被调用,但如果该方法的执行仍然处于这个动画事务的上下文里,那么你对中心控制器视图的修改(添加遮罩层)会被UIKit延迟到整个动画事务完全结束后才执行——这就是为什么遮罩要等到菜单隐藏后才显示的原因。
另外,你调用的view.setNeedsDisplay()其实并不适用于这种场景:这个方法是用来触发视图自身的重绘逻辑(比如draw(_ rect:)方法),而添加子视图属于视图层级的变更,并不需要触发重绘,所以这个调用不会起作用。
解决方案
这里有几个针对性的解决办法,按优先级推荐:
1. 用主线程异步任务脱离动画事务
把添加遮罩的操作放到DispatchQueue.main.async里,让它脱离当前的动画事务上下文,这样UIKit会立即处理这个视图层级变更:
public func sideMenuControllerDidReveal(_ sideMenuController: SideMenuController) { DispatchQueue.main.async { let coverView = UIView(frame: sideMenuController.centerViewController.view.bounds) coverView.backgroundColor = UIColor.black.withAlphaComponent(0.6) // 可选:给遮罩添加点击事件,点击时关闭侧边菜单 coverView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.closeSideMenu))) sideMenuController.centerViewController.view.addSubview(coverView) } } @objc private func closeSideMenu() { sideMenuController?.hideMenu() }
2. 直接在SideMenuController的视图层级中插入遮罩
如果第一种方法无效,你可以尝试把遮罩层添加到SideMenuController的根视图上,并且放在侧边菜单视图的下方,这样可以避开中心控制器视图的动画层级限制:
public func sideMenuControllerDidReveal(_ sideMenuController: SideMenuController) { let coverView = UIView(frame: sideMenuController.view.bounds) coverView.backgroundColor = UIColor.black.withAlphaComponent(0.6) coverView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.closeSideMenu))) // 确保遮罩在侧边菜单的下方,中心控制器的上方 if let leftMenuView = sideMenuController.leftViewController?.view { sideMenuController.view.insertSubview(coverView, belowSubview: leftMenuView) } }
3. 使用Auto Layout替代固定Frame
有时候固定Frame会因为视图的transform(转场时中心控制器会有位移变换)导致遮罩位置不对,改用Auto Layout可以确保遮罩始终覆盖中心控制器:
public func sideMenuControllerDidReveal(_ sideMenuController: SideMenuController) { DispatchQueue.main.async { let coverView = UIView() coverView.backgroundColor = UIColor.black.withAlphaComponent(0.6) coverView.translatesAutoresizingMaskIntoConstraints = false let centerView = sideMenuController.centerViewController.view centerView.addSubview(coverView) NSLayoutConstraint.activate([ coverView.topAnchor.constraint(equalTo: centerView.topAnchor), coverView.leadingAnchor.constraint(equalTo: centerView.leadingAnchor), coverView.trailingAnchor.constraint(equalTo: centerView.trailingAnchor), coverView.bottomAnchor.constraint(equalTo: centerView.bottomAnchor) ]) } }
额外提示
- 如果需要在菜单隐藏时移除遮罩层,可以在
sideMenuControllerDidHide代理方法里进行清理,避免内存泄漏。 - 检查SideMenuController的版本,有些旧版本可能存在代理方法调用时机的Bug,升级到最新版本可能会解决问题。
内容的提问来源于stack exchange,提问作者skornos
相关产品推荐
相关产品推荐

