iOS中如何给UIBarButtonItem的image叠加子视图且不移动导航栏现有按钮
问题根因
你的实现逻辑是直接将UIBarButtonItem对应的原生视图从导航栏的视图层级中移动到灰色蒙层上,而UINavigationBar的内部布局规则会实时根据内部子视图的数量、尺寸自动排布按钮,原按钮视图被移除后导航栏会判定该位置按钮被删除,触发自动布局重算,最终导致其他按钮位置偏移。
推荐解决方案
方案1:镂空蒙层实现高亮(无侵入,优先选择)
该方案完全不修改原有视图的层级关系,不会触发任何控件的布局重算,适配所有类型控件的高亮需求:
- 初始化半透明灰色蒙层,直接添加到当前window或者页面最顶层视图上,不调整任何原有子视图的父视图
- 转换坐标系:调用
convert(_:to:)方法,将需要高亮的控件frame从自身父视图坐标系转换到蒙层的坐标系 - 生成带镂空的蒙版:创建覆盖整个蒙层的闭合路径,再添加和高亮控件frame一致的镂空路径,设置路径填充规则为
.evenOdd,将路径生成的CAShapeLayer设置为蒙层的mask,即可实现对应位置高亮的效果
示例代码:
// 半透明蒙层初始化 let dimMaskView = UIView(frame: UIScreen.main.bounds) dimMaskView.backgroundColor = .black.withAlphaComponent(0.6) view.addSubview(dimMaskView) // 获取目标控件转换后的frame let targetFrame = targetView.convert(targetView.bounds, to: dimMaskView) // 构建镂空路径 let fullPath = UIBezierPath(rect: dimMaskView.bounds) fullPath.append(UIBezierPath(roundedRect: targetFrame, cornerRadius: 6)) // 设置蒙版 let maskLayer = CAShapeLayer() maskLayer.path = fullPath.cgPath maskLayer.fillRule = .evenOdd dimMaskView.layer.mask = maskLayer
提示功能关闭时直接移除dimMaskView即可,原有视图无任何改动,不会出现布局错位问题。
方案2:占位视图兼容原有逻辑
如果你需要保留现有将控件移到蒙层的实现逻辑,可以通过添加占位视图占住原布局位置解决:
- 拿到UIBarButtonItem对应的视图后,先创建一个和该视图尺寸、位置完全一致的透明UIView作为占位视图
- 将占位视图插入到原按钮视图所在的父视图的同一层级位置,代替原视图占住导航栏的布局空间
- 再将原按钮视图移动到蒙层上,此时导航栏检测到占位视图存在,不会触发布局重排
- 提示功能关闭后将原按钮视图插回占位视图的位置,删除占位视图即可
补充说明
获取UIBarButtonItem对应的原生视图时,可以通过barButtonItem.value(forKey: "view") as? UIView的方式快速获取,避免遍历导航栏子视图时找错目标。
内容的提问来源于stack exchange,提问作者Matt Harg
相关产品推荐
相关产品推荐

