iPhone X及以上机型导航栏圆角后无法顶屏问题求助
解决iPhone X+机型导航栏圆角后不贴合顶部的问题
问题回顾
在iPhone X及以上机型中,设置导航栏圆角后出现了导航栏无法贴合屏幕顶部的情况,当前效果如图:
导航栏顶部留白效果
对应的实现代码如下:
import UIKit class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() self.navigationController?.navigationBar.layer.cornerRadius = 20 self.navigationController?.navigationBar.clipsToBounds = true self.navigationController?.navigationBar.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] } }
问题原因
其实核心问题出在clipsToBounds = true上。iPhone X及以上机型的顶部有刘海区域,系统默认会让导航栏延伸覆盖到刘海下方(也就是利用了安全区域的top inset)。但当你开启clipsToBounds后,导航栏的layer会把超出自身范围的部分(也就是刘海区域的延伸部分)给裁剪掉,这就导致顶部出现了留白。
推荐解决方法
方法一:用自定义背景视图实现圆角(最稳妥)
我们可以绕过直接修改导航栏layer的方式,给导航栏添加一个自定义的背景视图,只让这个背景显示底部圆角,这样既保留导航栏顶部的全屏延伸,又能实现你想要的视觉效果:
override func viewDidLoad() { super.viewDidLoad() // 移除导航栏默认的背景和阴影 navigationController?.navigationBar.setBackgroundImage(UIImage(), for: .default) navigationController?.navigationBar.shadowImage = UIImage() // 创建自定义背景视图 let customBgView = UIView() // 这里可以自定义背景色,默认用导航栏的背景色 customBgView.backgroundColor = navigationController?.navigationBar.backgroundColor ?? .systemBlue customBgView.layer.cornerRadius = 20 customBgView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] customBgView.clipsToBounds = true // 添加到导航栏并设置约束 navigationController?.navigationBar.addSubview(customBgView) customBgView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ customBgView.topAnchor.constraint(equalTo: navigationController!.navigationBar.topAnchor), customBgView.leadingAnchor.constraint(equalTo: navigationController!.navigationBar.leadingAnchor), customBgView.trailingAnchor.constraint(equalTo: navigationController!.navigationBar.trailingAnchor), customBgView.bottomAnchor.constraint(equalTo: navigationController!.navigationBar.bottomAnchor) ]) // 把背景视图放到最底层,避免遮挡导航栏的按钮和标题 navigationController?.navigationBar.sendSubviewToBack(customBgView) }
这个方法的好处是完全保留了导航栏的系统行为,比如自动适配刘海、横竖屏切换,同时视觉效果完全符合需求。
方法二:调整导航栏的frame(不推荐)
如果你一定要直接修改导航栏的layer,可以关闭导航栏的半透明属性,然后手动调整frame让它顶部贴合屏幕:
override func viewDidLoad() { super.viewDidLoad() // 关闭半透明,避免系统自动调整布局 navigationController?.navigationBar.isTranslucent = false navigationController?.navigationBar.layer.cornerRadius = 20 navigationController?.navigationBar.clipsToBounds = true navigationController?.navigationBar.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner] // 手动设置导航栏frame,覆盖顶部安全区 if #available(iOS 11.0, *) { guard let window = UIApplication.shared.windows.first else { return } let topSafeHeight = window.safeAreaInsets.top navigationController?.navigationBar.frame = CGRect( x: 0, y: 0, width: view.frame.width, height: 44 + topSafeHeight ) } }
不过这个方法有个弊端:关闭isTranslucent会改变导航栏的默认样式,而且横竖屏切换时需要额外处理frame,兼容性不如方法一。
内容的提问来源于stack exchange,提问作者user12408353




