今是昨非

今是昨非

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

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 available online:

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

Details are as follows:

Implementation#

For Webpages:#

Handling for webpages:

  • 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#

  • For color and image processing:

    a. For image processing: Most images are ultimately displayed by calling the setImage method of UIImageView, so hook this method to generate a grayscale image before displaying it, 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, high memory usage, and affects performance when scrolling through multiple images
    //- (UIImage *)grayImage;
    
    // Recommended, relatively low memory usage, does not cause lag, needs to check 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 - image grayscale processing (create grayscale space)
        CGColorSpaceRef colorRef = CGColorSpaceCreateDeviceGray();
        
        // Step 2: Color space context (save image data information)
        // Parameter 1: Memory size (address pointing to this memory area)
        // Parameter 2: Image width
        // Parameter 3: Image height
        // Parameter 4: Pixel bit depth (color space, e.g., 32-bit pixel format and RGB color space, 8-bit)
        // Parameter 5: Memory bits occupied by each row of the image
        // Parameter 6: Color space
        // Parameter 7: Whether the image contains an alpha channel (ARGB channel), note this parameter
        CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, 0, colorRef, kCGImageAlphaPremultipliedLast);
        
        // Release memory
        CGColorSpaceRelease(colorRef);
        if (context == nil) {
            return nil;
        }
        
        // Step 3: Render the image (draw the image)
        // Parameter 1: Context
        // Parameter 2: Rendering area
        // Parameter 3: Source file (original image) (essentially a C/C++ memory area)
        CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
        
        // Step 4: Convert the drawn color space to CGImage (convert to recognizable image type)
        CGImageRef grayImageRef = CGBitmapContextCreateImage(context);
        
        // Step 5: Convert the C/C++ image CGImage to an object-oriented UIImage (convert to an image type recognized by iOS programs)
        UIImage *dstImage = [UIImage imageWithCGImage:grayImageRef];
        
        // Release memory
        CGContextRelease(context);
        CGImageRelease(grayImageRef);
        return dstImage;
    }
    
    @end
    
    

    However, after running the project, you will find that the color of the keyboard in the project has also turned black because the keyboard also uses images. Therefore, you need to check whether 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. For color processing:

    All color settings ultimately go through UIColor's colorWithRed:green:blue:alpha:, so hook this method to generate a gray color to return and display. 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
    
      ```
    
    
  • For overall interface processing

    a. Method 1: Create a gray view that does not respond to events and 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 black and white effects in iOS APP interfaces is not recommended to set images and colors separately, as most APP homepages are not H5. Therefore, it is recommended to create a gray view that does not respond to events and add it to the top layer of the page to be grayed out or the global window.

Complete code is available on GitHub: GrayTheme_iOS
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.