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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.30 12:27:28