子类化UINavigationBar重写draw方法后底部边框消失的解决办法
首先得指出你代码里的一个关键问题:你在draw(_ rect:)方法里调整子视图约束的做法是完全错误的。draw(_:)是系统用来触发视图内容绘制的方法,每次视图需要重绘(比如屏幕旋转、内容更新)时都会被调用,在这里修改约束会导致重复添加约束、布局反复刷新,不仅影响性能,还会干扰系统的正常布局逻辑——这也是导致边框消失的核心原因之一。
第一步:将布局调整代码移到正确的方法
把你在draw(_:)里的约束调整逻辑移到layoutSubviews()方法中,同时要避免重复添加约束:
class YC_NavigationBar: UINavigationBar { required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupNavigationBar() } override init(frame: CGRect) { super.init(frame: frame) setupNavigationBar() } private func setupNavigationBar() { // 初始化配置单独抽离,代码更清晰 if let backImage = UIImage(named: "TopBar_Button_Back")?.withRenderingMode(.alwaysOriginal) { backIndicatorImage = backImage backIndicatorTransitionMaskImage = backImage } UIBarButtonItem.appearance().setBackButtonTitlePositionAdjustment(UIOffset(horizontal: 0, vertical: -200), for: .default) } override func layoutSubviews() { super.layoutSubviews() // 定位导航栏内容容器 guard let contentView = subviews.first(where: { NSStringFromClass($0.classForCoder) == "_UINavigationBarContentView" }) else { return } for subview in contentView.subviews { // 调整返回按钮的leading约束 if NSStringFromClass(subview.classForCoder) == "_UIModernBarButton" { for constraint in subview.constraints { if constraint.firstAttribute == .leading { constraint.constant = 0 break } } } // 调整右侧按钮栈的trailing约束,避免重复添加 if NSStringFromClass(subview.classForCoder) == "_UIButtonBarStackView" { let hasExistingConstraint = contentView.constraints.contains { $0.firstItem === subview && $0.firstAttribute == .trailing && $0.secondItem === contentView && $0.secondAttribute == .trailing } if !hasExistingConstraint { let trailingConstraint = NSLayoutConstraint( item: subview, attribute: .trailing, relatedBy: .equal, toItem: contentView, attribute: .trailing, multiplier: 1, constant: 0 ) contentView.addConstraint(trailingConstraint) } } } } }
第二步:恢复底部边框
移除draw(_:)方法后,系统默认的底部边框应该会自动恢复。如果还是没有出现,你可以通过UINavigationBarAppearance明确配置边框样式:
在setupNavigationBar()方法里添加以下代码:
private func setupNavigationBar() { // ... 原来的初始化代码 let appearance = UINavigationBarAppearance() // 设置和系统默认一致的底部边框 appearance.shadowColor = UIColor.systemGray4 // 系统默认分隔线颜色,可按需调整 // 应用到所有导航栏状态 standardAppearance = appearance scrollEdgeAppearance = appearance compactAppearance = appearance }
如果你坚持要保留draw(_:)方法(不推荐,因为完全没必要),可以在super.draw(rect)之后手动绘制底部边框:
override func draw(_ rect: CGRect) { super.draw(rect) // 手动绘制底部边框 let borderColor = UIColor.systemGray4 let borderHeight: CGFloat = 0.5 let borderRect = CGRect(x: 0, y: bounds.height - borderHeight, width: bounds.width, height: borderHeight) borderColor.setFill() UIRectFill(borderRect) }
为什么原来的代码会导致边框消失?
当你重写draw(_:)方法时,系统会默认认为你要完全自定义导航栏的绘制内容,因此可能停止自动绘制默认的底部边框(这是iOS导航栏的内部实现逻辑)。另外,在draw(_:)里频繁修改约束会导致布局异常,进一步干扰系统的边框绘制流程。
内容的提问来源于stack exchange,提问作者NabiMo
相关产品推荐
相关产品推荐

