Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

SF Symbols の視認性を向上する

こんにちは、iOSエンジニアの西村です。

WWDC22で SF Symbols 4 が発表され、利用できるシンボルの種類が4,000を超えました!
種類が増えたことによって今後利用する場面が多くなるのではないでしょうか。

はじめに

今回の記事ではSF Symbolsが見づらくなる場面を想定し、視認性が向上するように対策を考えていこうと思います。

見づらくなる場面としては、白から黒へと背景色が動的に変わる画面上に黒色のシンボルが配置された場合を考えてみます。このとき背景色が黒になった場合、黒色シンボルとのコントラストが無くなり見づらい状態になります。
この見づらい状態を改善するために、シンボルの下にコントラスト差を意識した丸い背景画像を配置して、視認性の向上を図ろうと思います。

※ 簡単な実現方法としては、SF Symbolsの 『{SymbolName}.circle.fill』 を使うという手があるのですが、全てのシンボルに対応している訳ではないのでこちらは使いません。

1. シンボルの用意

まずは使用するシンボルを用意します。
シンボルはxmarkで、サイズは18pt、カラーは黒色を指定しました。

let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 18.0))
let symbolImage = UIImage(systemName: "xmark", withConfiguration: configuration)!
      .withTintColor(.black, renderingMode: .alwaysOriginal)

2. シンボルの背景に丸い画像を配置

1で用意したUIImageのシンボルに丸く塗りつぶされた背景画像を簡単に追加できるようにしたいので、UIImageのextensionにメソッドを追加していきます。
メソッドには、丸い画像を描画してその中央にシンボルを配置するという処理を追加します。

まずは、画像を描画するクラスであるUIGraphicsImageRendererを使って丸画像を描画していきます。

func roundBackgroundImage(size: CGSize, tintColor: UIColor = .clear) -> UIImage {
    // 画像の描画処理
    return UIGraphicsImageRenderer(size: size).image { context in
        tintColor.setFill() // 塗りつぶしの色を指定
        context.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size)) // 指定されたサイズ内に収まる楕円が描画され、塗りつぶされる
    }
}

次に、シンボルを丸画像の中央に配置するように調整していきます。

func roundBackgroundImage(size: CGSize, tintColor: UIColor = .clear) -> UIImage {
    // シンボルを丸画像の中央に描画するための座標を計算
    var centerPoint: CGPoint {
        let centerX = (size.width - self.size.width) / 2.0
        let centerY = (size.height - self.size.height) / 2.0
        return CGPoint(x: centerX, y: centerY)
    }

    return UIGraphicsImageRenderer(size: size).image { [weak self] context in
        guard let self = self else { return }
        tintColor.setFill()
        context.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size))

        // シンボルを丸画像の中央に描画
        self.draw(in: .init(origin: centerPoint, size: self.size))
    }
}

全体コード

import UIKit

public extension UIImage {

    func roundBackgroundImage(size: CGSize, tintColor: UIColor = .clear) -> UIImage {
        var centerPoint: CGPoint {
            let centerX = (size.width - self.size.width) / 2.0
            let centerY = (size.height - self.size.height) / 2.0
            return CGPoint(x: centerX, y: centerY)
        }

        return UIGraphicsImageRenderer(size: size).image { [weak self] context in
            guard let self = self else { return }
            tintColor.setFill()
            context.cgContext.fillEllipse(in: CGRect(origin: .zero, size: size))

            self.draw(in: .init(origin: centerPoint, size: self.size))
        }
    }

}

3. 完成

完成したコードを使って、シンボルを表示させてみます。
これで背景色とシンボルのコントラストがない場合でも、ある程度みやすくなったのではないでしょうか。

let configuration = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 18.0, weight: .medium))
let symbolImage = UIImage(systemName: "xmark", withConfiguration: configuration)!
        .withTintColor(.black, renderingMode: .alwaysOriginal)
        .roundBackgroundImage(size: CGSize(width: 44, height: 44), tintColor: .lightGray.withAlphaComponent(0.8))

おわりに

UIGraphicsImageRendererを使うことによってシンボルの背景に丸い画像を描画して、視認性を向上することができました。丸以外の形にも対応することが可能なので、表示する画面に合わせて形を変えても良さそうです。

しかし、シンボルの表示毎に描画処理が走るので、パフォーマンスを考えると別の策を考えたいところではありますが、今回はサイズや色を柔軟に変更出来るようにしたかったのでこのような解決法となりました。

背景色とシンボルが同化してしまい見づらくなってしまった際の参考にしていただければ幸いです。


ヤプリでは一緒に働く仲間を募集しています!「興味をもった!」という方や、「もう少し具体的な話が聞いてみたい」と思った方はぜひカジュアル面談にお越しください。

open.talentio.com