BASEプロダクトチームブログ

ネットショップ作成サービス「BASE ( https://thebase.in )」、ショッピングアプリ「BASE ( https://thebase.in/sp )」のプロダクトチームによるブログです。

Try! Swift 2017 - 2日目「Swiftで堅牢なカラーシステムを構築する」

こんにちは、iOSエンジニアの遠藤(秀)です。 3/2(木)〜 3/4(土)の3日間に渡って開催された世界的なイベント「Try! Swift 2017」に参加してきました。

2日目のセッション「Swiftで堅牢なカラーシステムを構築する」について、まとめてみました。

セッション概要

これまで以上に多くの企業が、新しく増え続けるユーザーに今までよりも魅力的なアプリだとアピールするために、アプリを再設計しています。この講演ではあらゆる規模のプロジェクトにスケールできる堅牢なカラーシステムを構築するための戦略について議論します。これらのアプローチはデザイン上の決定を迅速に繰り返すのに役立ち、実行時にカラーパレットのテーマを変更するようなこともできるかもしれません。さらに、iOS 10で導入された新しいカラーフィルターのアクセシビリティ機能を使用して、色覚の問題を抱える人を支援することにも応用できることを示すデモンストレーションも行います。



キーワード 、まとめ

・プロトコル、エクステンションで堅牢なカラーシステムにする。
・ランタイムでカラーテーマを変更する。
・色覚障がいを含む、全てのユーザについて考慮すべき。



BASEアプリでのカラーマネージメント

BASEアプリでは、なるべくコード量を減らすためにclrファイルを作成して、xibファイル側に色情報を持たせるように移行している途中でした。

f:id:base_hideo:20170307150102p:plain

clrファイルの作成方法


セッション中の説明にもあったとおり、clrファルでカラーパレットを共有したとしても、カラーパレットに変更が起こったときには、全てのxibファイルを書き直す作業が必要になります。また、プログラマティックにカラーテーブルをダイナミックに変更することができません。

これらの問題を解決するので手法として、今回のセッションはとても参考になる内容でした。


エクステンションの作成

UIColorのエクステンションを作成してstructを定義します。

extension UIColor {
    struct Palette {
        static let ceruleanBlue = UIColor(red:    0.0 / 255.0, green: 158.0 / 255.0, blue: 220.0 / 255.0, alpha: 1.0)
        static let cannonPurple = UIColor(red:  147.0 / 255.0, green:  78.0 / 255.0, blue: 132.0 / 255.0, alpha: 1.0)
        static let mulberryRed = UIColor(red:  197.0 / 255.0, green:  81.0 / 255.0, blue:  82.0 / 255.0, alpha: 1.0)
        static let fireBushOrange = UIColor(red:  225.0 / 255.0, green: 148.0 / 255.0, blue:  51.0 / 255.0, alpha: 1.0)
        static let saffronYellow  = UIColor(red:  242.0 / 255.0, green: 190.0 / 255.0, blue:  46.0 / 255.0, alpha: 1.0)
        static let sushiGreen = UIColor(red:  118.0 / 255.0, green: 184.0 / 255.0, blue:  59.0 / 255.0, alpha: 1.0)
        static let black = UIColor(white: 44.0 / 255.0, alpha: 1.0)
        static let white = UIColor.white
    }
}


・クラス変数 / クラスメソッドにて色を取得します。

class var primaryText: UIColor {
    return Palette.black
}

class func contentBackground() -> UIColor {
    return Palette.white
}


・デザイナー・エンジニア間でZeplinを使用している場合、カラーテーブルをエクステンションとして出力することが出来て便利です。

f:id:base_hideo:20170307150119p:plain


カラーテーマの適用

・アプリの外観を変更(LINEの着せ替えとか。)するような場合、ノティフィケーションを使用してカラーテーマを変更します。

最初にColorUpdatableプロトコルを定義します。

/// A protocol which denotes types which can update their colors.
protocol ColorUpdatable {
    /// The theme for which to update colors.
    var theme: Theme { get set }
    
    /// A function that is called when colors should be updated.
    func updateColors(for theme: Theme)
}


ノティフィケーションの名前を定義するためのプロトコル、ColorChangeObservingを定義します。

/// A protocol for responding to `didChangeColorTheme` custom notifications.
protocol ColorThemeObserving {
    /// Registers observance of `didChangeColorTheme` custom notifications.
    func addDidChangeColorThemeObserver(notificationCenter:
                                                   NotificationCenter)
     /// Removes observance of `didChangeColorTheme` custom notifications.
    func removeDidChangeColorThemeObserver(notificationCenter:
                                                   NotificationCenter)
    /// Responds to `didChangeColorTheme` custom notifications.
    func didChangeColorTheme(notification: Notification)
 }


ヘルパー用の関数を作ります。

private extension ColorThemeObserving {
     /// Returns the theme specified by the `didChangeColorTheme` notification’s `userInfo`.
     func theme(from notification: Notification) -> Theme? {
         // . . .
         return theme
     }

     /// Updates the colors of `ColorUpdatable`-conforming objects.
     func updateColors(from notification: Notification) {
         guard let theme = theme(from: notification) else { return }
         if var colorUpdatableObject = self as? ColorUpdatable, theme != colorUpdatableObject.theme {
             colorUpdatableObject.theme = theme
             colorUpdatableObject.updateColors(for: theme)
        }
    }
}


カラーテーマが変更されたノティフィケーションを受信します。

extension UIViewController: ColorThemeObserving {
    @objc func didChangeColorTheme(_ notification: Notification) {
        updateColors(from: notification)
    }
}


テーブルビュー、コレクションビューでも同様にノティフィケーションを受信します。

extension UITableViewController {
     @objc override func didChangeColorTheme(_ notification: Notification) {
         updateColors(from: notification)
         tableView.reloadData()
     }
}

extension UICollectionViewController {
     @objc override func didChangeColorTheme(_ notification: Notification) {
         updateColors(from: notification)
         collectionView?.reloadData()
     }
}


テーマ毎に色の変更を行います。

extension UIColor {
    class func backgroundContent(for theme: Theme) -> UIColor {
        switch theme {
            case .light:
                return Palette.white
            case .dark:
                return Palette.black
            }
    }
    // . . .
}


ビューコントラー側でテーマカラーを適用します。

extension ViewController: ColorUpdatable {
    func updateColors(for theme: Theme) {
        view.backgroundColor = .contentBackground(for: theme)
        childView.updateColors(for: theme)
        // . . .
    } 
}



色覚障がいについて

・男性の8%、女性の0.5%が何らかの色覚障がいを持っているとのことでしたが、実際には地域によって割合は異なります。

色に意味を持たせて、緑色は正常、赤色は異常という文化が広がっていますが、それらを識別できないと、そのメッセージは伝わりません。

iOS10では、色覚障がい者向けのカラーフィルター設定を持っており、全ての色のコントラストに適用します。

f:id:base_hideo:20170307150130p:plain

色覚障がい者向けのフィルター設定を適用します。

import InclusiveColor

extension UIColor {
    class func primaryText(for theme: Theme,
                        blindnessType: InclusiveColor.BlindnessType = .normal) ->      UIColor {
        let color: UIColor = {
            switch theme {
            case .light:
                return Palette.black
            case .dark:
                return Palette.white
            }
        }()
        return color.inclusiveColor(for: blindnessType)
    }
}