今是昨非

今是昨非

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

Black and White Implementation of iOS Interface

Background#

Research and organization of the implementation of black and white effects in iOS APP interfaces. Overall, there are several methods currently available online:

  • For H5 web pages: Injecting JS code
  • For native APP interfaces:
    • Handling images and colors separately
      • Hook the setImage method of UIImageView, add a Category for UIImage to generate grayscale images
      • Hook the colorWithRed:green:blue:alpha: method of UIColor
    • Overall interface processing
      • Create a gray view, set it to not respond to events, and then add it to the top layer of the window
      • Add a gray filter to the entire App

Details are as follows:

Implementation#

For Web Pages:#

Handling for web pages:

  • If there is a base class, you can directly add the following code where the base class initializes WKWebview:

        WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init];
        // JS script
        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%)';";
        // Injection
        WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
        [config.userContentController addUserScript:wkUScript];
    
  • If there is no base class, implement it through 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 script
        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%)';";
        // Injection
        WKUserScript *wkUScript = [[WKUserScript alloc] initWithSource:jScript injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES];
                     
        WKUserContentController *wkUController = [[WKUserContentController alloc] init];
           [wkUController addUserScript:wkUScript];
        // Configuration object
        WKWebViewConfiguration *wkWebConfig = [[WKWebViewConfiguration alloc] init];
        wkWebConfig.userContentController = wkUController;
        configuration = wkWebConfig;
        WKWebView *webView = [self swizzleInitWithFrame:frame configuration:configuration];
        return webView;
    }
    
    @end
    

Handling for Native APP Interfaces#

  • Handling for colors and images:

    a. Handling for images: Most images are ultimately displayed by calling the setImage method of UIImageView, so hook this method to generate a grayscale image before displaying it, and then assign it. The code is as follows:

    Hook the setImage method of UIImageView:

    
    #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
    
    

    The code to generate a grayscale image is as follows:

    
    #import <UIKit/UIKit.h>
    
    @interface UIImage (Category)
    
    // Not recommended due to high memory usage and performance impact when scrolling through multiple images
    //- (UIImage *)grayImage;
    
    // Recommended for relatively low memory usage and no lag, but be cautious if the image contains an alpha channel (ARGB channel)
    - (UIImage *)anotherGrayImage;
    
    @end
    
    // Reference: 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];
        // Set saturation to 0, range 0-2, default is 1
        [filter setValue:0 forKey:@"inputSaturation"];
        
        // Get the filtered image
        CIImage *outputImage = [filter outputImage];
        // Convert the image, create a GPU-based CIContext object
        CIContext *context = [CIContext contextWithOptions:nil];
        CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];
        UIImage *newImage = [UIImage imageWithCGImage:cgImage];
        
        // Release C objects
        CGImageRelease(cgImage);
        return newImage;
    }
    
    - (UIImage *)anotherGrayImage {
        // Note the size of the image here
        CGFloat scale = [UIScreen mainScreen].scale;
        NSInteger width = self.size.width * scale;
        NSInteger height = self.size.height * scale;
        
        // Step 1: Create color space for grayscale processing
        CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceGray();
        
        // Step 2: Create context for color space (to save image data)
        CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, 0, colorRef, kCGImageAlphaPremultipliedLast);
        
        // Release memory
        CGColorSpaceRelease(colorRef);
        if (context == nil) {
            return nil;
        }
        
        // Step 3: Render the image
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
        
        // Step 4: Convert the rendered color space to CGImage
        CGImageRef grayImageRef = CGBitmapContextCreateImage(context);
        
        // Step 5: Convert the C/C++ image CGImage to an object-oriented UIImage
        UIImage *dstImage = [UIImage imageWithCGImage:grayImageRef];
        
        // Release memory
        CGContextRelease(context);
        CGImageRelease(grayImageRef);
        return dstImage;
    }
    
    @end
    
    

    However, after running the project, you may find that the keyboard color has also turned black because the keyboard also uses images. Therefore, you need to check if the imageView is for the keyboard before replacing it. If it is, do not process it; otherwise, replace it with black. Modify the content of UIImageView+Swizzle.m as follows:

    
      #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];
          }
      }
    
    

    Run again to check the effect, and you will find that the keyboard displays normally.

    b. Handling for colors:

    All color settings ultimately go through UIColor's colorWithRed:green:blue:alpha:, so hook this method to generate and return a gray color. The code is as follows:

    
      #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
    
      ```
    
    
  • Overall interface processing

    a. Method 1: Create a gray view, set it to not respond to events, and then add it to the top layer of the window

      #import <UIKit/UIKit.h>
    
      /// Topmost view, carrying the filter, does not accept or intercept any touch events
      @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 {
          // Do not handle click events
          return nil;
      }
    
      @end
    
    

    b. Method 2: Add a gray filter to the entire App, also added to the top layer of the window

    // Get RGBA color values
    CGFloat r,g,b,a;
    [[UIColor lightGrayColor] getRed:&r green:&g blue:&b alpha:&a];
    // Create filter
    id cls = NSClassFromString(@"CAFilter");
    id filter = [cls filterWithName:@"colorMonochrome"];
    // Set filter parameters
    [filter setValue:@[@(r),@(g),@(b),@(a)] forKey:@"inputColor"];
    [filter setValue:@(0) forKey:@"inputBias"];
    [filter setValue:@(1) forKey:@"inputAmount"];
    // Set to window
    window.layer.filters = [NSArray arrayWithObject:filter];
    

Summary#

The implementation of the black and white effect in iOS APP interfaces is not recommended to handle images and colors separately, as most APP homepages are not H5. Therefore, it is recommended to create a gray view, set it to not respond to events, and then add it to the top layer of the page or the global window to achieve the desired effect.

The complete code is available on GitHub: GrayTheme_iOS
It can be installed via CocoaPods:
pod 'GrayTheme'

References#

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.