背景#
iOS APP インターフェースの白黒効果実現に関する調査整理。全体的に、現在オンラインには以下の方法があります:
- H5 ウェブページ向け:js コードを注入
- APP ネイティブインターフェース向け:
- 画像と色を個別に設定
- UIImageView の
setImage
メソッドをフックし、UIImage のCategory
を追加してグレー画像を生成 - UIColor の
colorWithRed:green:blue:alpha:
メソッドをフック
- UIImageView の
- インターフェース全体を処理
- グレーの view を作成し、イベントを無視するように設定し、
window
の最上層に追加 - アプリ全体にグレーのフィルターを追加
- グレーの view を作成し、イベントを無視するように設定し、
- 画像と色を個別に設定
具体的には以下の通りです:
実装#
ウェブページ向け:#
ウェブページの処理:
-
基本クラスがある場合、基本クラスで
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. 画像の処理:ほとんどの画像表示は最終的に
UIImageView
のsetImage
メソッドを呼び出すため、このメソッドをフックし、表示前にグレーの画像を生成してから代入します。コードは以下の通り:UIImageView
のsetImage
メソッドをフック:#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(); // 第二歩:色空間のコンテキスト(画像データ情報を保存) CGContextRef context = CGBitmapContextCreate(nil, width, height, 8, 0, colorRef, kCGImageAlphaPremultipliedLast); // メモリを解放 CGColorSpaceRelease(colorRef); if (context == nil) { return nil; } // 第三歩:画像をレンダリング(画像を描画) 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]; // selfの最後のsuperViewを見つける UIView *superView = self; NSString *className = @""; while (superView.superview) { superView = superView.superview; className = NSStringFromClass([superView class]); } // 最後のsuperViewがキーボードウィンドウであれば、grayImageを設定しない if ([className containsString:@"UIRemoteKeyboardWindow"]) { [self swizzleSetImage:image]; } else { [self swizzleSetImage:grayImage]; } }
再度実行して効果を確認すると、キーボードが正常に表示されることがわかります。
b. 色の処理:
すべての色の設定は最終的に
UIColor
のcolorWithRed:green:blue:alpha:
を通過するため、このメソッドをフックしてグレーの色を生成して返し表示します。コードは以下の通り:#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. 方法 1:グレーの 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. 方法 2:アプリ全体にグレーのフィルターを追加し、同様に
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'