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 aCategory
for UIImage to generate grayscale images - Hook the
colorWithRed:green:blue:alpha:
method of UIColor
- Hook the
- 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
- Create a gray view, set it to not respond to events, and then add it to the top layer of the
- Handling images and colors separately
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 ofUIImageView
, 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 ofUIImageView
:#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
'scolorWithRed: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'