Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

1BJSTʹ͍ͭͯ !4

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

Կ͔ͱνϟοτΛ࣮૷͖ͯͨ͠ ͜Ε·Ͱ࣮૷͖ͯͨ͠νϟοτ • 2013೥ Pairs ೔ຊ൛ using CoreData (΋͏͜ͷίʔυ͸ফ͞Ε͍ͯΔ) • 2014೥ Couples ࣮૷ using CoreData • 2017೥ Pairs άϩʔόϧ൛ using Realm • ϝοηʔδͷӬଓԽख๏Ͱ͸CoreDataͱRealmͷ྆ํͷܦݧ͕͋Δ͕ɺ
 Realmͷ΄͏͕ύϑΥʔϚϯενϡʔχϯά͕ߦ͍΍͔ͬͨ͢Πϝʔδ͕͋Δɻ

Slide 9

Slide 9 text

Pairs Global App

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

ϝοηʔδͷ؅ཧͱऔಘ

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

ϝοηʔδͷऔಘ • ϝοηʔδͷऔಘํ๏͸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

Slide 17

Slide 17 text

Offset Paginationͷྫ

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

਎ۙͳͱ͜ΖͰ͸ 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

Slide 28

Slide 28 text

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" }

Slide 29

Slide 29 text

"ts": "1358546515.000008" Microseconds Timestamp UniqueKey

Slide 30

Slide 30 text

1 timestamp

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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'

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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.

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

ϝοηʔδͷૹ৴

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

• ૹ৴։࢝࣌ʹϝοηʔδ͕දࣔ͞ΕΔ (ૹ৴த) • ✅ ૹ৴੒ޭ • ૹ৴׬ྃͷදࣔΛߦ͏ • ૹ৴ࣦഊ • ૹ৴ࣦഊͷදࣔΛߦ͏ • ΞϓϦͷλεΫΛ੾ΒΕͯ΋εςʔλεΛอ؅͢ΔͨΊૹ৴։࢝࣌఺Ͱϝοηʔδ͸ӬଓԽ͢Δ ϝοηʔδͷૹ৴

Slide 54

Slide 54 text

ϝοηʔδΦϒδΣΫτͷεΩʔϚ 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 } ϝοηʔδͷૹ৴

Slide 55

Slide 55 text

ૹ৴ঢ়ଶͷදݱ 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 } ϝοηʔδͷૹ৴

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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 } ૹ৴͢ΔϝοηʔδΛτϥοΩϯά͢Δ

Slide 63

Slide 63 text

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

Slide 64

Slide 64 text

User Interface

Slide 65

Slide 65 text

User Interface Send

Slide 66

Slide 66 text

User Interface Send Bubble Balloon Compose View UICollectionView UITableView

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

No content

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

Another approach

Slide 79

Slide 79 text

Cell Cell

Slide 80

Slide 80 text

Cell Cell

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

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

Slide 84

Slide 84 text

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

Slide 85

Slide 85 text

Compose Message View

Slide 86

Slide 86 text

No content

Slide 87

Slide 87 text

We need GrowingTextView.

Slide 88

Slide 88 text

Implementing Growing Text View is too hard • ਓੜͰҰ൪ਏ͍ࢥ͍Λͨ͠UI • ΩʔϘʔυͷ্෦ʹுΓ෇͍ͨςΩετೖྗUI • վߦʹԠͯ͡৳ॖ • ৳ͼ͍͚ͯͩ͘ͳΒ؆୯͕ͩɺ৳ॖʹ্ݶΛઃ͚Δͱɺٸʹ࣮૷͕೉͘͠ ͳΔ • ݁ߏݹ͍͚ͲOSS͋Γ·͢ • https://github.com/muukii/NextGrowingTextView • iOS Messagesͷ࣮૷ઃܭͱಉ͡ (ࠓ͸Θ͔Βͳ͍) • ΠϚυΩͳϞμϯͳ࣮૷͕ؾʹͳ͍ͬͯΔͱ͜Ζ It will work better with muukii/NextGrowingTextView.

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

Thank you