一直觉得UINavigationBar很难搞,Apple每年的改动几乎都有他,从iOS 7, 11, 13, 到今年的15,年年都动刀,年年都得适配。
基于iOS15的UINavigationBar的测试结果令我感到很意外,感觉很多东西和自己想的完全
不一样,很诡异。
一组简单的测试:
- 初始化一个以UIViewController为RootViewController的UINavigationController
- 设置standardAppearance的背景为红色
- 设置scrollEdgeAppearance的背景为绿色
测试结果:

结论:
iOS 14.8.1的行为符合预期,而iOS15.0的结果完全不正确。貌似scrollEdgeAppearance同样影响着不带scroll view视图的Navigation Bar。这和文档的描述还是有出入的。
个人觉得,这是一个Bug,今后可能会Fix。就目前来看,最好的方案是:
- 一律使用UINavigationBar(或者UINavigationItem)的Appearance(standardAppearance / compactAppearance / scrollEdgeAppearance / compactScrollEdgeAppearance)属性来进行外观设置
- standardAppearance / scrollEdgeAppearance这两项必须被设置,且如果没有特殊需求,建议两个设置成一致的值,以保证向之前的版本兼容。
我的需求:
看众多App的个人页面的设计,一般都是隐藏导航栏背景和标题,随着向上滚动,导航栏和标题逐渐显示,例如下面这种效果:
方案一
最先考虑到的方案是NavigationBar的alpha值随着ScrollView的contentOffsetY进行修正,但这种情况就是左右两边的BarButtonItem也会受到影响,所以是不符合的。
方案二
考虑背景View和标题View的alpha值随着ScrollView的contentOffsetY进行修正,但很遗憾的是UINavigationBar并没有公开它的具体的视图结构,所以我们不能直接获取背景View和标题View。
所以黑科技,就是在Debug的时候,通过Capture View Hierarchy来获取具体的视图结构,然后通过遍历UINavigationBar的子视图,查找对应的背景View和标题View来进行修改。
例如

我们可以猜想到_UIBarBackground是背景视图,以及UILabel-Title是标题视图,通过遍历,查找到他们,并修改他们的alpha值,就可以完成需求。
这样做的一个问题就是,如果Apple修改了UINavigationBar的内部结构,那么后果不可预计。
方案三:
通过修改Appearance/titleTextAttributes来实现
CGFloat offsetY = self.tableView.contentOffset.y;
// offsetY 偏移0-200,逐渐显示导航栏,超过200,一直显示
if (offsetY > 200) {
offsetY = 200;
}
CGFloat offsetYPercentage = offsetY / 200;
CGFloat red = 0;
CGFloat blue = 0;
CGFloat green = 0;
CGFloat alpha = 0;
UIColor *finalbackgroundColor = [UIColor systemBackgroundColor];
[finalbackgroundColor getRed:&red green:&green blue:&blue alpha:&alpha];
UIColor *backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:offsetYPercentage];
UIColor *finalShadowColor = [UIColor separatorColor];
[finalShadowColor getRed:&red green:&green blue:&blue alpha:&alpha];
UIColor *shadowColor = [UIColor colorWithRed:red green:green blue:blue alpha:offsetYPercentage];
UIColor *finalLabelColor = [UIColor labelColor];
[finalLabelColor getRed:&red green:&green blue:&blue alpha:&alpha];
UIColor *labelColor = [UIColor colorWithRed:red green:green blue:blue alpha:offsetYPercentage];
UINavigationBarAppearance *appearance = [[UINavigationBarAppearance alloc] init];
// 注意,一定得是Opaque,不能使用configureWithDefaultBackground。
// 通过backgroundColor的alpha来决定视图的透明度
[appearance configureWithOpaqueBackground];
appearance.backgroundColor = backgroundColor;
appearance.shadowColor = shadowColor;
appearance.titleTextAttributes = @{NSForegroundColorAttributeName:labelColor};
self.navigationItem.standardAppearance = appearance;
self.navigationItem.scrollEdgeAppearance = appearance;
要点就是必须是configureWithOpaqueBackground类型的appearance,通过修改背景颜色的alpha,来实现从透明到不透明的过程。
这种方案也有一个问题,就是最终的UINavigationBar没有了系统默认的半透明的效果。
参考资料:
UINavigationBar
Customizing Your App’s Navigation Bar
Sample Code
网友评论