如何实现类似Apple Mail线程视图的动态折叠式高UINavigationBar?
嘿,这个问题问得很到位!其实完全可以用苹果官方公开的API实现Mail.app里那种可折叠的高导航栏效果,根本不需要依赖内部私有接口~下面是具体的实现思路和关键步骤:
实现类似Mail.app的可折叠高导航栏
1. 配置两种状态的导航栏外观
我们可以用UINavigationBarAppearance分别定义**展开(高栏)和折叠(常规栏)**两种状态的样式,包括背景、文字样式、内边距等:
// 折叠状态的常规导航栏外观 let compactAppearance = UINavigationBarAppearance() compactAppearance.backgroundColor = .systemBackground compactAppearance.titleTextAttributes = [.foregroundColor: UIColor.label] // 展开状态的高导航栏外观 let expandedAppearance = UINavigationBarAppearance() expandedAppearance.backgroundColor = .systemBackground // 可通过调整内边距来撑高导航栏 expandedAppearance.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 16, bottom: 20, trailing: 16) // 自定义大标题样式(如果需要) expandedAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.label, .font: UIFont.systemFont(ofSize: 24, weight: .semibold)]
接着给导航栏绑定这两种外观:
navigationController?.navigationBar.standardAppearance = expandedAppearance // 默认展开状态 navigationController?.navigationBar.compactAppearance = compactAppearance navigationController?.navigationBar.scrollEdgeAppearance = expandedAppearance
2. 自定义导航栏高度(可选,精准控制)
如果需要精确控制展开/折叠时的高度,可以自定义UINavigationBar子类,重写sizeThatFits(_:)方法:
class CustomNavigationBar: UINavigationBar { var isExpanded = true { didSet { setNeedsLayout() } } override func sizeThatFits(_ size: CGSize) -> CGSize { // 展开时88pt,折叠时44pt(适配不同设备的状态栏高度可自行调整) let targetHeight = isExpanded ? 88 : 44 return CGSize(width: size.width, height: CGFloat(targetHeight)) } }
记得在创建导航控制器时使用这个自定义类,避免用KVC这类不推荐的方式替换。
3. 监听滚动,动态切换状态
借助UIScrollViewDelegate的scrollViewDidScroll(_:)方法,根据滚动偏移量触发导航栏的状态切换:
func scrollViewDidScroll(_ scrollView: UIScrollView) { let offsetY = scrollView.contentOffset.y // 设定滚动阈值,比如滚动超过20pt时切换到折叠状态 let shouldCollapse = offsetY > 20 guard let navBar = navigationController?.navigationBar as? CustomNavigationBar else { return } if shouldCollapse != navBar.isExpanded { navBar.isExpanded = shouldCollapse // 切换对应外观 navigationController?.navigationBar.standardAppearance = shouldCollapse ? compactAppearance : expandedAppearance // 强制更新布局 navigationController?.navigationBar.layoutIfNeeded() // 同步调整滚动视图的内边距,避免内容被遮挡 let navBarHeight = navBar.isExpanded ? 88 : 44 scrollView.contentInset.top = navBarHeight scrollView.scrollIndicatorInsets.top = navBarHeight } }
4. 动态切换导航栏内容
展开状态下,你可以给UINavigationItem设置自定义titleView(比如包含邮件主题、发件人、时间的复合视图),折叠时切换回常规标题:
// 自定义展开状态的标题视图 let expandedTitleView = ExpandedMailTitleView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 60)) expandedTitleView.configure(with: mailData) // 切换状态时更新内容 if shouldCollapse { navigationItem.titleView = nil navigationItem.title = mailData.subject } else { navigationItem.title = nil navigationItem.titleView = expandedTitleView }
注意事项
- 确保导航控制器的
isNavigationBarHidden为false,iOS 11+ 需正确设置滚动视图的contentInsetAdjustmentBehavior,避免布局冲突。 - 动态调整导航栏高度时,一定要同步更新滚动视图的内边距,保证内容显示正常。
内容的提问来源于stack exchange,提问作者gklka
相关产品推荐
相关产品推荐

