You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

子类化UINavigationBar重写draw方法后底部边框消失的解决办法

修复UINavigationBar子类化后底部边框消失的问题

首先得指出你代码里的一个关键问题:你在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 06:33:37