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 aCategory
for UIImage, and generate a grayscale image - Hook the
colorWithRed:green:blue:alpha:
method of UIColor
- Hook the
- 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
- Create a gray view that does not respond to events and add it to the top layer of the
- For images and colors separately:
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 ofUIImageView
, so hook this method to generate a grayscale image before displaying it, 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, 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
'scolorWithRed: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'