SwiftUI中如何观察Toolbar透明度变化并实现导航栏大/紧凑标题文本切换?
解决SwiftUI中大标题与紧凑标题显示不同文本的问题
你的思路方向有偏差——观察toolbar的alpha值并不能触发标题样式的切换逻辑,因为这个属性控制的是整个工具栏的显示/隐藏透明度,和大标题、紧凑标题的切换毫无关系。下面给你两种可行的解决方案:
方案一:监听导航栏高度变化(推荐)
这个方法通过监听UINavigationBar的高度变化来判断当前是大标题还是紧凑标题模式,因为大标题模式下导航栏高度约为96pt,紧凑模式下是44pt。我们可以用Introspect获取原生导航栏实例,再结合Combine监听其frame变化:
import SwiftUI import Introspect import Combine struct ContentView: View { @State private var currentTitle = "Large Title" private var cancellables = Set<AnyCancellable>() var body: some View { // iOS 16+用NavigationStack,iOS 15及以下用NavigationView NavigationStack { ScrollView { VStack(alignment: .leading) { // 用足够多的内容让滚动生效 ForEach(0..<20) { index in Text("Row \(index)") .padding() } } } .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .principal) { Text(currentTitle) } } .introspectNavigationBar { navigationBar in // 同时监听导航栏frame变化和设备旋转(旋转会改变导航栏高度) Publishers.Merge( NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification), navigationBar.publisher(for: \.frame) ) // 防抖处理,避免频繁切换标题 .debounce(for: .milliseconds(50), scheduler: DispatchQueue.main) .sink { [weak self] _ in guard let self = self else { return } // 高度阈值设为60,区分大标题(>60)和紧凑标题(<=60) self.currentTitle = navigationBar.frame.height > 60 ? "Large Title" : "Compact Title" } .store(in: &self.cancellables) } } } }
方案二:监听ScrollView滚动偏移量
如果你的场景更依赖滚动位置判断,也可以监听UIScrollView的滚动偏移量,当滚动超过大标题高度与紧凑标题高度的差值时,切换标题文本:
import SwiftUI import Introspect struct ContentView: View { @State private var currentTitle = "Large Title" @State private var largeTitleHeight: CGFloat = 96 @State private var scrollViewObserver: NSKeyValueObservation? var body: some View { NavigationStack { ScrollView { VStack(alignment: .leading) { ForEach(0..<20) { index in Text("Row \(index)") .padding() } } } .navigationBarTitleDisplayMode(.large) .toolbar { ToolbarItem(placement: .principal) { Text(currentTitle) } } .introspectScrollView { scrollView in // 监听scrollView的contentOffset变化 scrollViewObserver = scrollView.observe(\.contentOffset, options: [.new]) { [weak self] scrollView, _ in guard let self = self else { return } // 计算切换阈值:大标题高度 - 紧凑标题高度(44pt) let threshold = self.largeTitleHeight - 44 self.currentTitle = scrollView.contentOffset.y > threshold ? "Compact Title" : "Large Title" } } .introspectNavigationController { navController in // 获取大标题模式下导航栏的实际高度 self.largeTitleHeight = navController.navigationBar.sizeThatFits(CGSize(width: UIScreen.main.bounds.width, height: .infinity)).height } .onDisappear { // 移除观察者,避免内存泄漏 scrollViewObserver?.invalidate() } } } }
关键注意事项
- 确保已导入
Introspect和Combine(方案一需要)框架 - 根据iOS版本选择
NavigationStack(iOS 16+)或NavigationView(iOS 15及以下) - 方案一中的防抖处理是为了避免滚动时标题频繁切换,提升用户体验
- 方案二中记得在视图消失时移除观察者,防止内存泄漏
内容的提问来源于stack exchange,提问作者Praanto
相关产品推荐
相关产品推荐

