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

Ca8210ff0ece2bb6f9fff5fd0770ea64?s=47 Muukii
August 31, 2018

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

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

Ca8210ff0ece2bb6f9fff5fd0770ea64?s=128

Muukii

August 31, 2018
Tweet

Transcript

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

  2. ☕ ⌚ About Me • Muukii <Hiroshi Kimura> • iOS

    Engineer at eureka, Inc. • Pairs Global Team • GitHub : @muukii
  3. None
  4. 1BJSTʹ͍ͭͯ !4

  5. 4PVUI,PSFB Japan Taiwan No.1 2017 release No.1 !5 1BJSTʹ͍ͭͯ ల։ࠃ

    ̐ͭͷϓϥοτϑΥʔϜ CONFIDENTIAL INFORMATION: Not for Public Distribution - Do Not Copy
  6. None
  7. Contents • νϟοτΛ࣮૷͢ΔͨΊʹ (27min) • ظ଴͞ΕΔओͳಈ࡞ͱ͸ (2min) • ΫϥΠΞϯτ <->

    αʔόʔ ͷ࿈ܞ (15min) • UIͷ࣮૷ʹ͍ͭͯ (10min)
  8. Կ͔ͱνϟοτΛ࣮૷͖ͯͨ͠ ͜Ε·Ͱ࣮૷͖ͯͨ͠νϟοτ • 2013೥ Pairs ೔ຊ൛ using CoreData (΋͏͜ͷίʔυ͸ফ͞Ε͍ͯΔ) •

    2014೥ Couples ࣮૷ using CoreData • 2017೥ Pairs άϩʔόϧ൛ using Realm • ϝοηʔδͷӬଓԽख๏Ͱ͸CoreDataͱRealmͷ྆ํͷܦݧ͕͋Δ͕ɺ
 Realmͷ΄͏͕ύϑΥʔϚϯενϡʔχϯά͕ߦ͍΍͔ͬͨ͢Πϝʔδ͕͋Δɻ
  9. Pairs Global App

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

  11. ૹ৴ νϟοτʹظ଴͞ΕΔಈ࡞ • ૬खʹϝοηʔδ͕ಧ͘ • ૹ৴։࢝࣌఺Ͱਧ͖ग़͠ද͕ࣔߦΘΕΔ & ૹ৴தͷεςʔλεʹͳΔ • ૹ৴ʹࣦഊͨ͠ϝοηʔδ͕࢒Δ

    & ࣗಈͰ࠶ૹ͞ΕΔ & ௨৴ঢ়ଶͷ؂ࢹ • ૹ৴ϘλϯԡԼ௚ޙʹΞϓϦΛดͯ͡΋ૹ৴͞ΕΔ • όοΫάϥ΢ϯυૹ৴࣌ʹࣦഊͨ͠ΒϢʔβʔʹࣦഊͨ͜͠ͱΛ఻͑Δ • ૹ৴͢Δલͷϝοηʔδ͸ࣗಈతʹԼॻ͖ͱͯ͠อଘ͞Ε͍ͯΔ
  12. Ӿཡ ʢड৴ʣ • ૹ৴͞Εͨॱ൪௨Γʹදࣔ͞ΕΔ • ΦϑϥΠϯͰӾཡՄೳ͕๬·͍͠ (ࠓճͷ࿩͸ΦϑϥΠϯ࣌ରԠ͕ର৅) • ड৴ͨ͠ϝοηʔδ͸ӬଓԽ͞ΕɺεϜʔζʹաڈͷϝοηʔδΛݟΔ͜ͱ͕Ͱ͖Δ •

    աڈͷϝοηʔδΛݟ͍ͯΔͱ͖΋৽ணϝοηʔδʹؾ͚ͮΔΑ͏ʹͯ͋͛͠Δ • ಧ͍ͨΒҰ൪Լ·ͰεΫϩʔϧ͢Δͱ͔ • ը໘ͷԼͷํʹ৽ணϝοηʔδ͕͋Γ·͢ͱ͍͏දࣔΛ͚ͭΔͱ͔ νϟοτʹظ଴͞ΕΔಈ࡞
  13. ϝοηʔδͷ؅ཧͱऔಘ

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

    ResultsΛ׆༻ͯ͠ϝΠϯεϨου·ͰΦϒδΣΫτΛಧ͚ͯ΋Β͏
  15. εϨουͷҠಈΛ؆୯ʹ͢Δ Realm Tips extension Realm { public func detached() throws

    -> Realm { return try Realm(configuration: configuration) } } DispatchQueue.global().async { try! mainRealm.detached().write { // Write } }
  16. ϝοηʔδͷऔಘ • ϝοηʔδͷऔಘํ๏͸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
  17. Offset Paginationͷྫ

  18. Offset Based Pagination 1 2 3 4 5 6 7

    8 9 10
  19. 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
  20. 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
  21. 1 2 3 4 5 6 7 8 9 10

    Offset Based Pagination The Problem Case
  22. 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
  23. 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
  24. 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
  25. 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
  26. Timeline Pagination • औಘ͍ͨ͠σʔλͷൣғΛࢦఆͨ͠औಘํ๏ • σʔλ͕࣋ͭϢχʔΫIDΛ΋ͱʹൣғΛܾఆ͢Δ • ྫ͑͹ • ID͕

    1... ͷ΋ͷΛ10݅ • ID͕ ...1 ͷ΋ͷΛ10݅ • ID͕ 1...100 ͷ΋ͷΛ10݅ • औಘ͢ΔσʔλͷॏෳΛ࠷খݶʹ཈͑ɺαʔόʔɾΫϥΠΞϯτؒͷࠩ෼Λऔಘ͢Δ͜ͱ͕ग़དྷΔ
  27. ਎ۙͳͱ͜ΖͰ͸ 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
  28. 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" }
  29. "ts": "1358546515.000008" Microseconds Timestamp UniqueKey

  30. 1 timestamp

  31. 1 2 3 4 5 6 7 8 9 10

    Sorted by timestamp Older Newer
  32. 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
  33. 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
  34. 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
  35. The problem case in Offset Based Pagination Timeline Pagination 1

    2 3 4 5 6 7 8 9 10
  36. 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
  37. Inserted New Records Timeline Pagination 1 2 3 4 5

    6 7 8 9 10 1 2 3 4 5 1' 2'
  38. 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
  39. 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
  40. 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
  41. 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'
  42. 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
  43. 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
  44. 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
  45. 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.
  46. 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
  47. Data Synchronization • ͜ͷ࢓૊ΈͰαʔόʔͱͷσʔλಉظ͕Մೳʹ • latestͱoldest΍MissingRangeͷύϥϝʔλ͸ΞϓϦͰอ؅͓ͯ͘͠ͱྑ͍ • ΫϥΠΞϯτͰ͸͜ͷऔಘAPIͰऔΕͨσʔλΛਖ਼ͱͯ͠ॲཧ͍ͯ͘͠ͷ͕Φεεϝ • ଞͷॲཧͰDBΛߋ৽ͯ͠΋Α͍͕ɺऔಘॲཧͰ੔ཧ͞ΕΔΠϝʔδ

    • ͜ͷॲཧΛ౔୆ͱͯ͠WebSocket΍NotificationͰड৴ͷτϦΨʔʹ͢ΔͳͲͷϦΞϧλΠϜԽͷΞϓ ϩʔν͕ߦ͑Δ • Ծʹιέοτ௨৴్͕੾Εͨͱͯ͠΋ɺ҆ఆతʹσʔλΛऔಘ͍͚ͯ͠Δ Timeline Pagination
  48. ϝοηʔδͷૹ৴

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

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

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

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

  53. • ૹ৴։࢝࣌ʹϝοηʔδ͕දࣔ͞ΕΔ (ૹ৴த) • ✅ ૹ৴੒ޭ • ૹ৴׬ྃͷදࣔΛߦ͏ • ૹ৴ࣦഊ

    • ૹ৴ࣦഊͷදࣔΛߦ͏ • ΞϓϦͷλεΫΛ੾ΒΕͯ΋εςʔλεΛอ؅͢ΔͨΊૹ৴։࢝࣌఺Ͱϝοηʔδ͸ӬଓԽ͢Δ ϝοηʔδͷૹ৴
  54. ϝοηʔδΦϒδΣΫτͷεΩʔϚ 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 } ϝοηʔδͷૹ৴
  55. ૹ৴ঢ়ଶͷදݱ 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 } ϝοηʔδͷૹ৴
  56. ঢ়ଶ ϝοηʔδͷૹ৴ .draft .sending .pendingUpdate .delivered .failed

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

  58. Α͘ى͖Δࣄྫ • API RequestΛߦ͍ɺαʔόʔʹ౸ண͠ɺ૬खʹϝοηʔδ͸ಧ͘ • ͔͠͠ɺ௨৴͕ෆ҆ఆͰૹ৴ଆͷΞϓϦʹϨεϙϯε͕ಧ͔ͳ͔ͬͨ • state͸sendingͷ··ʹͳͬͯ͠·͕ͬͨɺड৴APIͰ͸ૹ৴ͨ͠ϝοηʔδ͸ฦ٫͞ΕΔ • ͜ͷ··ͩͱɺ࠶ૹ৴ϘλϯΛԡ͞ΕΔՄೳੑ͕͋Γɺ૬खʹಉ͡΋ͷ͕ૹ৴͞Εͯ͠·͏͔΋

    • ͦ͜Ͱ • ΫϥΠΞϯτଆͰૹ৴͍ͯ͠ΔϝοηʔδΛτϥοΩϯά͢Δඞཁੑ͕͋Δ • ૹ৴தͩͱࢥ͍ͬͯͨϝοηʔδ͕ड৴API͔Βؼ͖ͬͯͨΒૹ৴׬͍ྃͯ͠ΔͱΈͳ͢ɻ ϝοηʔδૹ৴ͷࣦഊ
  59. SlackͷϝοηʔδεΩʔϚΛࢀߟʹ͢Δ { "type": "message", "ts": "1358546515.000008", "user": "XXXXXXX", "client_msg_id": "d20e760f-456b-4ba0-a8fe-df2935694ee2",

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

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

    ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ
  62. 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 } ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ
  63. Ұ࿈ͷྲྀΕ • ϝοηʔδΛState.draftͰ࡞੒͢Δ client_msg_idΛੜ੒͠෇༩͢Δ • ϝοηʔδΛૹ৴͢Δ State.sending • ૹ৴׬ྃ State.pendingUpdate

    • ड৴APIͰऔಘͰ͖ͨϝοηʔδ͕࣋ͭclient_msg_idͰΫϥΠΞϯτଆͷϝοηʔδΛݕࡧ͢Δ • ݟ͔ͭͬͨϝοηʔδΛ State.deliveredʹมߋ͍ͯ͘͠ • State.pendingUpdate -> State.deliverd • State.failed -> State.delivered ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ
  64. User Interface

  65. User Interface Send

  66. User Interface Send Bubble Balloon Compose View UICollectionView UITableView

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

  68. • UITableViewͱൺֱͯ͠έʔεʹ߹ΘͤͨॊೈͳରԠ͕Մೳ • ηύϨʔλ͕ແ͍ • UICollectionViewLayoutͰϨΠΞ΢τΛΧελϚΠζ • ଟ͘ͷ৔߹͸UICollectionViewFlowLayoutͰ΍͍͚ͬͯΔ • ϨΠΞ΢τͷ࣮૷͸೉͍͕͠ɺΑΓڧྗͳϨΠΞ΢τΛ࡞Δ͜ͱ͕ग़དྷΔ͔΋͠Εͳ͍

    • FlowLayoutΑΓߴ଎ͳϨΠΞ΢τΛ࡞ΔɺͳͲ • UICollectionViewLayoutAttributesʹΑΔCellͷίϯτϩʔϧ • σʔλͷදࣔϩδοΫͱUIϩδοΫΛ؅ཧ͠΍͘͢ग़དྷΔ͔΋͠Εͳ͍? • UICollectionReusableView.preferredLayoutAttributesFitting(_:) UICollectionView
  69. Displaying Messages • Լʹ޲͔ͬͯ৽͍͠ϝοηʔδΛฒ΂Δ • IndexPath͕Ұ൪େ͖͍΋ͷ͕࠷৽ͷϝοηʔδʹͳΔΑ͏ʹ Using UICollectionView

  70. Loading Messages • ӬଓԽ͍ͯ͠Δ͢΂ͯͷϝοηʔδΛදࣔ͢ΔͷͰ͸ͳ͘ɺ࠷৽ͷϝοηʔδ͔Βগͮͭ͠ CollectionViewʹ௥Ճ͍ͯ͘͠ • ॳظදࣔύϑΥʔϚϯεͷͨΊ • ྫ͑͹ɺ࠷ॳ͸20݅දࣔ •

    ϝοηʔδड৴ɾૹ৴Ͱn݅௥Ճ • աڈͷϝοηʔδͷಡΈࠐΈͰ20݅௥Ճ • DB΁ͷΫΤϦ͸දࣔ݅਺Λ૿΍͢͝ͱʹ࣮ߦ͢ΔͷͰ͸ͳ͘ɺશ݅औಘͯ͠ArraySliceΛ࡞ΓɺͦΕ Λ࢖ͬͯCollectionViewʹදࣔΛ͍ͯ͘͠ Using UICollectionView
  71. • ࠷৽ͷϝοηʔδ͸Ұ൪Լʹදࣔ͞ΕΔͷͰɺScrollToBottomͷॲཧ͕ඞཁʹͳΔ • ͜ͷ࣮૷ͰϋϚΔ͜ͱ͕݁ߏ͋Δ • ͏·͘Ұ൪ԼʹεΫϩʔϧ͞Εͳ͍ͱ͖ͷετϨε͸൒୺ͳ͍ (։ൃऀͱͯ͠΋Ϣʔβʔͱͯ͠΋) • MessageKitͰ͸ࢼߦࡨޡͷ݁Ռγϯϓϧͳίʔυʹམͪண͍͍ͯΔ༷ࢠ •

    https://github.com/MessageKit/MessageKit/blob/ 28ac5f5e4180f55e75140417a9d6dabd2c6fda03/Sources/Views/ MessagesCollectionView.swift#L97:5 Scroll to bottom in UICollectionView
  72. 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
  73. εΫϩʔϧҐஔΛҡ࣋ͨ͠··ΞΠςϜΛ௥Ճ͢Δ աڈͷϝοηʔδΛಡΈࠐΉ • աڈͷϝοηʔδΛಡΈࠐΉ࣌͸ɺҰ൪্·ͰεΫϩʔϧΛ͢Δ͜ͱͰࣗಈతʹ্෦ʹϝοηʔδ͕ੵ· Εͯߦ͘Α͏ͳݟͤํ͕Ұൠత • ͔͠͠ɺUICollectionViewͷಛੑ্ɺݟ͍ͯΔ෦෼ΑΓ্෦ʹΞΠςϜ͕ૠೖ͞ΕΔͱεΫϩʔϧҐஔ͕ มΘͬͯ͠·͏ɻ • ૠೖ͞Εͨ෼ͷheightΛߟྀͯ͠contentOffsetΛ࠶ௐ੔ͯ͋͛͠ΔରԠ͕ඞཁ

    1.ΞΠςϜ௥ՃલʹcontentOffsetΛอ࣋ 2.ΞΠςϜ௥Ճ 3.௥ՃޙͷcontentSizeͱอ࣋ͨ͠contentOffsetΛ࢖͍ɺ্ʹΞΠςϜ͕௥Ճ͞ΕͨΑ͏ʹݟͤΔ
  74. None
  75. None
  76. εΫϩʔϧҐஔͷҡ࣋Λগָ͠ʹ͢ΔLayout • KeepingContentOffsetCollectionViewLayout • https://gist.github.com/muukii/29249c295a0f2c4fecde61d99abc94f5 • UICollectionViewFlowLayout.targetContentOffsetΛΦʔόʔϥΠυ͢ΔΞϓϩʔν աڈͷϝοηʔδΛಡΈࠐΉ

  77. (collectionView.collectionViewLayout as! KeepingContentOffsetCollectionViewLayout) .performWithKeepingContentOffset { (completion) in collectionView.performBatchUpdates({ // Update

    items }, completion: { _ in completion() }) } KeepingContentOffsetCollectionViewLayout աڈͷϝοηʔδΛಡΈࠐΉ
  78. Another approach

  79. Cell Cell

  80. Cell Cell

  81. • UICollectionViewΛ180౓ճస (transform) • UICollectionViewCellΛ180౓ճస (transform) • ͢ΔͱɺIndexPath (0, 0)

    ͕Ұ൪Լʹදࣔ͞ΕΔΑ͏ʹͳΔ Inverted UICollectionView
  82. ✅ ͜Ε͕͏·͍͘͘ͱ Inverted UICollectionView • Scroll to bottom͸Scroll to topʹͳָͬͯʹͳΔ

    • աڈͷϝοηʔδಡΈࠐΈ࣌ʹεΫϩʔϧҐஔΛҡ࣋͢Δॲཧ͕ෆཁʹͳΔ
  83. ⚠ ஫ҙ఺ Inverted UICollectionView • ΞΠςϜ͕গͳ͍࣌ʹը໘্෦ʹදࣔ͢Δ͜ͱ͕೉͘͠ͳΔ • εςʔλεόʔͷλοϓʹΑΓScroll to top͕ߦΘΕΔͷͰٯʹ͢Δඞཁ͕͋Δ

    • ͦͷ··ͩͱεςʔλεόʔΛλοϓͰ࠷৽ͷϝοηʔδ·ͰඈΜͰ͠·͏ • contentInsetͷઃఆ͕͢΂ͯٯ • OS͕ࣗಈͰઃఆͯ͘͠ΕΔ஋͸͢΂ͯແޮԽͯ͠खಈͰઃఆ͢Δ (safeAreaͳͲʣ
  84. OSS • UITableView༻Ͱ͕͢ɺ͜ͷΞϓϩʔνΛ࣮ݱ͢ΔOSS͕͋Γ·͢ • https://github.com/marty-suzuki/ReverseExtension • TextureͷASCollectionNodeʹ͸invertedϓϩύςΟ༻ҙ͞Ε͍ͯΔ Inverted UICollectionView

  85. Compose Message View

  86. None
  87. We need GrowingTextView.

  88. Implementing Growing Text View is too hard • ਓੜͰҰ൪ਏ͍ࢥ͍Λͨ͠UI •

    ΩʔϘʔυͷ্෦ʹுΓ෇͍ͨςΩετೖྗUI • վߦʹԠͯ͡৳ॖ • ৳ͼ͍͚ͯͩ͘ͳΒ؆୯͕ͩɺ৳ॖʹ্ݶΛઃ͚Δͱɺٸʹ࣮૷͕೉͘͠ ͳΔ • ݁ߏݹ͍͚ͲOSS͋Γ·͢ • https://github.com/muukii/NextGrowingTextView • iOS Messagesͷ࣮૷ઃܭͱಉ͡ (ࠓ͸Θ͔Βͳ͍) • ΠϚυΩͳϞμϯͳ࣮૷͕ؾʹͳ͍ͬͯΔͱ͜Ζ It will work better with muukii/NextGrowingTextView.
  89. ͓ΘΓʹ • νϟοτ͸SNSܥΞϓϦʹ͓͍ͯ࠷΋ॏཁͳػೳͰ͋Γɺ࣮૷͸೉͍͠৔໘͕ଟʑ͋Δ • ೉͘͠ߟ͑͗͢ΔͱɺͲΜͲΜ೉͘͠ͳ͍ͬͯ͘ • σʔλઃܭ΍UIઃܭʹ͓͍ͯॊೈͳΞΠσΞ͕ٻΊΒΕΔ • ࠓճൃදͨ͠಺༰ʹՃ͑ɺϦΞϧλΠϜԽͷΞϓϩʔν࣍ୈͰ·ͨঢ়گ͸มΘͬͯ͘Δ͸ͣ •

    ࠷ۙͰ͸MessageKit (UI) ΍ ChatKit(SaaS)ͳͲ͕ొ৔͖͓ͯͯ͠Γɺར༻͢Δͷ΋ྑ͍͠ɺ࣮ݱํ๏ͷ ࢀߟʹ΋ͳΔ • νϟοτΛ࡞Ζ͏ͱࢥ͍ͬͯΔਓɺνϟοτͱಆ͖ͬͯͨਓͱҰॹʹ࿩Λ͠·͠ΐ͏ 1
  90. Thank you