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

安定したチャットを実現するための アプリとAPI設計

Muukii
August 31, 2018

安定したチャットを実現するための アプリとAPI設計

SNS系アプリにおけるコミュニケーション部分で欠かせないチャット機能についてお話します。
エウレカが展開するPairs, Couplesでもチャット機能はサービスの重要な要素として開発に取り組んでいます。
チャット機能は、期待される動作のレベルが高く、同時に様々な例外が起こりえます。
突き詰めるとなかなか大変なプロジェクトです。
最近ではFirebaseやチャットに特化したSaaSなどが多く立ち上がってきており、技術的にリアルタイム性を高く保つことのハードルが下がりつつあります。
またUIの実装ではMessageKitなどの高品質・高機能なライブラリも登場しています。
これらのSaaSやライブラリを使っている方、そうでない方にもきっとどこかで役立つ話をします。

Muukii

August 31, 2018
Tweet

More Decks by Muukii

Other Decks in Programming

Transcript

  1. iOSDC 2018
    Muukii, iOS Engineer
    eureka, Inc.
    ҆ఆͨ͠νϟοτΛ࣮ݱ͢ΔͨΊͷ
    ΞϓϦͱAPIઃܭ

    View full-size slide

  2. ☕ ⌚
    About Me
    • Muukii
    • iOS Engineer at eureka, Inc.
    • Pairs Global Team
    • GitHub : @muukii

    View full-size slide

  3. 1BJSTʹ͍ͭͯ
    !4

    View full-size slide

  4. 4PVUI,PSFB
    Japan
    Taiwan
    No.1
    2017
    release
    No.1
    !5
    1BJSTʹ͍ͭͯ
    ల։ࠃ ̐ͭͷϓϥοτϑΥʔϜ
    CONFIDENTIAL INFORMATION: Not for Public Distribution - Do Not Copy

    View full-size slide

  5. Contents
    • νϟοτΛ࣮૷͢ΔͨΊʹ (27min)
    • ظ଴͞ΕΔओͳಈ࡞ͱ͸ (2min)
    • ΫϥΠΞϯτ <-> αʔόʔ ͷ࿈ܞ (15min)
    • UIͷ࣮૷ʹ͍ͭͯ (10min)

    View full-size slide

  6. Կ͔ͱνϟοτΛ࣮૷͖ͯͨ͠
    ͜Ε·Ͱ࣮૷͖ͯͨ͠νϟοτ
    • 2013೥ Pairs ೔ຊ൛ using CoreData (΋͏͜ͷίʔυ͸ফ͞Ε͍ͯΔ)
    • 2014೥ Couples ࣮૷ using CoreData
    • 2017೥ Pairs άϩʔόϧ൛ using Realm
    • ϝοηʔδͷӬଓԽख๏Ͱ͸CoreDataͱRealmͷ྆ํͷܦݧ͕͋Δ͕ɺ

    Realmͷ΄͏͕ύϑΥʔϚϯενϡʔχϯά͕ߦ͍΍͔ͬͨ͢Πϝʔδ͕͋Δɻ

    View full-size slide

  7. Pairs Global App

    View full-size slide

  8. νϟοτʹظ଴͞ΕΔಈ࡞

    View full-size slide

  9. ૹ৴
    νϟοτʹظ଴͞ΕΔಈ࡞
    • ૬खʹϝοηʔδ͕ಧ͘
    • ૹ৴։࢝࣌఺Ͱਧ͖ग़͠ද͕ࣔߦΘΕΔ & ૹ৴தͷεςʔλεʹͳΔ
    • ૹ৴ʹࣦഊͨ͠ϝοηʔδ͕࢒Δ & ࣗಈͰ࠶ૹ͞ΕΔ & ௨৴ঢ়ଶͷ؂ࢹ
    • ૹ৴ϘλϯԡԼ௚ޙʹΞϓϦΛดͯ͡΋ૹ৴͞ΕΔ
    • όοΫάϥ΢ϯυૹ৴࣌ʹࣦഊͨ͠ΒϢʔβʔʹࣦഊͨ͜͠ͱΛ఻͑Δ
    • ૹ৴͢Δલͷϝοηʔδ͸ࣗಈతʹԼॻ͖ͱͯ͠อଘ͞Ε͍ͯΔ

    View full-size slide

  10. Ӿཡ ʢड৴ʣ
    • ૹ৴͞Εͨॱ൪௨Γʹදࣔ͞ΕΔ
    • ΦϑϥΠϯͰӾཡՄೳ͕๬·͍͠ (ࠓճͷ࿩͸ΦϑϥΠϯ࣌ରԠ͕ର৅)
    • ड৴ͨ͠ϝοηʔδ͸ӬଓԽ͞ΕɺεϜʔζʹաڈͷϝοηʔδΛݟΔ͜ͱ͕Ͱ͖Δ
    • աڈͷϝοηʔδΛݟ͍ͯΔͱ͖΋৽ணϝοηʔδʹؾ͚ͮΔΑ͏ʹͯ͋͛͠Δ
    • ಧ͍ͨΒҰ൪Լ·ͰεΫϩʔϧ͢Δͱ͔
    • ը໘ͷԼͷํʹ৽ணϝοηʔδ͕͋Γ·͢ͱ͍͏දࣔΛ͚ͭΔͱ͔
    νϟοτʹظ଴͞ΕΔಈ࡞

    View full-size slide

  11. ϝοηʔδͷ؅ཧͱऔಘ

    View full-size slide

  12. Ͳ͏΍ͬͯσʔλΛอଘͯ͠؅ཧ͠Α͏͔
    ϝοηʔδͷ؅ཧ
    • ࠓͷͱ͜Ζ͸RealmΛΦεεϝ
    • ࣮ࡍʹߴ͍ύϑΥʔϚϯεΛ͍࣋ͬͯΔ
    • ϝΠϯεϨου༻ͷRealmΦϒδΣΫτͱόοΫάϥ΢ϯυ༻ͷRealmΦϒδΣΫτΛ༻ҙͯ͠ɺॻ͖ࠐ
    Έ͸جຊతʹόοΫάϥ΢ϯυͰߦ͏Α͏ʹ͢Δɻ
    • ResultsΛ׆༻ͯ͠ϝΠϯεϨου·ͰΦϒδΣΫτΛಧ͚ͯ΋Β͏

    View full-size slide

  13. εϨουͷҠಈΛ؆୯ʹ͢Δ
    Realm Tips
    extension Realm {
    public func detached() throws -> Realm {
    return try Realm(configuration: configuration)
    }
    }
    DispatchQueue.global().async {
    try! mainRealm.detached().write {
    // Write
    }
    }

    View full-size slide

  14. ϝοηʔδͷऔಘ
    • ϝοηʔδͷऔಘํ๏͸Offset, LimitͷΑ͏ͳAPI͸ద੾Ͱ͸ͳ͍ɻ
    • Ϩίʔυ͕ͲΜͲΜ௥Ճ͞Ε͍ͯ͘ͷͰoffset͸ৗʹҠಈ͍ͯ͘͠
    • TwitterAPI΍SlackAPIͷΑ͏ʹɺRangeͰऔಘͰ͖Δ࢓૊Έ͕๬·͍͠ɻ
    • Timeline Pagination
    • νϟοτʹݶΒͣɺසൟʹϨίʔυ͕ࠩ͠ࠐ·ΕΔσʔλΛऔಘ͢Δ৔߹ʹ༗ޮ
    • https://developer.twitter.com/en/docs/tweets/timelines/guides/working-with-timelines.html
    • https://api.slack.com/methods/channels.history

    View full-size slide

  15. Offset Paginationͷྫ

    View full-size slide

  16. Offset Based Pagination
    1 2 3 4 5 6 7 8 9 10

    View full-size slide

  17. Fetch First Page
    1 2 3 4 5 6 7 8 9 10
    offset : 0
    limit : 5
    Offset Based Pagination
    1 2 3 4 5

    View full-size slide

  18. 1 2 3 4 5 6 7 8 9 10
    offset : 5
    limit : 5
    Offset Based Pagination
    Fetch Next Page
    6 7 8 9 10
    1 2 3 4 5

    View full-size slide

  19. 1 2 3 4 5 6 7 8 9 10
    Offset Based Pagination
    The Problem Case

    View full-size slide

  20. 1 2 3 4 5 6 7 8 9 10
    offset : 0
    limit : 5
    Offset Based Pagination
    Fetch First Page
    1 2 3 4 5

    View full-size slide

  21. Inserted New Records
    1 2 3 4 5 6 7 8 9 10
    offset : 0
    limit : 5
    1' 2'
    Offset Based Pagination
    1 2 3 4 5

    View full-size slide

  22. offset : 5
    limit : 5
    1 2 3 4 5 6 7 8 9 10
    1' 2'
    Offset Based Pagination
    1 2 3 4 5 4 5 6 7 8
    Fetch Next Page

    View full-size slide

  23. 1 2 3 4 5
    offset : 5
    limit : 5
    1 2 3 4 5 6 7 8 9 10
    1' 2'
    4 5 6 7 8
    ⚠ Duplicated
    Offset Based Pagination
    Fetch Next Page

    View full-size slide

  24. Timeline Pagination
    • औಘ͍ͨ͠σʔλͷൣғΛࢦఆͨ͠औಘํ๏
    • σʔλ͕࣋ͭϢχʔΫIDΛ΋ͱʹൣғΛܾఆ͢Δ
    • ྫ͑͹
    • ID͕ 1... ͷ΋ͷΛ10݅
    • ID͕ ...1 ͷ΋ͷΛ10݅
    • ID͕ 1...100 ͷ΋ͷΛ10݅
    • औಘ͢ΔσʔλͷॏෳΛ࠷খݶʹ཈͑ɺαʔόʔɾΫϥΠΞϯτؒͷࠩ෼Λऔಘ͢Δ͜ͱ͕ग़དྷΔ

    View full-size slide

  25. ਎ۙͳͱ͜ΖͰ͸ SlackAPI
    • Slack (https://api.slack.com/methods/channels.history)
    • Fetch Messages
    • Parameters
    • count
    • number : 10 (Optional, default=100)
    • latest
    • timestamp : "1481196383.000292" (Optional, default=now)
    • oldest
    • timestamp : "1481196383.000292" (Optional, default=0)
    Timeline Pagination

    View full-size slide

  26. Timeline Pagination
    The schema for message using in Slack
    {
    "type": "message",
    "ts": "1358546515.000008",
    "user": "XXXXXXX",
    "client_msg_id": "d20e760f-456b-4ba0-a8fe-df2935694ee2",
    "text": "Hello"
    }

    View full-size slide

  27. "ts": "1358546515.000008"
    Microseconds Timestamp
    UniqueKey

    View full-size slide

  28. 1 2 3 4 5 6 7 8 9 10
    Sorted by timestamp
    Older
    Newer

    View full-size slide

  29. Fetch First Chunk
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    latest : null
    oldest : null
    count : 5
    1 2 3 4 5

    View full-size slide

  30. Fetch Next Chunk
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    1 2 3 4 5
    "10"
    "11"
    "12"
    "13"
    "15"
    timestamp

    View full-size slide

  31. Fetch Next Chunk
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    1 2 3 4 5
    latest :
    oldest : null
    count : 5
    "10"
    6 7 8 9 10

    View full-size slide

  32. The problem case in Offset Based Pagination
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10

    View full-size slide

  33. Fetch First Chunk
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    latest : null
    oldest : null
    count : 5
    1 2 3 4 5

    View full-size slide

  34. Inserted New Records
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    1 2 3 4 5
    1' 2'

    View full-size slide

  35. Inserted New Records
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    1 2 3 4
    1' 2'
    5
    "10"
    "11"
    "12"
    "13"
    "15"
    timestamp

    View full-size slide

  36. Fetch Next Chunk
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    1 2 3 4 5
    1' 2'
    latest :
    oldest : null
    count : 5
    "10"
    6 7 8 9 10

    View full-size slide

  37. Fetch Latest Items
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    2 3 4 5
    1' 2'
    6 7 8 9 10
    1
    "10"
    "11"
    "12"
    "13"
    "15"
    timestamp

    View full-size slide

  38. Fetch Latest Items
    Timeline Pagination
    1 2 3 4 5 6 7 8 9 10
    2 3 4 5
    1' 2'
    6 7 8 9 10
    1
    latest : null
    oldest :
    count : 5
    "15"
    1' 2'

    View full-size slide

  39. If many items inserted
    Timeline Pagination
    1 2 3 4 5
    2 3 4 5
    1' 2'
    1
    3' 4' 5' 6' 7' 8' 9' 10'
    "10"
    "11"
    "12"
    "13"
    "15"
    timestamp

    View full-size slide

  40. Timeline Pagination
    1 2 3 4 5
    2 3 4 5
    1' 2'
    1
    3' 4' 5' 6' 7' 8' 9' 10'
    latest : null
    oldest :
    count : 5
    "15"
    1' 2' 3' 4' 5'
    First, fetch latest items

    View full-size slide

  41. Timeline Pagination
    1 2 3 4 5
    2 3 4 5
    1' 2'
    1
    3' 4' 5' 6' 7' 8' 9' 10'
    1' 2' 3' 4' 5'
    ⚠ Missing Range
    Detect item missing range

    View full-size slide

  42. Timeline Pagination
    1 2 3 4 5
    2 3 4 5
    1' 2'
    1
    3' 4' 5' 6' 7' 8' 9' 10'
    1' 2' 3' 4' 5'
    "15"
    "24"
    "28"
    "31"
    Fetch items in missing range using oldest and latest params.

    View full-size slide

  43. Timeline Pagination
    1 2 3 4 5
    2 3 4 5
    1' 2'
    1
    3' 4' 5' 6' 7' 8' 9' 10'
    1' 2' 3' 4' 5'
    latest :
    oldest :
    count : 5
    "15"
    "24"
    6' 7' 8' 9' 10'
    Fetch completed

    View full-size slide

  44. Data Synchronization
    • ͜ͷ࢓૊ΈͰαʔόʔͱͷσʔλಉظ͕Մೳʹ
    • latestͱoldest΍MissingRangeͷύϥϝʔλ͸ΞϓϦͰอ؅͓ͯ͘͠ͱྑ͍
    • ΫϥΠΞϯτͰ͸͜ͷऔಘAPIͰऔΕͨσʔλΛਖ਼ͱͯ͠ॲཧ͍ͯ͘͠ͷ͕Φεεϝ
    • ଞͷॲཧͰDBΛߋ৽ͯ͠΋Α͍͕ɺऔಘॲཧͰ੔ཧ͞ΕΔΠϝʔδ
    • ͜ͷॲཧΛ౔୆ͱͯ͠WebSocket΍NotificationͰड৴ͷτϦΨʔʹ͢ΔͳͲͷϦΞϧλΠϜԽͷΞϓ
    ϩʔν͕ߦ͑Δ
    • Ծʹιέοτ௨৴్͕੾Εͨͱͯ͠΋ɺ҆ఆతʹσʔλΛऔಘ͍͚ͯ͠Δ
    Timeline Pagination

    View full-size slide

  45. ϝοηʔδͷૹ৴

    View full-size slide

  46. ૹ৴લ
    5
    5
    ϝοηʔδͷૹ৴

    View full-size slide

  47. ૹ৴த
    5
    5
    ϝοηʔδͷૹ৴

    View full-size slide

  48. ૹ৴׬ྃ
    5
    5
    ϝοηʔδͷૹ৴

    View full-size slide

  49. ૹ৴ࣦഊ
    5
    5
    ϝοηʔδͷૹ৴

    View full-size slide

  50. • ૹ৴։࢝࣌ʹϝοηʔδ͕දࣔ͞ΕΔ (ૹ৴த)

    ✅ ૹ৴੒ޭ
    • ૹ৴׬ྃͷදࣔΛߦ͏

    ૹ৴ࣦഊ
    • ૹ৴ࣦഊͷදࣔΛߦ͏
    • ΞϓϦͷλεΫΛ੾ΒΕͯ΋εςʔλεΛอ؅͢ΔͨΊૹ৴։࢝࣌఺Ͱϝοηʔδ͸ӬଓԽ͢Δ
    ϝοηʔδͷૹ৴

    View full-size slide

  51. ϝοηʔδΦϒδΣΫτͷεΩʔϚ
    struct Message {
    enum State {
    case draft
    case sending
    case pendingUpdate
    case delivered
    case failed
    }
    var sentTime: Date // PrimaryKey
    var state: State
    var text: String
    var clientIdentifier: String
    }
    ϝοηʔδͷૹ৴

    View full-size slide

  52. ૹ৴ঢ়ଶͷදݱ
    struct Message {
    enum State {
    case draft
    case sending
    case pendingUpdate
    case delivered
    case failed
    }
    var sentTime: Date // PrimaryKey
    var state: State
    var text: String
    var clientIdentifier: String
    }
    ϝοηʔδͷૹ৴

    View full-size slide

  53. ঢ়ଶ
    ϝοηʔδͷૹ৴
    .draft .sending .pendingUpdate .delivered
    .failed

    View full-size slide

  54. ঢ়ଶ
    ϝοηʔδͷૹ৴
    .draft .sending .pendingUpdate .delivered
    .failed
    Request Response

    View full-size slide

  55. Α͘ى͖Δࣄྫ
    • API RequestΛߦ͍ɺαʔόʔʹ౸ண͠ɺ૬खʹϝοηʔδ͸ಧ͘
    • ͔͠͠ɺ௨৴͕ෆ҆ఆͰૹ৴ଆͷΞϓϦʹϨεϙϯε͕ಧ͔ͳ͔ͬͨ
    • state͸sendingͷ··ʹͳͬͯ͠·͕ͬͨɺड৴APIͰ͸ૹ৴ͨ͠ϝοηʔδ͸ฦ٫͞ΕΔ
    • ͜ͷ··ͩͱɺ࠶ૹ৴ϘλϯΛԡ͞ΕΔՄೳੑ͕͋Γɺ૬खʹಉ͡΋ͷ͕ૹ৴͞Εͯ͠·͏͔΋
    • ͦ͜Ͱ
    • ΫϥΠΞϯτଆͰૹ৴͍ͯ͠ΔϝοηʔδΛτϥοΩϯά͢Δඞཁੑ͕͋Δ
    • ૹ৴தͩͱࢥ͍ͬͯͨϝοηʔδ͕ड৴API͔Βؼ͖ͬͯͨΒૹ৴׬͍ྃͯ͠ΔͱΈͳ͢ɻ
    ϝοηʔδૹ৴ͷࣦഊ

    View full-size slide

  56. SlackͷϝοηʔδεΩʔϚΛࢀߟʹ͢Δ
    {
    "type": "message",
    "ts": "1358546515.000008",
    "user": "XXXXXXX",
    "client_msg_id": "d20e760f-456b-4ba0-a8fe-df2935694ee2",
    "text": "Hello"
    }
    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

    View full-size slide

  57. client_msg_idͱ͍͏΋ͷ͕͋Δ
    {
    "type": "message",
    "ts": "1358546515.000008",
    "user": "XXXXXXX",
    "client_msg_id": "d20e760f-456b-4ba0-a8fe-df2935694ee2",
    "text": "Hello"
    }
    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

    View full-size slide

  58. client_msg_id͸ΫϥΠΞϯτͰੜ੒͞Εͨ΋ͷ
    • ΫϥΠΞϯτଆͰϢχʔΫͳIDΛൃߦ͠ɺϝοηʔδͷclient_msg_idʹ෇༩ͯ͠อଘ
    • ྫ͑͹ UUID().uuidStringͰ΋ྑ͍
    • ͦͷσʔλͰαʔόʔʹϦΫΤετ
    • αʔόʔ͸client_msg_idΛͦͷ··อଘ͠ɺAPIͷϨεϙϯεʹͦͷ··ฦ͢
    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

    View full-size slide

  59. struct Message {
    enum State {
    case draft
    case sending
    case pendingUpdate
    case delivered
    case failed
    }
    var sentTime: Date // PrimaryKey
    var state: State
    var text: String
    var clientIdentifier: String
    }
    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

    View full-size slide

  60. Ұ࿈ͷྲྀΕ
    • ϝοηʔδΛState.draftͰ࡞੒͢Δ client_msg_idΛੜ੒͠෇༩͢Δ
    • ϝοηʔδΛૹ৴͢Δ State.sending
    • ૹ৴׬ྃ State.pendingUpdate
    • ड৴APIͰऔಘͰ͖ͨϝοηʔδ͕࣋ͭclient_msg_idͰΫϥΠΞϯτଆͷϝοηʔδΛݕࡧ͢Δ
    • ݟ͔ͭͬͨϝοηʔδΛ State.deliveredʹมߋ͍ͯ͘͠
    • State.pendingUpdate -> State.deliverd
    • State.failed -> State.delivered
    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

    View full-size slide

  61. User Interface

    View full-size slide

  62. User Interface
    Send

    View full-size slide

  63. User Interface
    Send
    Bubble
    Balloon Compose View
    UICollectionView
    UITableView

    View full-size slide

  64. ͲͬͪΛ࢖͓͏͔
    UITableView?
    UICollectionView?

    View full-size slide

  65. • UITableViewͱൺֱͯ͠έʔεʹ߹ΘͤͨॊೈͳରԠ͕Մೳ
    • ηύϨʔλ͕ແ͍
    • UICollectionViewLayoutͰϨΠΞ΢τΛΧελϚΠζ
    • ଟ͘ͷ৔߹͸UICollectionViewFlowLayoutͰ΍͍͚ͬͯΔ
    • ϨΠΞ΢τͷ࣮૷͸೉͍͕͠ɺΑΓڧྗͳϨΠΞ΢τΛ࡞Δ͜ͱ͕ग़དྷΔ͔΋͠Εͳ͍
    • FlowLayoutΑΓߴ଎ͳϨΠΞ΢τΛ࡞ΔɺͳͲ
    • UICollectionViewLayoutAttributesʹΑΔCellͷίϯτϩʔϧ
    • σʔλͷදࣔϩδοΫͱUIϩδοΫΛ؅ཧ͠΍͘͢ग़དྷΔ͔΋͠Εͳ͍?
    • UICollectionReusableView.preferredLayoutAttributesFitting(_:)
    UICollectionView

    View full-size slide

  66. Displaying Messages
    • Լʹ޲͔ͬͯ৽͍͠ϝοηʔδΛฒ΂Δ
    • IndexPath͕Ұ൪େ͖͍΋ͷ͕࠷৽ͷϝοηʔδʹͳΔΑ͏ʹ
    Using UICollectionView

    View full-size slide

  67. Loading Messages
    • ӬଓԽ͍ͯ͠Δ͢΂ͯͷϝοηʔδΛදࣔ͢ΔͷͰ͸ͳ͘ɺ࠷৽ͷϝοηʔδ͔Βগͮͭ͠
    CollectionViewʹ௥Ճ͍ͯ͘͠
    • ॳظදࣔύϑΥʔϚϯεͷͨΊ
    • ྫ͑͹ɺ࠷ॳ͸20݅දࣔ
    • ϝοηʔδड৴ɾૹ৴Ͱn݅௥Ճ
    • աڈͷϝοηʔδͷಡΈࠐΈͰ20݅௥Ճ
    • DB΁ͷΫΤϦ͸දࣔ݅਺Λ૿΍͢͝ͱʹ࣮ߦ͢ΔͷͰ͸ͳ͘ɺશ݅औಘͯ͠ArraySliceΛ࡞ΓɺͦΕ
    Λ࢖ͬͯCollectionViewʹදࣔΛ͍ͯ͘͠
    Using UICollectionView

    View full-size slide

  68. • ࠷৽ͷϝοηʔδ͸Ұ൪Լʹදࣔ͞ΕΔͷͰɺScrollToBottomͷॲཧ͕ඞཁʹͳΔ
    • ͜ͷ࣮૷ͰϋϚΔ͜ͱ͕݁ߏ͋Δ
    • ͏·͘Ұ൪ԼʹεΫϩʔϧ͞Εͳ͍ͱ͖ͷετϨε͸൒୺ͳ͍ (։ൃऀͱͯ͠΋Ϣʔβʔͱͯ͠΋)
    • MessageKitͰ͸ࢼߦࡨޡͷ݁Ռγϯϓϧͳίʔυʹམͪண͍͍ͯΔ༷ࢠ
    • https://github.com/MessageKit/MessageKit/blob/
    28ac5f5e4180f55e75140417a9d6dabd2c6fda03/Sources/Views/
    MessagesCollectionView.swift#L97:5
    Scroll to bottom in UICollectionView

    View full-size slide

  69. by MessageKit
    extension UICollectionView {
    public func scrollToBottom(animated: Bool = false) {
    let collectionViewContentHeight = collectionViewLayout.collectionViewContentSize.height
    performBatchUpdates(nil) { _ in
    self.scrollRectToVisible(
    CGRect(
    x: 0,
    y: collectionViewContentHeight - 1,
    width: 1,
    height: 1
    ),
    animated: animated
    )
    }
    }
    }
    Scroll to bottom in UICollectionView

    View full-size slide

  70. εΫϩʔϧҐஔΛҡ࣋ͨ͠··ΞΠςϜΛ௥Ճ͢Δ
    աڈͷϝοηʔδΛಡΈࠐΉ
    • աڈͷϝοηʔδΛಡΈࠐΉ࣌͸ɺҰ൪্·ͰεΫϩʔϧΛ͢Δ͜ͱͰࣗಈతʹ্෦ʹϝοηʔδ͕ੵ·
    Εͯߦ͘Α͏ͳݟͤํ͕Ұൠత
    • ͔͠͠ɺUICollectionViewͷಛੑ্ɺݟ͍ͯΔ෦෼ΑΓ্෦ʹΞΠςϜ͕ૠೖ͞ΕΔͱεΫϩʔϧҐஔ͕
    มΘͬͯ͠·͏ɻ
    • ૠೖ͞Εͨ෼ͷheightΛߟྀͯ͠contentOffsetΛ࠶ௐ੔ͯ͋͛͠ΔରԠ͕ඞཁ
    1.ΞΠςϜ௥ՃલʹcontentOffsetΛอ࣋
    2.ΞΠςϜ௥Ճ
    3.௥ՃޙͷcontentSizeͱอ࣋ͨ͠contentOffsetΛ࢖͍ɺ্ʹΞΠςϜ͕௥Ճ͞ΕͨΑ͏ʹݟͤΔ

    View full-size slide

  71. εΫϩʔϧҐஔͷҡ࣋Λগָ͠ʹ͢ΔLayout
    • KeepingContentOffsetCollectionViewLayout
    • https://gist.github.com/muukii/29249c295a0f2c4fecde61d99abc94f5
    • UICollectionViewFlowLayout.targetContentOffsetΛΦʔόʔϥΠυ͢ΔΞϓϩʔν
    աڈͷϝοηʔδΛಡΈࠐΉ

    View full-size slide

  72. (collectionView.collectionViewLayout as! KeepingContentOffsetCollectionViewLayout)
    .performWithKeepingContentOffset { (completion) in
    collectionView.performBatchUpdates({
    // Update items
    }, completion: { _ in
    completion()
    })
    }
    KeepingContentOffsetCollectionViewLayout
    աڈͷϝοηʔδΛಡΈࠐΉ

    View full-size slide

  73. Another approach

    View full-size slide

  74. • UICollectionViewΛ180౓ճస (transform)
    • UICollectionViewCellΛ180౓ճస (transform)
    • ͢ΔͱɺIndexPath (0, 0) ͕Ұ൪Լʹදࣔ͞ΕΔΑ͏ʹͳΔ
    Inverted UICollectionView

    View full-size slide

  75. ✅ ͜Ε͕͏·͍͘͘ͱ
    Inverted UICollectionView
    • Scroll to bottom͸Scroll to topʹͳָͬͯʹͳΔ
    • աڈͷϝοηʔδಡΈࠐΈ࣌ʹεΫϩʔϧҐஔΛҡ࣋͢Δॲཧ͕ෆཁʹͳΔ

    View full-size slide

  76. ⚠ ஫ҙ఺
    Inverted UICollectionView
    • ΞΠςϜ͕গͳ͍࣌ʹը໘্෦ʹදࣔ͢Δ͜ͱ͕೉͘͠ͳΔ
    • εςʔλεόʔͷλοϓʹΑΓScroll to top͕ߦΘΕΔͷͰٯʹ͢Δඞཁ͕͋Δ
    • ͦͷ··ͩͱεςʔλεόʔΛλοϓͰ࠷৽ͷϝοηʔδ·ͰඈΜͰ͠·͏
    • contentInsetͷઃఆ͕͢΂ͯٯ
    • OS͕ࣗಈͰઃఆͯ͘͠ΕΔ஋͸͢΂ͯແޮԽͯ͠खಈͰઃఆ͢Δ (safeAreaͳͲʣ

    View full-size slide

  77. OSS
    • UITableView༻Ͱ͕͢ɺ͜ͷΞϓϩʔνΛ࣮ݱ͢ΔOSS͕͋Γ·͢
    • https://github.com/marty-suzuki/ReverseExtension
    • TextureͷASCollectionNodeʹ͸invertedϓϩύςΟ༻ҙ͞Ε͍ͯΔ
    Inverted UICollectionView

    View full-size slide

  78. Compose Message View

    View full-size slide

  79. We need GrowingTextView.

    View full-size slide

  80. Implementing Growing Text View is too hard
    • ਓੜͰҰ൪ਏ͍ࢥ͍Λͨ͠UI
    • ΩʔϘʔυͷ্෦ʹுΓ෇͍ͨςΩετೖྗUI
    • վߦʹԠͯ͡৳ॖ
    • ৳ͼ͍͚ͯͩ͘ͳΒ؆୯͕ͩɺ৳ॖʹ্ݶΛઃ͚Δͱɺٸʹ࣮૷͕೉͘͠
    ͳΔ

    ݁ߏݹ͍͚ͲOSS͋Γ·͢
    • https://github.com/muukii/NextGrowingTextView
    • iOS Messagesͷ࣮૷ઃܭͱಉ͡ (ࠓ͸Θ͔Βͳ͍)
    • ΠϚυΩͳϞμϯͳ࣮૷͕ؾʹͳ͍ͬͯΔͱ͜Ζ
    It will work better with muukii/NextGrowingTextView.

    View full-size slide

  81. ͓ΘΓʹ
    • νϟοτ͸SNSܥΞϓϦʹ͓͍ͯ࠷΋ॏཁͳػೳͰ͋Γɺ࣮૷͸೉͍͠৔໘͕ଟʑ͋Δ
    • ೉͘͠ߟ͑͗͢ΔͱɺͲΜͲΜ೉͘͠ͳ͍ͬͯ͘
    • σʔλઃܭ΍UIઃܭʹ͓͍ͯॊೈͳΞΠσΞ͕ٻΊΒΕΔ
    • ࠓճൃදͨ͠಺༰ʹՃ͑ɺϦΞϧλΠϜԽͷΞϓϩʔν࣍ୈͰ·ͨঢ়گ͸มΘͬͯ͘Δ͸ͣ
    • ࠷ۙͰ͸MessageKit (UI) ΍ ChatKit(SaaS)ͳͲ͕ొ৔͖͓ͯͯ͠Γɺར༻͢Δͷ΋ྑ͍͠ɺ࣮ݱํ๏ͷ
    ࢀߟʹ΋ͳΔ
    • νϟοτΛ࡞Ζ͏ͱࢥ͍ͬͯΔਓɺνϟοτͱಆ͖ͬͯͨਓͱҰॹʹ࿩Λ͠·͠ΐ͏ 1

    View full-size slide