杨锴
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
/**
 * 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.
 */
//
//  UITabBar+QMUIBarProtocol.m
//  QMUIKit
//
//  Created by molice on 2022/5/18.
//
 
#import "UITabBar+QMUIBarProtocol.h"
#import "QMUIBarProtocolPrivate.h"
#import "QMUICore.h"
#import "UIVisualEffectView+QMUI.h"
#import "NSArray+QMUI.h"
 
@interface UITabBar ()<QMUIBarProtocolPrivate>
@end
 
@implementation UITabBar (QMUIBarProtocol)
 
QMUISynthesizeBOOLProperty(qmuibar_hasSetEffect, setQmuibar_hasSetEffect)
QMUISynthesizeBOOLProperty(qmuibar_hasSetEffectForegroundColor, setQmuibar_hasSetEffectForegroundColor)
 
BeginIgnoreClangWarning(-Wobjc-protocol-method-implementation)
- (void)qmuibar_updateEffect {
    [self.qmui_effectViews enumerateObjectsUsingBlock:^(UIVisualEffectView * _Nonnull effectView, NSUInteger idx, BOOL * _Nonnull stop) {
        if (self.qmuibar_hasSetEffect) {
            // 这里对 iOS 13 不使用 UITabBarAppearance.backgroundEffect 来修改,是因为反正不管 iOS 10 还是 13,最终都是 setBackgroundEffects: 在起作用,而且不用 UITabBarAppearance 还可以规避与 UIAppearance 机制的冲突
            NSArray<UIVisualEffect *> *effects = self.qmuibar_backgroundEffects;
            [effectView qmui_performSelector:NSSelectorFromString(@"setBackgroundEffects:") withArguments:&effects, nil];
        }
        if (self.qmuibar_hasSetEffectForegroundColor) {
            effectView.qmui_foregroundColor = self.qmui_effectForegroundColor;
        }
    }];
}
EndIgnoreClangWarning
 
// UITabBar、UIVisualEffectView  都有一个私有的方法 backgroundEffects,当 UIVisualEffectView 应用于 UITabBar 场景时,磨砂的效果实际上被放在 backgroundEffects 内,而不是公开接口的 effect 属性里,这里为了方便,将 UITabBar (QMUI).effect 转成可用于 backgroundEffects 的数组
- (NSArray<UIVisualEffect *> *)qmuibar_backgroundEffects {
    if (self.qmuibar_hasSetEffect) {
        return self.qmui_effect ? @[self.qmui_effect] : nil;
    }
    return nil;
}
 
#pragma mark - <QMUIBarProtocol>
 
- (UIView *)qmui_backgroundView {
    return [self qmui_valueForKey:@"_backgroundView"];
}
 
- (UIImageView *)qmui_shadowImageView {
    // bar 在 init 完就可以获取到 backgroundView 和 shadowView,无需关心调用时机的问题
    return [self.qmui_backgroundView qmui_valueForKey:@"_shadowView1"];
}
 
- (UIVisualEffectView *)qmui_effectView {
    NSArray<UIVisualEffectView *> *visibleEffectViews = [self.qmui_effectViews qmui_filterWithBlock:^BOOL(UIVisualEffectView * _Nonnull item) {
        return !item.hidden && item.alpha > 0.01 && item.superview;
    }];
    return visibleEffectViews.lastObject;
}
 
- (NSArray<UIVisualEffectView *> *)qmui_effectViews {
    UIView *backgroundView = self.qmui_backgroundView;
    NSMutableArray<UIVisualEffectView *> *result = NSMutableArray.new;
    UIVisualEffectView *backgroundEffectView1 = [backgroundView valueForKey:@"_effectView1"];
    UIVisualEffectView *backgroundEffectView2 = [backgroundView valueForKey:@"_effectView2"];
    if (backgroundEffectView1) {
        [result addObject:backgroundEffectView1];
    }
    if (backgroundEffectView2) {
        [result addObject:backgroundEffectView2];
    }
    return result.count > 0 ? result : nil;
}
 
static char kAssociatedObjectKey_effect;
- (void)setQmui_effect:(UIBlurEffect *)qmui_effect {
    if (qmui_effect) {
        [QMUIBarProtocolPrivate swizzleBarBackgroundViewIfNeeded];
    }
    
    BOOL valueChanged = self.qmui_effect != qmui_effect;
    objc_setAssociatedObject(self, &kAssociatedObjectKey_effect, qmui_effect, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (valueChanged) {
        self.qmuibar_hasSetEffect = YES;// QMUITheme 切换时会重新赋值,所以可能出现本来就是 nil,还给它又赋值了 nil,这种场景不应该导致 hasSet 标志位改变,所以要把标志位的设置放在 if (valueChanged) 里
        [self qmuibar_updateEffect];
    }
}
 
- (UIBlurEffect *)qmui_effect {
    return (UIBlurEffect *)objc_getAssociatedObject(self, &kAssociatedObjectKey_effect);
}
 
static char kAssociatedObjectKey_effectForegroundColor;
- (void)setQmui_effectForegroundColor:(UIColor *)qmui_effectForegroundColor {
    if (qmui_effectForegroundColor) {
        [QMUIBarProtocolPrivate swizzleBarBackgroundViewIfNeeded];
    }
    BOOL valueChanged = ![self.qmui_effectForegroundColor isEqual:qmui_effectForegroundColor];
    objc_setAssociatedObject(self, &kAssociatedObjectKey_effectForegroundColor, qmui_effectForegroundColor, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    if (valueChanged) {
        self.qmuibar_hasSetEffectForegroundColor = YES;// QMUITheme 切换时会重新赋值,所以可能出现本来就是 nil,还给它又赋值了 nil,这种场景不应该导致 hasSet 标志位改变,所以要把标志位的设置放在 if (valueChanged) 里
        [self qmuibar_updateEffect];
    }
}
 
- (UIColor *)qmui_effectForegroundColor {
    return (UIColor *)objc_getAssociatedObject(self, &kAssociatedObjectKey_effectForegroundColor);
}
 
@end