今是昨非

今是昨非

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

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'

參考#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。