今是昨非

今是昨非

日出江花红胜火,春来江水绿如蓝

iOS界面黑白实现

背景#

iOS APP 界面黑白效果实现调研整理,总的来说网上目前有下面几种方法:

  • 针对 H5 网页:注入 js 代码
  • 针对 APP 原生界面:
    • 针对图片和颜色单独设置
      • hook UIImageView 的setImage方法,添加 UIImage 的Category,生成灰色图片
      • hook UIColor 的colorWithRed:green:blue:alpha:方法
    • 针对界面整体处理
      • 创建一个灰色 view,设置不响应事件,然后添加在window最上层
      • 给 App 整体添加灰色滤镜

具体如下:

实现#

针对网页:#

针对网页的处理:

  • 如果有基类,可以直接在基类初始化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. 针对图片的处理:大部分图片的显示都是最后都是调用UIImageViewsetImage方法,所以hook这个方法,在显示前生成灰色的图片,然后在赋值,代码如下:

    hook UIImageViewsetImage方法

    
    #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. 针对颜色的处理:

    所有颜色的设置,最终都会走UIColorcolorWithRed: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'

参考#

加载中...
此文章数据所有权由区块链加密技术和智能合约保障仅归创作者所有。