Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS 12以下でDark Modeに対応した地獄の話 #iosdc #a #b/dark_mode_iosdc_2019

fromkk
September 07, 2019

iOS 12以下でDark Modeに対応した地獄の話 #iosdc #a #b/dark_mode_iosdc_2019

# iOS 12以下でDark Modeに対応した地獄の話

TimersでiOSエンジニアをしている植岡和哉と申します。Twitter, Github, Qiitaなどは全てfromkkでいう名前で活動しています。
WWDC 2019でiOS 13向けにDark Modeが発表されました。
・Dark Modeは暗い背景に明るいテキストやコンテンツを表示するカラースキームの事を言います。カスタムカラーを利用する場合はコントラスト比4.5:1〜7:1を目指すと良いらしいです。
・目やバッテリーに優しいとされています。特に夜間に明るい画面は見たくないですね。
・iOS 13ではLight Mode、Dark Modeそれぞれに色の微調整が行われています。ちなみに色に名前や役割を付けてアプリ上で扱いやすくする事をセマンティックカラーと呼びます。
僕が個人的に開発している# Typeというアプリにダークモード機能を追加しました。設定画面からON/OFFするとアプリ全体の色が変更されます。

## Dark Modeを自分で実装する時の考え方

色を整理します。セマンティックカラーを意識してアプリ全体で利用する色を管理します。
UIからのイベントを受け取ります。
UIが変更された事を受け取ってアプリ全体で管理している色のモードを変更します。
モードが変更された事を検知してUIに反映させる
ColorSetというライブラリを開発しました。
https://github.com/fromkk/ColorSet
色の種類が変更された事を検知してUIに色を設定する事を簡素化出来るライブラリです。subscribeとbindというメソッドを使い分ければある程度柔軟に利用が可能かと思います。
よかったらスターください!
ここまでで対応方法は大体わかりましたね。実際のプロダクトに適用してみよう!と思ったのが地獄の始まりです。
(以下地獄絵図)

## 簡単に対処方法

Interface Builderやコードでの色の直接指定は禁止です。
動的変更に対応出来る様にしましょう。
Appearanceの指定で済むならそれが一番良いです。
画像が既存の背景色を前提としている場合は差し替えましょう。
差し替えが出来ない画像はイベントハンドリングで対応します。
アイコンの様な一色の画像はtintColorを設定すれば色が変わる様にします。
ステータスバーはモードが変更された時に更新する様にします。

# まとめ

・最初は色変更ぐらいで済むかと思っていたら、想像以上に対応しないといけない箇所が出てきて大変でした。
・特に元々背景色が白のアプリだと、背景色が指定済みかどうかが分りづらいです。。背景も意識して設定する事が大事だと思いました。
・縦や横画面、iPadでのSplit Viewなど様々な画面の確認が必要でした。全画面網羅のUIテストとスクリーンショットが求められます。

fromkk

September 07, 2019
Tweet

More Decks by fromkk

Other Decks in Programming

Transcript

  1. J04ҎԼͰ%BSL.PEFʹରԠͨ͠஍ࠈͷ࿩
    !GSPNLLJ04%$BC
    !1

    View full-size slide

  2. 1SPpMF
    struct Profile {
    let name = "Kazuya Ueoka”
    let nickname = "͔ͬ͘Μ"
    let twitter = "@fromkk"
    let github = "fromkk"
    let qiita = "fromkk"
    let company = "Timers Inc."
    }

    2

    View full-size slide

  3. %BSL.PEF❓
    3

    View full-size slide

  4. 88%$ͰJ04޲͚ʹ%BSL.PEF͕ൃද
    !5

    View full-size slide

  5. %BSL.PEFͱ͸ʁ
    w ҉͍എܠʹ໌Δ͍ςΩετ΍ίϯςϯπΛදࣔ͢ΔΧϥʔεΩʔϜͷࣄ
    w ίϯτϥετൺΛ໨ࢦ͢

    w ໨΍όοςϦʔʹ༏͍͠ͱ͞Ε͍ͯΔ
    w ໷ؒʹ໌Δ͍ը໘ݟͨ͘ͳ͍
    w J04Ͱ͸Ϟʔυຖʹ֤৭ ηϚϯςΟοΫΧϥʔ
    ͷඍௐ੔͕ߦΘΕ
    ͍ͯΔ
    w 4'4ZNCPMT͸ͲͷϞʔυͰ΋Α͘ݟ͑Δ༷ʹͳ͍ͬͯΔ
    !6
    IUUQTEFWFMPQFSBQQMFDPNEFTJHOIVNBOJOUFSGBDFHVJEFMJOFTBDDFTTJCJMJUZPWFSWJFXDPMPSBOEDPOUSBTU

    View full-size slide

  6. 5JQTίϯτϥετൺ
    !7
    Ҿ༻IUUQTHJUIVCDPNFNBSMFZ$PMPS$POUSBTU

    View full-size slide

  7. J04ҎԼͰ΋%BSL.PEF͍ͨ͠
    !9

    View full-size slide

  8. # Typeʹ΋Dark ModeೖΕͯΈͨ
    IUUQTUZQFNBSLEPXOBQQ

    View full-size slide

  9. %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ
    !11

    View full-size slide

  10. ৭Λ੔ཧ͢Δ ৭ʹ໾ׂ΍ҙຯΛ෇༩͢Δ

    !12
    protocol ColorSetType {
    static var theme: UIColor { get }
    static var action: UIColor { get }
    static var placeholder: UIColor { get }
    static var backgroundColor: UIColor { get }
    static var share: UIColor { get }
    static var archive: UIColor { get }
    static var error: UIColor { get }
    }
    %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ

    View full-size slide

  11. 6*ͷΠϕϯτΛड͚औΔ
    !13
    %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ

    View full-size slide

  12. ΞϓϦશମͰ؅ཧ͍ͯ͠Δ৭Λมߋ͢Δ
    !14
    class App {
    static var colorSet: ColorSetType = DefaultColorSet()
    }
    if modeSwitch.isOn {
    app.colorSet = DarkColorSet()
    } else {
    app.colorSet = DefaultColorSet()
    }
    %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ

    View full-size slide

  13. Ϟʔυ͕มߋ͞ΕͨࣄΛݕ஌ͯ͠6*ʹ൓өͤ͞Δ
    !15
    %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ
    "QQ
    7JFX" 7JFX# 7JFX$ 7JFX%

    View full-size slide

  14. Ϟʔυ͕มߋ͞ΕͨࣄΛݕ஌ͯ͠6*ʹ൓өͤ͞Δ
    !16
    %BSL.PEFΛ࣮૷͢Δҝͷߟ͑ํ
    "QQ
    7JFX" 7JFX# 7JFX$ 7JFX%

    View full-size slide

  15. IUUQTHJUIVCDPNGSPNLL
    $PMPS4FU
    ৭ͷछྨ͕มߋ͞ΕͨࣄΛײ஌ͯ͠
    6*ʹ৭Λઃఆ͢ΔࣄΛ؆ૉԽग़དྷΔ
    ϥΠϒϥϦ
    TVCTDSJCFͱCJOEΛ࢖͍෼͚Ε͹
    ͋Δఔ౓ॊೈʹ
    !17
    $PMPS4FU

    View full-size slide

  16. ελʔ͍ͩ͘͞
    !18
    $PMPS4FU
    IUUQTHJUIVCDPNGSPNLL
    $PMPS4FU
    ৭ͷछྨ͕มߋ͞ΕͨࣄΛײ஌ͯ͠
    6*ʹ৭Λઃఆ͢ΔࣄΛ؆ૉԽग़དྷΔ
    ϥΠϒϥϦ
    TVCTDSJCFͱCJOEΛ࢖͍෼͚Ε͹
    ͋Δఔ౓ॊೈʹ

    View full-size slide

  17. ରԠํ๏͸େମ෼͔ͬͨ%
    !19

    View full-size slide

  18. ࣮ࡍͷϓϩμΫτʹద༻ͯ͠ΈΑ͏
    !20

    View full-size slide

  19. ஍ࠈͷ࢝·Γ
    !21

    View full-size slide

  20. ݩʑͷը໘
    !22

    View full-size slide

  21. ৭͚ͩΛมߋͯ͠Έͨ
    !23

    View full-size slide

  22. ·ͣ৭Λมߋͯ͠Έͨ
    ヤバそう
    !24

    View full-size slide

  23. ͱΓ͋͑ͣഎܠ৭ઃఆ
    !25

    View full-size slide

  24. ͱΓ͋͑ͣഎܠ৭ઃఆ
    なるほど?
    !26

    View full-size slide

  25. ৭ʑௐ੔ͯ͠ΈΔ
    !27

    View full-size slide

  26. ͍ͩͿྑ͘ͳ͖ͬͯͨʁ
    !28

    View full-size slide

  27. 残念でした
    !29

    View full-size slide

  28. 画像も対応しないと
    !30

    View full-size slide

  29. こうじゃ
    !33

    View full-size slide

  30. ΞΠίϯͷ༷ͳը૾͸UJOU$PMPSͷมߋͰରԠ
    !34

    View full-size slide

  31. ステータスバーも変えたい
    !35

    View full-size slide

  32. ͍͍͍͍ͧͧ
    !36

    View full-size slide

  33. ରॲํ๏
    !37

    View full-size slide

  34. ରॲํ๏

    view.backgroundColor = .white
    *#΍ίʔυͰͷ৭ͷ௚઀ࢦఆېࢭ)

    TypeColorSet.backgroundColorLight
    .bind(to: view, keyPath: \.backgroundColor)
    .append(to: observationBag)
    !38

    View full-size slide

  35. ରॲํ๏
    AppearanceͷઃఆͰࡁΉ৔߹͸Ұׅઃఆ͕खͬऔΓૣ͍
    TypeColorSet.backgroundColorLight
    .bind(to: UITableViewCell.appearance(),
    keyPath: \.backgroundColor)
    .append(to: observationBag)
    !39

    View full-size slide

  36. ରॲํ๏
    എܠ͕ന΍طଘͷ৭Λલఏͱ͍ͯ͠Δը૾͕͋Ε͹ࠩ͠ସ͑Δ
    !40

    View full-size slide

  37. ରॲํ๏
    !41
    TypeColorSet.theme.subscribe { [weak button] _ in
    let colorSet = UIColor.currentColorSet()
    switch colorSet.identifier {
    case .default:
    button?.setImage(#imageLiteral(resourceName: “bt_add_light"), for: .normal)
    case .dark:
    button?.setImage(#imageLiteral(resourceName: "bt_add_dark"), for: .normal)
    }
    }.append(to: observationBag)
    ࠩ͠ସ͑ग़དྷͳ͍ը૾͸ΠϕϯτϋϯυϦϯάͰରԠ

    View full-size slide

  38. ରॲํ๏
    let imageView = UIImageView(image: UIImage(named: "image"))

    let imageView = UIImageView(image: UIImage(named: “image")?
    .withRenderingMode(.alwaysTemplate))
    TypeColorSet.theme.bind(

    to: imageView, keyPath: \.tintColor

    ).append(to: observationBag)
    ΞΠίϯͷ༷ͳҰ৭ͷը૾͸tintColorΛઃఆ͢Ε͹৭͕มΘΔ༷ʹରॲ
    !42

    View full-size slide

  39. ରॲํ๏
    εςʔλεόʔ͸Ϟʔυ͕มߋ͞Εͨ࣌ʹߋ৽͢ΔΑ͏ʹ
    TypeColorSet.theme.subscribe { [weak self] _ in
    self?.setNeedsStatusBarAppearanceUpdate()
    }.append(to: observationBag)
    override var preferredStatusBarStyle: UIStatusBarStyle {
    switch UIColor.currentColorSet().identifier {
    case .default:
    return .default
    case .dark:
    return .lightContent
    }
    }
    !43

    View full-size slide

  40. ·ͱΊ
    w ࢥͬͨΑΓରԠ͕େม
    w ΋ͱ΋ͱഎܠ৭͕നͷΞϓϦͩͱͲ͜ʹഎܠ৭ࢦఆ͔͕ͨ͠

    ෼͔ΓͮΒ͍
    ‎എܠ৭Λҙࣝͯ͠ઃఆ͢Δͷ͕େࣄ
    w ॎ΍ԣɺ4QMJU7JFXͳͲ༷ʑͳը໘ͷ֬ೝඞཁ
    ‎શը໘໢ཏͷ6*ςετͱεΫϦʔϯγϣοτ͕ٻΊΒΕΔ
    !44

    View full-size slide

  41. • αʔόʔαΠυΤϯδχΞ (PHP, Golang, AWS)
    • AndroidΤϯδχΞ (Kotlin)
    • iOSΤϯδχΞ (Swift)
    TimersͰ͸ݱࡏΤϯδχΞશ৬छ࠾༻தʂ

    ৄ͘͠͸”Timers”Ͱݕࡧ
    !
    46

    View full-size slide

  42. ϦδΣΫτίϯ΍Γ·͢
    !47
    %BZ %BZ

    View full-size slide

  43. ͝ਗ਼ௌ͋Γ͕ͱ͏͍͟͝·ͨ͠.
    48

    View full-size slide