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 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 Slide

  3. %BSL.PEF❓
    3

    View Slide

  4. %BSL.PEF
    4

    View Slide

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

    View Slide

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

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

    View Slide

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

    View Slide

  8. !8

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    !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 Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  21. ஍ࠈͷ࢝·Γ
    !21

    View Slide

  22. ݩʑͷը໘
    !22

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  29. 残念でした
    !29

    View Slide

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

    View Slide

  31. Α͠Α͠
    !31

    View Slide

  32. ͜Ε͕
    !32

    View Slide

  33. こうじゃ
    !33

    View Slide

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

    View Slide

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

    View Slide

  36. ͍͍͍͍ͧͧ
    !36

    View Slide

  37. ରॲํ๏
    !37

    View Slide

  38. ରॲํ๏

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

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

    View Slide

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

    View Slide

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

    View Slide

  41. ରॲํ๏
    !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 Slide

  42. ରॲํ๏
    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 Slide

  43. ରॲํ๏
    εςʔλεόʔ͸Ϟʔυ͕มߋ͞Εͨ࣌ʹߋ৽͢ΔΑ͏ʹ
    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 Slide

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

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

    View Slide

  45. 13
    45

    View Slide

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

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

    View Slide

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

    View Slide

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

    View Slide