背景#
iOS APP 介面黑白效果實現調研整理,總的來說網上目前有下面幾種方法:
- 針對 H5 網頁:注入 js 代碼
- 針對 APP 原生介面:
- 針對圖片和顏色單獨設置
- hook UIImageView 的
setImage
方法,添加 UIImage 的Category
,生成灰色圖片 - hook UIColor 的
colorWithRed:green:blue:alpha:
方法
- hook UIImageView 的
- 針對介面整體處理
- 創建一個灰色 view,設置不響應事件,然後添加在
window
最上層 - 給 App 整體添加灰色濾鏡
- 創建一個灰色 view,設置不響應事件,然後添加在
- 針對圖片和顏色單獨設置
具體如下:
實現#
針對網頁:#
針對網頁的處理:
-
如果有基類,可以直接在基類初始化
WKWebview
的地方,添加如下代碼:WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; // js腳本 NSString *jScript = @"var filter = '-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%); -ms-filter:grayscale(100%); -o-filter:grayscale(100%) filter:grayscale(100%);';document.getElementsByTagName('html')[0].style.filter = 'grayscale(100%)';"; // 注入 WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; [config.userContentController addUserScript:wkUScript];
-
如果沒有基類,則通過
Swizzle_Method
實現:#import "WKWebView+Swizzle.h" @implementation WKWebView (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(initWithFrame:configuration:); SEL swizzleSelector = @selector(swizzleInitWithFrame:configuration:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzleMethod = class_getInstanceMethod(class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); } - (instancetype)swizzleInitWithFrame:(CGRect)frame configuration:(WKWebViewConfiguration *)configuration { // js腳本 NSString *jScript = @"var filter = '-webkit-filter:grayscale(100%);-moz-filter:grayscale(100%); -ms-filter:grayscale(100%); -o-filter:grayscale(100%) filter:grayscale(100%);';document.getElementsByTagName('html')[0].style.filter = 'grayscale(100%)';"; // 注入 WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; WKUserContentController *wkUController = [[WKUserContentController alloc] init]; [wkUController addUserScript:wkUScript]; // 配置對象 WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init]; wkWebConfig.userContentController = wkUController; configuration = wkWebConfig; WKWebView *webView = [self swizzleInitWithFrame:frame configuration:configuration]; return webView; } @end
針對 APP 原生介面的處理#
-
針對顏色和圖片處理:
a. 針對圖片的處理:大部分圖片的顯示都是最後都是調用
UIImageView
的setImage方法
,所以hook
這個方法,在顯示前生成灰色的圖片,然後在賦值,代碼如下:hook
UIImageView
的setImage方法
:#import "UIImageView+Swizzle.h" #import "UIImage+Category.h" @implementation UIImageView (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(setImage:); SEL swizzleSelector = @selector(swizzleSetImage:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzleMethod = class_getInstanceMethod(class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); } - (void)swizzleSetImage:(UIImage *)image { UIImage *grayImage = [image anotherGrayImage]; [self swizzleSetImage:grayImage]; } @end
生成灰色圖片的代碼如下:
#import <UIKit/UIKit.h> @interface UIImage (Category) // 不建議使用,內存佔用大,且在多圖列表上滑動時,影響性能,造成卡頓 //- (UIImage *)grayImage; // 推薦使用,內存相對小,不卡頓,需注意圖片是否包含A通道(ARGB通道) - (UIImage *)anotherGrayImage; @end // 參考https://blog.csdn.net/iOSxiaodaidai/article/details/113553395 #import "UIImage+Category.h" @implementation UIImage (Category) - (UIImage *)grayImage { CIImage *beginImage = [CIImage imageWithCGImage:self.CGImage]; CIFilter *filter = [CIFilter filterWithName:@"CIColorControls"]; [filter setValue:beginImage forKey:kCIInputImageKey]; // 修改飽和度為0,範圍0-2,默認為1 [filter setValue:0 forKey:@"inputSaturation"]; // 得到過濾後的圖片 CIImage *outputImage = [filter outputImage]; // 轉換圖片,創建基於GPU的CIContext對象 CIContext *context = [CIContext contextWithOptions:nil]; CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]]; UIImage *newImage = [UIImage imageWithCGImage:cgImage]; // 釋放C對象 CGImageRelease(cgImage); return newImage; } - (UIImage *)anotherGrayImage { // 注意這裡圖片的大小 CGFloat scale = [UIScreen mainScreen].scale; NSInteger width = self.size.width * scale; NSInteger height = self.size.height * scale; // 第一步:創建顏色空間——圖片灰度處理(創建灰度空間) CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceGray(); //第二步:顏色空間的上下文(保存圖像數據信息) //參數1:內存大小(指向這塊內存區域的地址)(內存地址) //參數2:圖片寬 //參數3:圖片高 //參數4:像素位數(顏色空間,例如:32位像素格式和RGB顏色空間,8位) //參數5:圖片每一行佔用的內存比特數 //參數6:顏色空間 //參數7:圖片是否包含A通道(ARGB通道),注意這個參數 CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, 0, colorRef, kCGImageAlphaPremultipliedLast); // 釋放內存 CGColorSpaceRelease(colorRef); if (context == nil) { return nil; } //第三步:渲染圖片(繪制圖片) //參數1:上下文 //參數2:渲染區域 //參數3:源文件(原圖片)(說白了現在是一個C/C++的內存區域) CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage); //第四步:將繪制顏色空間轉成CGImage(轉成可識別圖片類型) CGImageRef grayImageRef = CGBitmapContextCreateImage(context); //第五步:將C/C++ 的圖片CGImage轉成面向對象的UIImage(轉成iOS程序認識的圖片類型) UIImage *dstImage = [UIImage imageWithCGImage:grayImageRef]; //釋放內存 CGContextRelease(context); CGImageRelease(grayImageRef); return dstImage; } @end
但是運行項目後,會發現,項目中鍵盤的顏色也變成一片片黑色了,因為鍵盤也是用的圖片,所以要在替換前判斷是否是鍵盤的 imageView,如果是則不處理,否則替換為黑色,將
UIImageView+Swizzle.m
內容修改為如下:#import "UIImageView+Swizzle.h" #import <objc/runtime.h> #import "UIImage+Category.h" @implementation UIImageView (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(setImage:); SEL swizzleSelector = @selector(swizzleSetImage:); Method originalMethod = class_getInstanceMethod(class, originalSelector); Method swizzleMethod = class_getInstanceMethod(class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); } - (void)swizzleSetImage:(UIImage *)image { UIImage *grayImage = [image anotherGrayImage]; // find self's last superView UIView *superView = self; NSString *className = @""; while (superView.superview) { superView = superView.superview; className = NSStringFromClass([superView class]); } // if lastSuperView is keyboard window, then do not set grayImage if ([className containsString:@"UIRemoteKeyboardWindow"]) { [self swizzleSetImage:image]; } else { [self swizzleSetImage:grayImage]; } }
再次運行查看效果,可以發現鍵盤正常顯示了。
b. 針對顏色的處理:
所有顏色的設置,最終都會走
UIColor
的colorWithRed:green:blue:alpha:
,所以通過hook
這個方法,生成灰色的顏色返回並顯示,代碼如下:#import "UIColor+Swizzle.h" @implementation UIColor (Swizzle) + (void)load { Class class = [self class]; SEL originalSelector = @selector(colorWithRed:green:blue:alpha:); SEL swizzleSelector = @selector(swizzle_colorWithRed:green:blue:alpha:); Method originalMethod = class_getClassMethod(class, originalSelector); Method swizzleMethod = class_getClassMethod(class, swizzleSelector); method_exchangeImplementations(originalMethod, swizzleMethod); } + (UIColor *)swizzle_colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha { CGFloat grayValue = 0.299*red + 0.587*green + 0.114*blue; UIColor *gray = [UIColor colorWithWhite:grayValue alpha:alpha]; return gray; } @end ```
-
針對介面整體處理
a. 方法一:創建一個灰色 view,設置不響應事件,然後添加在
window
最上層#import <UIKit/UIKit.h> /// 最頂層視圖,承載濾鏡,自身不接受、不攔截任何觸摸事件 @interface UIViewOverLay : UIView @end #import "UIViewOverLay.h" @implementation UIViewOverLay - (instancetype)init { self = [super init]; if (self) { [self setupSubviews]; } return self; } - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { [self setupSubviews]; } return self; } - (void)setupSubviews { self.translatesAutoresizingMaskIntoConstraints = NO; self.backgroundColor = [UIColor lightGrayColor]; self.layer.compositingFilter = @"saturationBlendMode"; } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { // 不處理點擊事件 return nil; } @end
b. 方法二:給 App 整體添加灰色濾鏡,同樣加在
window
最上層//獲取RGBA顏色數值 CGFloat r,g,b,a; [[UIColor lightGrayColor] getRed:&r green:&g blue:&b alpha:&a]; //創建濾鏡 id cls = NSClassFromString(@"CAFilter"); id filter = [cls filterWithName:@"colorMonochrome"]; //設置濾鏡參數 [filter setValue:@[@(r),@(g),@(b),@(a)] forKey:@"inputColor"]; [filter setValue:@(0) forKey:@"inputBias"]; [filter setValue:@(1) forKey:@"inputAmount"]; //設置給window window.layer.filters = [NSArray arrayWithObject:filter];
總結#
iOS APP 介面黑白效果實現,不建議圖片和顏色單獨分開設置,而大部分 APP 首頁不是 H5 的。所以建議創建一個灰色 view,設置不響應事件,然後添加在要置灰的頁面或者全局window
的最上層即可。
完整代碼放在 Github:GrayTheme_iOS
可通過CocoaPods
安裝:
pod 'GrayTheme'