杨锴
2025-04-16 09a372bc45fde16fd42257ab6f78b8deeecf720b
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
/**
 * Tencent is pleased to support the open source community by making QMUI_iOS available.
 * Copyright (C) 2016-2021 THL A29 Limited, a Tencent company. All rights reserved.
 * Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
 * http://opensource.org/licenses/MIT
 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.
 */
 
//
//  UINavigationController+NavigationBarTransition.m
//  qmui
//
//  Created by QMUI Team on 16/2/22.
//
 
#import "UINavigationController+NavigationBarTransition.h"
#import "QMUINavigationController.h"
#import "QMUICore.h"
#import "UINavigationController+QMUI.h"
#import "UIImage+QMUI.h"
#import "UIViewController+QMUI.h"
#import "UINavigationBar+Transition.h"
#import "QMUINavigationTitleView.h"
#import "UINavigationBar+QMUI.h"
#import "UINavigationBar+QMUIBarProtocol.h"
#import "UIView+QMUI.h"
#import "QMUILog.h"
 
/**
 *  为了响应<b>NavigationBarTransition</b>分类的功能,UIViewController需要做一些相应的支持。
 *  @see UINavigationController+NavigationBarTransition.h
 */
@interface UIViewController (NavigationBarTransition)
 
@property(nonatomic, assign) BOOL qmuinb_shouldShowTransitionBar;
 
/// 用来模仿真的navBar的,在转场过程中存在的一条假navBar
@property(nonatomic, strong) _QMUITransitionNavigationBar *transitionNavigationBar;
 
/// 是否要把真的navBar隐藏
@property(nonatomic, assign) BOOL prefersNavigationBarBackgroundViewHidden;
 
/// 原始containerView的背景色
@property(nonatomic, strong) UIColor *originContainerViewBackgroundColor;
 
@end
 
@interface UILabel (NavigationBarTransition)
@property(nonatomic, strong) UIColor *qmui_specifiedTextColor;
@end
 
@implementation UILabel (NavigationBarTransition)
 
QMUISynthesizeIdStrongProperty(qmui_specifiedTextColor, setQmui_specifiedTextColor)
 
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        OverrideImplementation(NSClassFromString(@"UIButtonLabel"), @selector(setAttributedText:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(UILabel *selfObject, NSAttributedString *attributedText) {
                
                if (selfObject.qmui_specifiedTextColor) {
                    NSMutableAttributedString *mutableAttributedText = [attributedText isKindOfClass:NSMutableAttributedString.class] ? attributedText : [attributedText mutableCopy];
                    [mutableAttributedText addAttributes:@{ NSForegroundColorAttributeName : selfObject.qmui_specifiedTextColor} range:NSMakeRange(0, mutableAttributedText.length)];
                    attributedText = mutableAttributedText;
                }
                
                void (*originSelectorIMP)(id, SEL, NSAttributedString *);
                originSelectorIMP = (void (*)(id, SEL, NSAttributedString *))originalIMPProvider();
                originSelectorIMP(selfObject, originCMD, attributedText);
            };
        });
    });
}
 
@end
 
@implementation UINavigationBar (NavigationBarTransition)
 
/// 获取系统自带的返回按钮 Label,如果在转场时,会获取到最上面控制器的。
- (UILabel *)qmui_backButtonLabel {
    __block UILabel *backButtonLabel = nil;
    [self.qmui_contentView.subviews enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(__kindof UIView * _Nonnull subview, NSUInteger idx, BOOL * _Nonnull stop) {
        if ([subview isKindOfClass:NSClassFromString(@"_UIButtonBarButton")]) {
            UIButton *titleButton = [subview valueForKeyPath:@"visualProvider.titleButton"];
            backButtonLabel = titleButton.titleLabel;
            *stop = YES;
        }
    }];
    return backButtonLabel;
}
 
@end
 
 
@implementation UIViewController (NavigationBarTransition)
 
#pragma mark - 主流程
 
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        ExtendImplementationOfVoidMethodWithoutArguments([UINavigationController class], @selector(qmui_didInitialize), ^(UINavigationController *selfObject) {
            [selfObject qmui_addNavigationActionDidChangeBlock:^(QMUINavigationAction action, BOOL animated, __kindof UINavigationController * _Nullable weakNavigationController, __kindof UIViewController * _Nullable appearingViewController, NSArray<__kindof UIViewController *> * _Nullable disappearingViewControllers) {
                
                // 左右两个界面都必须存在
                UIViewController *disappearingViewController = disappearingViewControllers.lastObject;
                if (!appearingViewController || !disappearingViewController) {
                    return;
                }
                
                switch (action) {
                    case QMUINavigationActionDidPush:
                    case QMUINavigationActionWillPop:
                    case QMUINavigationActionDidSet: {
                        BOOL shouldCustomNavigationBarTransition =
                        [weakNavigationController shouldCustomTransitionAutomaticallyForOperation:UINavigationControllerOperationPush firstViewController:disappearingViewController secondViewController:appearingViewController];
                        if (shouldCustomNavigationBarTransition) {
                            disappearingViewController.qmuinb_shouldShowTransitionBar = YES;
                            appearingViewController.qmuinb_shouldShowTransitionBar = YES;
                            
                            // 只绑定即将显示的 vc 的 bar,注意可能在 setNavigationBarHidden: 里被覆盖,引起下述问题:
                            // https://github.com/Tencent/QMUI_iOS/issues/1335
                            weakNavigationController.navigationBar.qmuinb_copyStylesToBar = appearingViewController.transitionNavigationBar;
                        }
                    }
                        break;
                    case QMUINavigationActionPushCompleted:
                    case QMUINavigationActionPopCompleted:
                    case QMUINavigationActionSetCompleted: {
                        disappearingViewController.qmuinb_shouldShowTransitionBar = NO;
                        appearingViewController.qmuinb_shouldShowTransitionBar = NO;
                        weakNavigationController.navigationBar.qmuinb_copyStylesToBar = nil;
                    }
                        break;
                    default:
                        break;
                }
            }];
        });
        
        OverrideImplementation([UINavigationController class], @selector(setNavigationBarHidden:animated:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(UINavigationController *selfObject, BOOL hidden, BOOL animated) {
                
                // call super
                void (*originSelectorIMP)(id, SEL, BOOL, BOOL);
                originSelectorIMP = (void (*)(id, SEL, BOOL, BOOL))originalIMPProvider();
                originSelectorIMP(selfObject, originCMD, hidden, animated);
                
                if ((selfObject.qmui_isPushing || selfObject.qmui_isPopping) && selfObject.topViewController.qmuinb_shouldShowTransitionBar) {
                    if (hidden) {
                        [selfObject.topViewController removeTransitionNavigationBar];
                    } else {
                        [selfObject.topViewController addTransitionNavigationBarAndBindNavigationBar:YES];
                    }
                }
            };
        });
        
        OverrideImplementation([UIViewController class], @selector(viewWillAppear:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(UIViewController *selfObject, BOOL firstArgv) {
                
                // 放在最前面,留一个时机给业务可以覆盖
                [selfObject renderNavigationBarStyleAnimated:firstArgv];
                
                // call super
                void (*originSelectorIMP)(id, SEL, BOOL);
                originSelectorIMP = (void (*)(id, SEL, BOOL))originalIMPProvider();
                originSelectorIMP(selfObject, originCMD, firstArgv);
            };
        });
        
        OverrideImplementation([UIViewController class], @selector(viewWillLayoutSubviews), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(UIViewController *selfObject) {
                [selfObject.transitionNavigationBar updateLayout];
                
                // call super
                void (*originSelectorIMP)(id, SEL);
                originSelectorIMP = (void (*)(id, SEL))originalIMPProvider();
                originSelectorIMP(selfObject, originCMD);
            };
        });
        
        // 修复 UISearchController push 到导航栏隐藏的界面时,会强制把导航栏重新显示出来的 bug
        // https://github.com/Tencent/QMUI_iOS/issues/479
        // _navigationControllerWillShowViewController:
        SEL selector = NSSelectorFromString([NSString stringWithFormat:@"_%@%@:", @"navigationController", @"WillShowViewController"]);
        QMUIAssert([[UISearchController class] instancesRespondToSelector:selector], @"UIViewController (NavigationBarTransition)", @"iOS 版本更新导致 UISearchController 无法响应方法 %@", NSStringFromSelector(selector));
        OverrideImplementation([UISearchController class], selector, ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
            return ^(UISearchController *selfObject, NSNotification *firstArgv) {
                UIViewController *nextViewController = firstArgv.userInfo[@"UINavigationControllerNextVisibleViewController"];
                if (![nextViewController canCustomNavigationBarTransitionIfBarHiddenable]) {
                    void (*originSelectorIMP)(id, SEL, NSNotification *);
                    originSelectorIMP = (void (*)(id, SEL, NSNotification *))originalIMPProvider();
                    originSelectorIMP(selfObject, originCMD, firstArgv);
                }
            };
        });
        
        if (@available(iOS 15.0, *)) {
            // - [UINavigationBar didMoveToWindow]
            OverrideImplementation([UINavigationBar class], @selector(didMoveToWindow), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
                return ^(UINavigationBar *selfObject) {
                    
                    // call super
                    void (*originSelectorIMP)(id, SEL);
                    originSelectorIMP = (void (*)(id, SEL))originalIMPProvider();
                    originSelectorIMP(selfObject, originCMD);
                    
                    // 由于 renderNavigationBarStyleAnimated: 里对导航栏尚未添加到 window 上(UIAppearance 尚未被应用)的情况,跳过了 renderNavigationBarAppearanceAnimated:,所以这里在导航栏添加到 window 上时刷新一下导航栏样式
                    // https://github.com/Tencent/QMUI_iOS/issues/1437
                    if (selfObject.window) {
                        UINavigationController *nav = (UINavigationController *)selfObject.qmui_viewController;
                        if (![nav isKindOfClass:UINavigationController.class]) return;
                        UIViewController *topViewController = nav.topViewController;
                        if (topViewController.qmui_visibleState & QMUIViewControllerVisible) {// 加上这个 visibleState 的判断是因为一个普通的 UINavigationController 被初始化后导航栏默认就有一个 didMoveToWindow 的时机,这个时机里 topViewController 尚未触发 viewWillAppear:,如果不判断 visibleState,就会导致在过早的时候去设置导航栏样式,然后 viewWillAppear: 时又设置了一次。
                            [topViewController renderNavigationBarAppearanceAnimated:NO];
                        }
                    }
                };
            });
        }
    });
}
 
- (void)addTransitionNavigationBarAndBindNavigationBar:(BOOL)shouldBind {
    // add 时虽然过滤了 navigationBarHidden 的条件,但可能在 push/pop 时,新界面暂时还没刷新导航栏的显隐状态,所以还是需要在 viewWillLayoutSubviews 那边再重新根据 navigationBarHidden 的值来决定是否隐藏假 bar
    if (!self.qmuinb_shouldShowTransitionBar || self.transitionNavigationBar || !self.navigationController.navigationBar || self.navigationController.navigationBarHidden) {
        return;
    }
    
    _QMUITransitionNavigationBar *customBar = [[_QMUITransitionNavigationBar alloc] init];
    customBar.parentViewController = self;
    self.transitionNavigationBar = customBar;
    
    // iOS 15 里,假 bar 在 add 到界面上时会被强制同步为 UIAppearance 的值,不管你之前是否设置过自己的样式。而且在那个 runloop 内不管你后续怎么更新 standardAppearance,都会呈现出 UIAppearance 里的统一的值的样式。所以这里一方面屏蔽 didMoveToWindow,从而避免在这时候应用 UIAppearance,另一方面要保证先 add 到界面上再同步当前导航栏的样式。
    // 经测试只有 push 或 push 动画的 set 需要这么处理,pop 及 pop 动画的 set 没问题
    // iOS 14 及以下没这种问题。
    // https://github.com/Tencent/QMUI_iOS/issues/1501
    if (@available(iOS 15.0, *)) {
        BOOL isPush = self.navigationController.qmui_navigationAction == QMUINavigationActionDidPush;
        BOOL isSet = self.navigationController.qmui_navigationAction == QMUINavigationActionDidSet;
        BOOL isPopAnimation = isSet && self.navigationController.qmui_lastOperation == UINavigationControllerOperationPop;
        if (isPush || (isSet && !isPopAnimation)) {
            customBar.shouldPreventAppearance = YES;
        }
    }
    [self.view addSubview:customBar];
    customBar.originalNavigationBar = self.navigationController.navigationBar;// 注意这里内部不会保留真 bar 和假 bar 的 copy 关系
    if (shouldBind) {
        self.navigationController.navigationBar.qmuinb_copyStylesToBar = customBar;
    }
}
 
- (void)removeTransitionNavigationBar {
    if (self.transitionNavigationBar) {
        [self.transitionNavigationBar removeFromSuperview];
        self.transitionNavigationBar = nil;
        id <UIViewControllerTransitionCoordinator> transitionCoordinator = self.transitionCoordinator;
        if (self.navigationController.navigationBar.translucent && self.originContainerViewBackgroundColor) {
            [transitionCoordinator containerView].backgroundColor = self.originContainerViewBackgroundColor;
        }
    }
}
 
#pragma mark - 工具方法
 
// 根据当前的viewController,统一处理导航栏的显隐、样式
- (void)renderNavigationBarStyleAnimated:(BOOL)animated {
    
    // 屏蔽不处于 UINavigationController 里的 viewController,以及 custom containerViewController 里的 childViewController
    if (![self.navigationController.viewControllers containsObject:self]) {
        return;
    }
    
    if (![self conformsToProtocol:@protocol(QMUINavigationControllerAppearanceDelegate)]) {
        return;
    }
    
    // 以下用于控制 vc 的外观样式,如果某个方法有实现则用方法的返回值,否则再看配置表对应的值是否有配置,有配置就使用配置表,没配置则什么都不做,维持系统原生样式
    UIViewController<QMUINavigationControllerAppearanceDelegate> *vc = (UIViewController<QMUINavigationControllerAppearanceDelegate> *)self;
    UINavigationController *navigationController = vc.navigationController;
    
    // 显示/隐藏 导航栏
    if ([vc canCustomNavigationBarTransitionIfBarHiddenable]) {
        if ([vc hideNavigationBarWhenTransitioning]) {
            if (!navigationController.isNavigationBarHidden) {
                [navigationController setNavigationBarHidden:YES animated:animated];
            }
        } else {
            if (navigationController.isNavigationBarHidden) {
                [navigationController setNavigationBarHidden:NO animated:animated];
            }
        }
    }
    
    // 仅当导航栏被添加到 window 之后(UIAppearance 被应用之后),业务才可以设置导航栏的样式,否则在 UINavigationBar (QMUI) 里获取到的 navigationBar.standardAppearance 是系统默认的样式而不是 App 全局配置的样式,导致后续导航栏样式都是错的。
    // https://github.com/Tencent/QMUI_iOS/issues/1437
    if (@available(iOS 15.0, *)) {
        if (!navigationController.navigationBar.window) {
            return;
        }
    }
    
    [self renderNavigationBarAppearanceAnimated:animated];
}
 
// 仅处理导航栏的样式,不涉及显隐
- (void)renderNavigationBarAppearanceAnimated:(BOOL)animated {
    
    // 屏蔽不处于 UINavigationController 里的 viewController,以及 custom containerViewController 里的 childViewController
    if (![self.navigationController.viewControllers containsObject:self]) {
        return;
    }
    
    if (![self conformsToProtocol:@protocol(QMUINavigationControllerAppearanceDelegate)]) {
        return;
    }
    
    // 以下用于控制 vc 的外观样式,如果某个方法有实现则用方法的返回值,否则再看配置表对应的值是否有配置,有配置就使用配置表,没配置则什么都不做,维持系统原生样式
    UIViewController<QMUINavigationControllerAppearanceDelegate> *vc = (UIViewController<QMUINavigationControllerAppearanceDelegate> *)self;
    UINavigationController *navigationController = vc.navigationController;
    
    // 导航栏的背景色
    if ([vc respondsToSelector:@selector(qmui_navigationBarBarTintColor)]) {
        UIColor *barTintColor = [vc qmui_navigationBarBarTintColor];
        navigationController.navigationBar.barTintColor = barTintColor;
    } else if (QMUICMIActivated) {
        navigationController.navigationBar.barTintColor = UINavigationBar.qmui_appearanceConfigured.barTintColor;
    }
    
    // 导航栏的背景
    if ([vc respondsToSelector:@selector(qmui_navigationBarBackgroundImage)]) {
        UIImage *backgroundImage = [vc qmui_navigationBarBackgroundImage];
        [navigationController.navigationBar setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
    } else if (QMUICMIActivated) {
        [navigationController.navigationBar setBackgroundImage:[UINavigationBar.qmui_appearanceConfigured backgroundImageForBarMetrics:UIBarMetricsDefault] forBarMetrics:UIBarMetricsDefault];
    }
    
    //  导航栏的 style
    if ([vc respondsToSelector:@selector(qmui_navigationBarStyle)]) {
        UIBarStyle barStyle = [vc qmui_navigationBarStyle];
        navigationController.navigationBar.barStyle = barStyle;
    } else if (QMUICMIActivated) {
        navigationController.navigationBar.barStyle = UINavigationBar.qmui_appearanceConfigured.barStyle;
    }
    
    // 导航栏底部的分隔线
    if ([vc respondsToSelector:@selector(qmui_navigationBarShadowImage)]) {
        navigationController.navigationBar.shadowImage = [vc qmui_navigationBarShadowImage];
    } else if (QMUICMIActivated) {
        navigationController.navigationBar.shadowImage = NavBarShadowImage;
    }
    
    // 导航栏上控件的主题色
    UIColor *tintColor =
    [vc respondsToSelector:@selector(qmui_navigationBarTintColor)] ? [vc qmui_navigationBarTintColor] :
                                             QMUICMIActivated ? NavBarTintColor : nil;
    if (tintColor) {
        // https://github.com/Tencent/QMUI_iOS/issues/654
        // 改变 navigationBar.tintColor 后会同步改变返回按钮的文字颜色,在 iOS 10及以下,把修改 tintColor 的代码包裹在 animateAlongsideTransition 中能实现转场过渡,而从 iOS 11 开始不生效,现象是:修改了 navigationBar.tintColor 后,返回按钮的文字颜色瞬间变化。
        // 为了实现转场过渡,不要让返回按钮的文字瞬间变化,在转场前锁定 topViewController 所属的 backButtonLabel 颜色,这样在转场过程中改变了 navBar 的 tintColor 不会影响到他。
        if (navigationController.qmui_isPopping) {
            UILabel *backButtonLabel = navigationController.navigationBar.qmui_backButtonLabel;
            if (backButtonLabel) {
                backButtonLabel.qmui_specifiedTextColor = backButtonLabel.textColor;
                [vc qmui_animateAlongsideTransition:nil completion:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
                    backButtonLabel.qmui_specifiedTextColor = nil;
                }];
            }
        }
       
        [vc qmui_animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext>  _Nonnull context) {
            navigationController.navigationBar.tintColor = tintColor;
        } completion:nil];
    }
    
    // iOS 13 及以上,title 的更新只在 viewWillAppear 这里进行就可以了,但 iOS 12 及以下还要靠 popViewController 那边
    // iOS 12 及以下系统,在不使用自定义 titleView 的情况下,在 viewWillAppear 时通过修改 navigationBar.titleTextAttributes 来设置新界面的导航栏标题样式,push 时是生效的,但 pop 时右边界面的样式会覆盖左边界面的样式,所以 pop 时的 titleTextAttributes 改为在 did pop 时处理
    // 如果用自定义 titleView 则没这种问题,只是为了代码简单,时机的选择不区分是否自定义 title
    [vc renderNavigationBarTitleAppearanceAnimated:animated];
}
 
// 仅处理导航栏标题
- (void)renderNavigationBarTitleAppearanceAnimated:(BOOL)animated {
    
    // 屏蔽不处于 UINavigationController 里的 viewController,以及 custom containerViewController 里的 childViewController
    if (![self.navigationController.viewControllers containsObject:self]) {
        return;
    }
    
    if (![self conformsToProtocol:@protocol(QMUINavigationControllerAppearanceDelegate)]) {
        return;
    }
    
    // 以下用于控制 vc 的外观样式,如果某个方法有实现则用方法的返回值,否则再看配置表对应的值是否有配置,有配置就使用配置表,没配置则什么都不做,维持系统原生样式
    UIViewController<QMUINavigationControllerAppearanceDelegate> *vc = (UIViewController<QMUINavigationControllerAppearanceDelegate> *)self;
    UINavigationController *navigationController = vc.navigationController;
    
    // 导航栏title的颜色
    if ([vc respondsToSelector:@selector(qmui_titleViewTintColor)]) {
        UIColor *tintColor = [vc qmui_titleViewTintColor];
        if ([vc.navigationItem.titleView isKindOfClass:QMUINavigationTitleView.class]) {
            ((QMUINavigationTitleView *)vc.navigationItem.titleView).tintColor = tintColor;
        } else if (!vc.navigationItem.titleView) {
            NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = (navigationController.navigationBar.titleTextAttributes ?: @{}).mutableCopy;
            titleTextAttributes[NSForegroundColorAttributeName] = tintColor;
            navigationController.navigationBar.titleTextAttributes = titleTextAttributes.copy;
        } else {
            // 设置了自定义的 navigationItem.titleView,则不处理
        }
    } else if (QMUICMIActivated) {
        UIColor *tintColor = NavBarTitleColor;
        if ([vc.navigationItem.titleView isKindOfClass:QMUINavigationTitleView.class]) {
            ((QMUINavigationTitleView *)vc.navigationItem.titleView).tintColor = tintColor;
        } else if (!vc.navigationItem.titleView) {
            NSMutableDictionary<NSAttributedStringKey, id> *titleTextAttributes = (navigationController.navigationBar.titleTextAttributes ?: @{}).mutableCopy;
            titleTextAttributes[NSForegroundColorAttributeName] = tintColor;
            navigationController.navigationBar.titleTextAttributes = titleTextAttributes.copy;
        } else {
            // 设置了自定义的 navigationItem.titleView,则不处理
        }
    }
}
 
- (BOOL)respondCustomNavigationBarTransitionIfBarHiddenable {
    BOOL respondIfBarHiddenable = NO;
    
    // 如果当前界面正在搜索,由于 UISearchController 会自动把 navigationBar 移上去,所以这种时候 QMUI 就不应该再去操作 bar 的显隐了
    if ([self.presentedViewController isKindOfClass:[UISearchController class]] && ((UISearchController *)self.presentedViewController).hidesNavigationBarDuringPresentation) {
        return NO;
    }
    
    if ([self conformsToProtocol:@protocol(QMUICustomNavigationBarTransitionDelegate)]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        if ([vc respondsToSelector:@selector(shouldCustomizeNavigationBarTransitionIfHideable)]) {
            respondIfBarHiddenable = YES;
        }
    }
    return respondIfBarHiddenable;
}
 
- (BOOL)respondCustomNavigationBarTransitionWithBarHiddenState {
    BOOL respondWithBarHidden = NO;
    if ([self conformsToProtocol:@protocol(QMUICustomNavigationBarTransitionDelegate)]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        if ([vc respondsToSelector:@selector(preferredNavigationBarHidden)]) {
            respondWithBarHidden = YES;
        }
    }
    return respondWithBarHidden;
}
 
- (BOOL)canCustomNavigationBarTransitionIfBarHiddenable {
    if ([self respondCustomNavigationBarTransitionIfBarHiddenable]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        return [vc shouldCustomizeNavigationBarTransitionIfHideable];
    }
    return NO;
}
 
- (BOOL)hideNavigationBarWhenTransitioning {
    if ([self respondCustomNavigationBarTransitionWithBarHiddenState]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        BOOL hidden = [vc preferredNavigationBarHidden];
        return hidden;
    }
    return NO;
}
 
- (BOOL)shouldCustomTransitionAutomaticallyForOperation:(UINavigationControllerOperation)operation firstViewController:(UIViewController *)viewController1 secondViewController:(UIViewController *)viewController2 {
    
    UIViewController<QMUINavigationControllerDelegate> *vc1 = (UIViewController<QMUINavigationControllerDelegate> *)viewController1;
    UIViewController<QMUINavigationControllerDelegate> *vc2 = (UIViewController<QMUINavigationControllerDelegate> *)viewController2;
    
    if (![vc1 conformsToProtocol:@protocol(QMUINavigationControllerDelegate)] || ![vc2 conformsToProtocol:@protocol(QMUINavigationControllerDelegate)]) {
        return NO;// 只处理前后两个界面都是 QMUI 系列的场景
    }
    
    BOOL vc1Clips = vc1.isViewLoaded && vc1.view.clipsToBounds && vc1.qmui_navigationBarMaxYInViewCoordinator < NavigationContentTopConstant;
    BOOL vc2Clips = vc2.isViewLoaded && vc2.view.clipsToBounds && vc2.qmui_navigationBarMaxYInViewCoordinator < NavigationContentTopConstant;
    if (vc1Clips || vc2Clips) {
        QMUILogWarn(@"UINavigationController (NavigationBarTransition)", @"因界面布局原因导致无法优化导航栏动画,vc1 = %@,maxY1 = %.0f, vc2 = %@,maxY2 = %.0f", vc1, vc1.qmui_navigationBarMaxYInViewCoordinator, vc2, vc2.qmui_navigationBarMaxYInViewCoordinator);
        return NO;// 左右两个界面只要其中某个界面无法完整显示 navigationBar,都不进行动画优化
    }
    
    if ([vc1.navigationController.delegate respondsToSelector:@selector(navigationController:animationControllerForOperation:fromViewController:toViewController:)]) {
        // 说明可能有自定义的系统转场动画
        BOOL a = [vc1 respondsToSelector:@selector(shouldCustomizeNavigationBarTransitionIfUsingCustomTransitionForOperation:fromViewController:toViewController:)] ? [vc1 shouldCustomizeNavigationBarTransitionIfUsingCustomTransitionForOperation:operation fromViewController:vc1 toViewController:vc2] : NO;
        BOOL b = [vc2 respondsToSelector:@selector(shouldCustomizeNavigationBarTransitionIfUsingCustomTransitionForOperation:fromViewController:toViewController:)] ? [vc2 shouldCustomizeNavigationBarTransitionIfUsingCustomTransitionForOperation:operation fromViewController:vc1 toViewController:vc2] : NO;
        if (!a && !b) {
            return NO;
        }
    }
    
    if ([vc1 respondsToSelector:@selector(customNavigationBarTransitionKey)] || [vc2 respondsToSelector:@selector(customNavigationBarTransitionKey)]) {
        NSString *key1 = [vc1 respondsToSelector:@selector(customNavigationBarTransitionKey)] ? [vc1 customNavigationBarTransitionKey] : nil;
        NSString *key2 = [vc2 respondsToSelector:@selector(customNavigationBarTransitionKey)] ? [vc2 customNavigationBarTransitionKey] : nil;
        BOOL result = (key1 || key2) && ![key1 isEqualToString:key2];
        return result;
    }
    
    if (!AutomaticCustomNavigationBarTransitionStyle) {
        return NO;
    }
    
 
    
    UIImage *bg1 = [vc1 respondsToSelector:@selector(qmui_navigationBarBackgroundImage)] ? [vc1 qmui_navigationBarBackgroundImage] : [UINavigationBar.qmui_appearanceConfigured backgroundImageForBarMetrics:UIBarMetricsDefault];
    UIImage *bg2 = [vc2 respondsToSelector:@selector(qmui_navigationBarBackgroundImage)] ? [vc2 qmui_navigationBarBackgroundImage] : [UINavigationBar.qmui_appearanceConfigured backgroundImageForBarMetrics:UIBarMetricsDefault];
    if (bg1 || bg2) {
        if (!bg1 || !bg2) {
            return YES;// 一个有一个没有,则需要自定义
        }
        if (![bg1.qmui_averageColor isEqual:bg2.qmui_averageColor]) {
            return YES;// 目前只能判断图片颜色是否相等了
        }
    }
    
    // 如果存在 backgroundImage,则 barTintColor、barStyle 就算存在也不会被显示出来,所以这里只判断两个 backgroundImage 都不存在的时候
    if (!bg1 && !bg2) {
        UIColor *barTintColor1 = [vc1 respondsToSelector:@selector(qmui_navigationBarBarTintColor)] ? [vc1 qmui_navigationBarBarTintColor] : UINavigationBar.qmui_appearanceConfigured.barTintColor;
        UIColor *barTintColor2 = [vc2 respondsToSelector:@selector(qmui_navigationBarBarTintColor)] ? [vc2 qmui_navigationBarBarTintColor] : UINavigationBar.qmui_appearanceConfigured.barTintColor;
        if (barTintColor1 || barTintColor2) {
            if (!barTintColor1 || !barTintColor2) {
                return YES;
            }
            if (![barTintColor1 isEqual:barTintColor2]) {
                return YES;
            }
        }
        
        UIBarStyle barStyle1 = [vc1 respondsToSelector:@selector(qmui_navigationBarStyle)] ? [vc1 qmui_navigationBarStyle] : UINavigationBar.qmui_appearanceConfigured.barStyle;
        UIBarStyle barStyle2 = [vc2 respondsToSelector:@selector(qmui_navigationBarStyle)] ? [vc2 qmui_navigationBarStyle] : UINavigationBar.qmui_appearanceConfigured.barStyle;
        if (barStyle1 != barStyle2) {
            return YES;
        }
    }
    
    UIImage *shadowImage1 = [vc1 respondsToSelector:@selector(qmui_navigationBarShadowImage)] ? [vc1 qmui_navigationBarShadowImage] : (vc1.navigationController.navigationBar ? vc1.navigationController.navigationBar.shadowImage : (QMUICMIActivated ? NavBarShadowImage : nil));
    UIImage *shadowImage2 = [vc2 respondsToSelector:@selector(qmui_navigationBarShadowImage)] ? [vc2 qmui_navigationBarShadowImage] : (vc2.navigationController.navigationBar ? vc2.navigationController.navigationBar.shadowImage : (QMUICMIActivated ? NavBarShadowImage : nil));
    if (shadowImage1 || shadowImage2) {
        if (!shadowImage1 || !shadowImage2) {
            return YES;
        }
        if (![shadowImage1.qmui_averageColor isEqual:shadowImage2.qmui_averageColor]) {
            return YES;
        }
    }
    
    return NO;
}
 
- (UIColor *)containerViewBackgroundColor {
    if ([self conformsToProtocol:@protocol(QMUICustomNavigationBarTransitionDelegate)]) {
        UIViewController<QMUICustomNavigationBarTransitionDelegate> *vc = (UIViewController<QMUICustomNavigationBarTransitionDelegate> *)self;
        if ([vc respondsToSelector:@selector(containerViewBackgroundColorWhenTransitioning)]) {
            return [vc containerViewBackgroundColorWhenTransitioning];
        }
    }
    return self.isViewLoaded && self.view.backgroundColor ? self.view.backgroundColor : UIColorWhite;
}
 
#pragma mark - Setter / Getter
 
QMUISynthesizeIdStrongProperty(transitionNavigationBar, setTransitionNavigationBar)
QMUISynthesizeIdStrongProperty(originContainerViewBackgroundColor, setOriginContainerViewBackgroundColor)
 
static char kAssociatedObjectKey_backgroundViewHidden;
- (void)setPrefersNavigationBarBackgroundViewHidden:(BOOL)prefersNavigationBarBackgroundViewHidden {
    // 从某个版本开始,发现从有 navBar 的界面返回无 navBar 的界面,backgroundView 会跑出来,发现是被系统重新设置了显示,所以改用其他的方法来隐藏 backgroundView,就是 mask。
    if (prefersNavigationBarBackgroundViewHidden) {
        self.navigationController.navigationBar.qmui_backgroundView.layer.mask = [CALayer layer];
    } else {
        self.navigationController.navigationBar.qmui_backgroundView.layer.mask = nil;
    }
    objc_setAssociatedObject(self, &kAssociatedObjectKey_backgroundViewHidden, @(prefersNavigationBarBackgroundViewHidden), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
 
- (BOOL)prefersNavigationBarBackgroundViewHidden {
    return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_backgroundViewHidden)) boolValue];
}
 
static char kAssociatedObjectKey_shouldShowTransitionBar;
- (void)setQmuinb_shouldShowTransitionBar:(BOOL)shouldShowTransitionBar {
    objc_setAssociatedObject(self, &kAssociatedObjectKey_shouldShowTransitionBar, @(shouldShowTransitionBar), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (shouldShowTransitionBar) {
        [self addTransitionNavigationBarAndBindNavigationBar:NO];// 这里不绑定 bar,因为不知道此时是两个 vc 里的哪一个
        self.prefersNavigationBarBackgroundViewHidden = YES;
    } else {
        [self removeTransitionNavigationBar];
        // 屏蔽一些 childViewController 触发的场景,只关心堆栈里的
        if ([self.navigationController.viewControllers containsObject:self]) {
            self.prefersNavigationBarBackgroundViewHidden = NO;
        }
    }
}
 
- (BOOL)qmuinb_shouldShowTransitionBar {
    return [((NSNumber *)objc_getAssociatedObject(self, &kAssociatedObjectKey_shouldShowTransitionBar)) boolValue];
}
 
@end