cdts/xdts-ios 3/TreeHole/CYHResetCode/CYH/QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m

564 lines
24 KiB
Mathematica
Raw Normal View History

2023-07-27 09:20:00 +08:00
/**
* 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.
*/
//
// UIImage+QMUITheme.m
// QMUIKit
//
// Created by MoLice on 2019/J/16.
//
#import "UIImage+QMUITheme.h"
#import "QMUIThemeManager.h"
#import "QMUIThemeManagerCenter.h"
#import "QMUIThemePrivate.h"
#import "NSMethodSignature+QMUI.h"
#import "QMUICore.h"
#import "UIImage+QMUI.h"
#import <objc/message.h>
@interface UIImage (QMUITheme)
@property(nonatomic, assign) BOOL qmui_shouldUseSystemIMP;
+ (nullable UIImage *)qmui_dynamicImageWithOriginalImage:(UIImage *)image tintColor:(UIColor *)tintColor originalActionBlock:(UIImage * (^)(UIImage *aImage, UIColor *aTintColor))originalActionBlock;
@end
@interface QMUIThemeImageCache : NSCache
@end
@implementation QMUIThemeImageCache
- (instancetype)init {
if (self = [super init]) {
// NSCache app init UIApplicationDidEnterBackgroundNotification removeAllObjects removeObserver
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
}
return self;
}
@end
@interface QMUIAvoidExceptionProxy : NSProxy
@end
@implementation QMUIAvoidExceptionProxy
+ (instancetype)proxy {
static dispatch_once_t onceToken;
static QMUIAvoidExceptionProxy *instance = nil;
dispatch_once(&onceToken,^{
instance = [super alloc];
});
return instance;
}
- (void)forwardInvocation:(NSInvocation *)invocation {
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSMethodSignature qmui_avoidExceptionSignature];
}
@end
@interface QMUIThemeImage()
@property(nonatomic, strong) QMUIThemeImageCache *cachedRawImages;
@end
@implementation QMUIThemeImage
static IMP qmui_getMsgForwardIMP(NSObject *self, SEL selector) {
IMP msgForwardIMP = _objc_msgForward;
#if !defined(__arm64__)
// As an ugly internal runtime implementation detail in the 32bit runtime, we need to determine of the method we hook returns a struct or anything larger than id.
// https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/LowLevelABI/000-Introduction/introduction.html
// https://github.com/ReactiveCocoa/ReactiveCocoa/issues/783
// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0042e/IHI0042E_aapcs.pdf (Section 5.4)
Method method = class_getInstanceMethod(self.class, selector);
const char *encoding = method_getTypeEncoding(method);
BOOL methodReturnsStructValue = encoding[0] == _C_STRUCT_B;
if (methodReturnsStructValue) {
@try {
// JSPatch OpenCV
NSMethodSignature *methodSignature = [NSMethodSignature signatureWithObjCTypes:encoding];
if ([methodSignature.debugDescription rangeOfString:@"is special struct return? YES"].location == NSNotFound) {
methodReturnsStructValue = NO;
}
} @catch (__unused NSException *e) {
// Aspect OpenCV
@try {
NSUInteger valueSize = 0;
NSGetSizeAndAlignment(encoding, &valueSize, NULL);
if (valueSize == 1 || valueSize == 2 || valueSize == 4 || valueSize == 8) {
methodReturnsStructValue = NO;
}
} @catch (NSException *exception) {}
}
}
if (methodReturnsStructValue) {
msgForwardIMP = (IMP)_objc_msgForward_stret;
}
#endif
return msgForwardIMP;
}
- (void)dealloc {
_themeProvider = nil;
}
- (id)forwardingTargetForSelector:(SEL)aSelector {
if (self.qmui_rawImage) {
// [self.qmui_rawImage respondsToSelector:aSelector] UIImage
return self.qmui_rawImage;
}
// dealloc UIImage _isNamed image [UIImage imageNamed:] image QMUIThemeImage qmui_rawImage dealloc
NSArray *ignoreSelectorNames = @[@"_isNamed"];
if (![ignoreSelectorNames containsObject:NSStringFromSelector(aSelector)]) {
QMUILogWarn(@"UIImage+QMUITheme", @"QMUIThemeImage 试图执行 %@ 方法,但是 qmui_rawImage 为 nil", NSStringFromSelector(aSelector));
}
return [QMUIAvoidExceptionProxy proxy];
}
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
Class selfClass = [QMUIThemeImage class];
UIImage *instance = UIImage.new;
// QMUIThemeImage UIImage qmui_rawImage
// UIImage QMUIThemeImage qmui_rawImage
[NSObject qmui_enumrateInstanceMethodsOfClass:instance.class includingInherited:NO usingBlock:^(Method _Nonnull method, SEL _Nonnull selector) {
// QMUIThemeImage
if (class_getInstanceMethod(selfClass, selector) != method) return;
const char * typeDescription = (char *)method_getTypeEncoding(method);
class_addMethod(selfClass, selector, qmui_getMsgForwardIMP(instance, selector), typeDescription);
}];
});
}
// QMUIThemeImage NSCopying iOS 12 QMUIThemeImage resizable 使 bug使 UIView+QMUITheme qmui_themeDidChangeByManager:identifier:theme
// https://github.com/Tencent/QMUI_iOS/issues/971
- (id)copyWithZone:(NSZone *)zone {
QMUIThemeImage *image = [[self.class allocWithZone:zone] init];
image.cachedRawImages = self.cachedRawImages;
image.name = self.name;
image.managerName = self.managerName;
image.themeProvider = self.themeProvider;
return image;
}
- (NSString *)description {
return [NSString stringWithFormat:@"<%@: %p>,%@rawImage is %@", NSStringFromClass(self.class), self, self.name.length ? [NSString stringWithFormat:@" name = %@, ", self.name] : @" ", self.qmui_rawImage.description];
}
- (instancetype)init {
return ((id (*)(id, SEL))[NSObject instanceMethodForSelector:_cmd])(self, _cmd);
}
- (BOOL)respondsToSelector:(SEL)aSelector {
if ([super respondsToSelector:aSelector]) {
return YES;
}
return [self.qmui_rawImage respondsToSelector:aSelector];
}
- (BOOL)isKindOfClass:(Class)aClass {
if (aClass == QMUIThemeImage.class) return YES;
return [self.qmui_rawImage isKindOfClass:aClass];
}
- (BOOL)isMemberOfClass:(Class)aClass {
if (aClass == QMUIThemeImage.class) return YES;
return [self.qmui_rawImage isMemberOfClass:aClass];
}
- (BOOL)conformsToProtocol:(Protocol *)aProtocol {
return [self.qmui_rawImage conformsToProtocol:aProtocol];
}
- (NSUInteger)hash {
return (NSUInteger)self.themeProvider;
}
- (BOOL)isEqual:(id)object {
return NO;
}
- (CGSize)size {
return self.qmui_rawImage.size;
}
- (CGImageRef)CGImage {
return self.qmui_rawImage.CGImage;
}
- (CIImage *)CIImage {
return self.qmui_rawImage.CIImage;
}
- (UIImageOrientation)imageOrientation {
return self.qmui_rawImage.imageOrientation;
}
- (CGFloat)scale {
return self.qmui_rawImage.scale;
}
- (NSArray<UIImage *> *)images {
return self.qmui_rawImage.images;
}
- (NSTimeInterval)duration {
return self.qmui_rawImage.duration;
}
- (UIEdgeInsets)alignmentRectInsets {
return self.qmui_rawImage.alignmentRectInsets;
}
- (void)drawAtPoint:(CGPoint)point {
[self.qmui_rawImage drawAtPoint:point];
}
- (void)drawAtPoint:(CGPoint)point blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha {
[self.qmui_rawImage drawAtPoint:point blendMode:blendMode alpha:alpha];
}
- (void)drawInRect:(CGRect)rect {
[self.qmui_rawImage drawInRect:rect];
}
- (void)drawInRect:(CGRect)rect blendMode:(CGBlendMode)blendMode alpha:(CGFloat)alpha {
[self.qmui_rawImage drawInRect:rect blendMode:blendMode alpha:alpha];
}
- (void)drawAsPatternInRect:(CGRect)rect {
[self.qmui_rawImage drawAsPatternInRect:rect];
}
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets {
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
return [self.qmui_rawImage resizableImageWithCapInsets:capInsets];
}];
}
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets resizingMode:(UIImageResizingMode)resizingMode {
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
return [self.qmui_rawImage resizableImageWithCapInsets:capInsets resizingMode:resizingMode];
}];
}
- (UIEdgeInsets)capInsets {
return [self.qmui_rawImage capInsets];
}
- (UIImageResizingMode)resizingMode {
return [self.qmui_rawImage resizingMode];
}
- (UIImage *)imageWithAlignmentRectInsets:(UIEdgeInsets)alignmentInsets {
return [self.qmui_rawImage imageWithAlignmentRectInsets:alignmentInsets];
}
- (UIImage *)imageWithRenderingMode:(UIImageRenderingMode)renderingMode {
return [self.qmui_rawImage imageWithRenderingMode:renderingMode];
}
- (UIImageRenderingMode)renderingMode {
return self.qmui_rawImage.renderingMode;
}
- (UIGraphicsImageRendererFormat *)imageRendererFormat {
return self.qmui_rawImage.imageRendererFormat;
}
- (UITraitCollection *)traitCollection {
return self.qmui_rawImage.traitCollection;
}
- (UIImageAsset *)imageAsset {
return self.qmui_rawImage.imageAsset;
}
- (UIImage *)imageFlippedForRightToLeftLayoutDirection {
return self.qmui_rawImage.imageFlippedForRightToLeftLayoutDirection;
}
- (BOOL)flipsForRightToLeftLayoutDirection {
return self.qmui_rawImage.flipsForRightToLeftLayoutDirection;
}
- (UIImage *)imageWithHorizontallyFlippedOrientation {
return self.qmui_rawImage.imageWithHorizontallyFlippedOrientation;
}
- (BOOL)isSymbolImage {
return self.qmui_rawImage.isSymbolImage;
}
- (CGFloat)baselineOffsetFromBottom {
return self.qmui_rawImage.baselineOffsetFromBottom;
}
- (BOOL)hasBaseline {
return self.qmui_rawImage.hasBaseline;
}
- (UIImage *)imageWithBaselineOffsetFromBottom:(CGFloat)baselineOffset {
return [self.qmui_rawImage imageWithBaselineOffsetFromBottom:baselineOffset];
}
- (UIImage *)imageWithoutBaseline {
return self.qmui_rawImage.imageWithoutBaseline;
}
- (UIImageConfiguration *)configuration {
return self.qmui_rawImage.configuration;
}
- (UIImage *)imageWithConfiguration:(UIImageConfiguration *)configuration {
return [self.qmui_rawImage imageWithConfiguration:configuration];
}
- (UIImageSymbolConfiguration *)symbolConfiguration {
return self.qmui_rawImage.symbolConfiguration;
}
- (UIImage *)imageByApplyingSymbolConfiguration:(UIImageSymbolConfiguration *)configuration {
return [self.qmui_rawImage imageByApplyingSymbolConfiguration:configuration];
}
#pragma mark - <QMUIDynamicImageProtocol>
- (NSString *)qmui_name {
return self.name;
}
- (UIImage *)qmui_rawImage {
if (!_themeProvider) return nil;
QMUIThemeManager *manager = [QMUIThemeManagerCenter themeManagerWithName:self.managerName];
NSString *cacheKey = [NSString stringWithFormat:@"%@%@_%@", self.name ? [NSString stringWithFormat:@"%@_", self.name] : @"", manager.name, manager.currentThemeIdentifier];
UIImage *rawImage = [self.cachedRawImages objectForKey:cacheKey];
if (!rawImage) {
rawImage = self.themeProvider(manager, manager.currentThemeIdentifier, manager.currentTheme).qmui_rawImage;
if (rawImage) [self.cachedRawImages setObject:rawImage forKey:cacheKey];
}
return rawImage;
}
- (BOOL)qmui_isDynamicImage {
return YES;
}
#pragma mark - Translator
// QMUIThemeImage QMUIThemeImage rawImage method swizzle UIImage.class imageWithTintColor UIImage QMUIThemeImage rawImage QMUIThemeImage
- (UIImage *)imageWithTintColor:(UIColor *)color {
return [UIImage qmui_dynamicImageWithOriginalImage:self tintColor:color originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage imageWithTintColor:color];
}];
}
- (UIImage *)imageWithTintColor:(UIColor *)color renderingMode:(UIImageRenderingMode)renderingMode {
return [UIImage qmui_dynamicImageWithOriginalImage:self tintColor:color originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage imageWithTintColor:color renderingMode:renderingMode];
}];
}
- (UIImage *)qmui_imageWithTintColor:(UIColor *)color {
return [UIImage qmui_dynamicImageWithOriginalImage:self tintColor:color originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage qmui_imageWithTintColor:color];
}];
}
// QMUIThemeImage qmui_shouldUseSystemIMP UIImage NO [QMUIThemeImage qmui_imageWithTintColor:QMUIThemeColor]
- (BOOL)qmui_shouldUseSystemIMP {
return NO;
}
@end
@implementation UIImage (QMUITheme)
QMUISynthesizeBOOLProperty(qmui_shouldUseSystemIMP, setQmui_shouldUseSystemIMP)
static BOOL generatorSupportsDynamicColor = NO;
+ (BOOL)qmui_generatorSupportsDynamicColor {
return generatorSupportsDynamicColor;
}
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//
OverrideImplementation(object_getClass(UIImage.class), @selector(qmui_imageWithColor:size:cornerRadius:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^UIImage *(UIImage *selfObject, UIColor *color, CGSize size, CGFloat cornerRadius) {
// call super
UIImage * (^callSuperBlock)(UIColor *, CGSize, CGFloat) = ^UIImage *(UIColor *aColor, CGSize aSize, CGFloat aCornerRadius) {
UIImage * (*originSelectorIMP)(id, SEL, UIColor *, CGSize, CGFloat);
originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *, CGSize, CGFloat))originalIMPProvider();
UIImage * result = originSelectorIMP(selfObject, originCMD, aColor, aSize, aCornerRadius);
return result;
};
if ([color isKindOfClass:QMUIThemeColor.class]) {
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
return callSuperBlock(((QMUIThemeColor *)color).themeProvider(manager, identifier, theme), size, cornerRadius);
}];
}
return callSuperBlock(color, size, cornerRadius);
};
});
OverrideImplementation(object_getClass(UIImage.class), @selector(qmui_imageWithColor:size:cornerRadiusArray:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^UIImage *(UIImage *selfObject, UIColor *color, CGSize size, NSArray<NSNumber *> *cornerRadius) {
// call super
UIImage * (^callSuperBlock)(UIColor *, CGSize, NSArray<NSNumber *> *) = ^UIImage *(UIColor *aColor, CGSize aSize, NSArray<NSNumber *> * aCornerRadius) {
UIImage * (*originSelectorIMP)(id, SEL, UIColor *, CGSize, NSArray<NSNumber *> *);
originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *, CGSize, NSArray<NSNumber *> *))originalIMPProvider();
UIImage * result = originSelectorIMP(selfObject, originCMD, aColor, aSize, aCornerRadius);
return result;
};
if ([color isKindOfClass:QMUIThemeColor.class]) {
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
return callSuperBlock(((QMUIThemeColor *)color).themeProvider(manager, identifier, theme), size, cornerRadius);
}];
}
return callSuperBlock(color, size, cornerRadius);
};
});
//
OverrideImplementation([UIImage class], @selector(qmui_imageWithTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^UIImage *(UIImage *selfObject, UIColor *tintColor) {
UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage qmui_imageWithTintColor:aTintColor];
}];
if (!result) {
// call super
UIImage *(*originSelectorIMP)(id, SEL, UIColor *);
originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *))originalIMPProvider();
result = originSelectorIMP(selfObject, originCMD, tintColor);
}
return result;
};
});
// UIImage imageWithTintColor: UIImage iOS 13 QMUITheme QMUIThemeImage
// imageWithTintColor: imageWithTintColor:renderingMode:
OverrideImplementation([UIImage class], @selector(imageWithTintColor:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^UIImage *(UIImage *selfObject, UIColor *tintColor) {
UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage imageWithTintColor:aTintColor];
}];
if (!result) {
// call super
UIImage *(*originSelectorIMP)(id, SEL, UIColor *);
originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *))originalIMPProvider();
result = originSelectorIMP(selfObject, originCMD, tintColor);
}
return result;
};
});
OverrideImplementation([UIImage class], @selector(imageWithTintColor:renderingMode:), ^id(__unsafe_unretained Class originClass, SEL originCMD, IMP (^originalIMPProvider)(void)) {
return ^UIImage *(UIImage *selfObject, UIColor *tintColor, UIImageRenderingMode renderingMode) {
UIImage *result = [UIImage qmui_dynamicImageWithOriginalImage:selfObject tintColor:tintColor originalActionBlock:^UIImage *(UIImage *aImage, UIColor *aTintColor) {
aImage.qmui_shouldUseSystemIMP = YES;
return [aImage imageWithTintColor:aTintColor renderingMode:renderingMode];
}];
if (!result) {
// call super
UIImage *(*originSelectorIMP)(id, SEL, UIColor *, UIImageRenderingMode);
originSelectorIMP = (UIImage * (*)(id, SEL, UIColor *, UIImageRenderingMode))originalIMPProvider();
result = originSelectorIMP(selfObject, originCMD, tintColor, renderingMode);
}
return result;
};
});
generatorSupportsDynamicColor = YES;
});
}
+ (UIImage *)qmui_imageWithThemeProvider:(UIImage * _Nonnull (^)(__kindof QMUIThemeManager * _Nonnull, __kindof NSObject<NSCopying> * _Nullable, __kindof NSObject * _Nullable))provider {
return [self qmui_imageWithName:nil themeManagerName:QMUIThemeManagerNameDefault provider:provider];
}
+ (UIImage *)qmui_imageWithName:(NSString *)name themeProvider:(UIImage * _Nonnull (^)(__kindof QMUIThemeManager * _Nonnull, __kindof NSObject<NSCopying> * _Nullable, __kindof NSObject * _Nullable))provider {
return [self qmui_imageWithName:name themeManagerName:QMUIThemeManagerNameDefault provider:provider];
}
+ (UIImage *)qmui_imageWithThemeManagerName:(__kindof NSObject<NSCopying> *)managerName provider:(UIImage * _Nonnull (^)(__kindof QMUIThemeManager * _Nonnull, __kindof NSObject<NSCopying> * _Nullable, __kindof NSObject * _Nullable))provider {
return [self qmui_imageWithName:nil themeManagerName:managerName provider:provider];
}
+ (UIImage *)qmui_imageWithName:(NSString *)name themeManagerName:(__kindof NSObject<NSCopying> *)managerName provider:(UIImage * _Nonnull (^)(__kindof QMUIThemeManager * _Nonnull, __kindof NSObject<NSCopying> * _Nullable, __kindof NSObject * _Nullable))provider {
QMUIThemeImage *image = [[QMUIThemeImage alloc] init];
image.cachedRawImages = [[QMUIThemeImageCache alloc] init];
image.name = name;
image.managerName = managerName;
image.themeProvider = provider;
return (UIImage *)image;
}
+ (nullable UIImage *)qmui_dynamicImageWithOriginalImage:(UIImage *)image tintColor:(UIColor *)tintColor originalActionBlock:(UIImage * (^)(UIImage *aImage, UIColor *aTintColor))originalActionBlock {
if (image.qmui_shouldUseSystemIMP) {
image.qmui_shouldUseSystemIMP = NO;
return nil;
}
if ([image isKindOfClass:QMUIThemeImage.class]) {
// image tintColor image
QMUIThemeImage *themeImage = (QMUIThemeImage *)image;
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
return originalActionBlock(themeImage.themeProvider(manager, identifier, theme), tintColor);
}];
}
if ([tintColor isKindOfClass:QMUIThemeColor.class]) {
// image tintColor image image
return [UIImage qmui_imageWithThemeProvider:^UIImage * _Nonnull(__kindof QMUIThemeManager * _Nonnull manager, __kindof NSObject<NSCopying> * _Nullable identifier, __kindof NSObject * _Nullable theme) {
QMUIThemeColor *themeColor = (QMUIThemeColor *)tintColor;
return originalActionBlock(image, themeColor.themeProvider(manager, identifier, theme));
}];
}
return nil;
}
#pragma mark - <QMUIDynamicImageProtocol>
- (NSString *)qmui_name {
return nil;
}
- (UIImage *)qmui_rawImage {
return self;
}
- (BOOL)qmui_isDynamicImage {
return NO;
}
@end