Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
minneのメッセージ機能改善活動
Search
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
nakajijapan
December 15, 2015
Technology
2.8k
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
minneのメッセージ機能改善活動
http://shibuya-swift.connpass.com/event/21979/
nakajijapan
December 15, 2015
More Decks by nakajijapan
See All by nakajijapan
今時のフルスタック主導の iOSアプリ開発
nakajijapan
0
380
サービスにおけるDesign Systemの構築
nakajijapan
11
4.2k
Markdownをリアルタイムに解析する
nakajijapan
5
4.8k
Firebase Authorization
nakajijapan
0
360
Intoducing Izumo
nakajijapan
1
2.1k
Practical CloudKit
nakajijapan
1
1.9k
Introducing to Ajimi - プロダクトを味見していこう
nakajijapan
0
2.5k
Shari
nakajijapan
1
3.1k
Japan Apple Pay Development
nakajijapan
0
350
Other Decks in Technology
See All in Technology
Futexes the good, the bad, the ugly
ennael
PRO
0
110
AI時代のAPI開発を加速する品質ガードレール / API Quality Guardrails in the AI Era
yokawasa
0
100
自主式軟體工廠
philipz
0
190
Execution in the Kingdom of Agents: Reflections on Abstraction and Complexity
bcantrill
0
810
VS Code × GitHub Copilot での Fabric 開発
ryomaru0825
1
270
『GOエコノミー 』(相乗りサービス) におけるスペック駆動開発
mot_techtalk
1
300
検証フェーズはAIの回答を判断するための学習機会
toru_kubota
2
490
【データ横丁主催】AI Agentがコンテキストを使って仕事をした後、何が残るのか― 組織の経験を次の判断に引き継ぐ「Agent Memory」
shisyu_gaku
2
320
「とりあえず動く」の先へ。 AI時代のチーム開発と内部設計/2026-slsdays
slsops
0
130
Incremental HTTP
kazuho
5
2k
SDDの運用にめげずに向き合った話
sansantech
PRO
0
130
個別開発で終わらせない。 現場の課題をプロダクトの強さに変える StockmarkのFDE
ktkrhr
0
580
Featured
See All Featured
How To Stay Up To Date on Web Technology
chriscoyier
790
250k
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
540
Technical Leadership for Architectural Decision Making
baasie
3
590
State of Search Keynote: SEO is Dead Long Live SEO
ryanjones
0
300
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
3.2k
Balancing Empowerment & Direction
lara
6
1.3k
Scaling GitHub
holman
464
140k
Between Models and Reality
mayunak
4
480
The Hidden Cost of Media on the Web [PixelPalooza 2025]
tammyeverts
2
510
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
35
2.9k
Context Engineering - Making Every Token Count
addyosmani
9
1.2k
<Decoding/> the Language of Devs - We Love SEO 2024
nikkihalliwell
1
340
Transcript
ϝοηʔδ ػೳվળ׆ಈ shibuya.swift #2 @nakajijapan
About Me
@nakajijapan Software Engineer GMO PEPABO inc. iOS / Web /
OS X
GitHub
NKJMultiMovieCaptureView NKJMovieComposer NKJPagerViewController PhotoSlider etc Teiten
NKJMultiMovieCaptureView NKJMovieComposer NKJPagerViewController PhotoSlider etc Teiten SwiftԽʂʂʂʂ
minne iOS / Android
400 million app download ࠃ࠷େͷϋϯυϝΠυϚʔέοτ
Google Play™ετΞ ʮ2015ϕετΞϓ Ϧʯʹબग़ʂ
ँ
Talk about
ϝοηʔδ ػೳվળ׆ಈ
ϝοηʔδ ػೳ
ϝοηʔδػೳ • 201408݄ϦϦʔε • ࡞Ո͞ΜͱίϛϡχέʔγϣϯͰ͖Δ • ؾܰʹϝοηʔδަͯ͠΄͍͠
ϝοηʔδػೳվળ׆ಈ • ܦҢ • มߋ༰ • ϝοηʔδೖྗ • Ұཡ •
·ͱΊ
ܦҢ
ܦҢ • όʔδϣϯΞοϓͰΠϯλʔϑΣʔε ΛҰ৽͕ͨ͠ɺΠϯλϥΫγϣϯͷ վળ์ஔؾຯͩͬͨ • ཁόάͷ૿Ճ
ܦҢ • όʔδϣϯΞοϓͰΠϯλʔϑΣʔε ΛҰ৽͕ͨ͠ɺΠϯλϥΫγϣϯͷ վળ์ஔؾຯͩͬͨ • ཁόάͷ૿Ճ ͬͱ҆৺͍ͤͨ͞ʂ
վળ
લఏࣝ
Text Kit
UITextView • ෳߦͷςΩετྖҬɾεΫϩʔϧΛཧ͢Δ • Πϯελϯεੜ࣌ʹҎԼͷΠϯελϯεੜ • NSTextContainer • NSLayoutManager •
NSTextStorage • ͍ͣΕreadOnly
Text Kit • NSTextContainer • ςΩετͷ࠲ඪͱܗঢ়Λཧ • NSLayoutManager • ϨΠΞτͱϨϯμϦϯάΛௐ
• NSTextStorage • NSMutableAttributedStringͷαϒΫϥε
ϝοηʔδೖྗ
ϝοηʔδೖྗ • Growing Text View • ಈతʹTextྖҬΛ֦ு͢Δ • ϦΞϧλΠϜϓϨϏϡʔ •
ૹ৴ͨ͠ঢ়ଶͱಉ͡ͷΛදࣔ͢Δ
MITextInputView MITextView NSLayoutConstraint *heightConstraint;
Growing Text View • ೖྗͨ࣌͠ͷΠϕϯτΛܭଌ͢Δ • ߴ͞Λܭଌ͠ɺViewΛߋ৽͢Δ
Growing Text View [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(textDidChange:) name:UITextViewTextDidChangeNotification object:nil];
Growing Text View ςΩετ UITextView
roundf(textView.font.lineHeight * numberOfLines) textView.textContainerInset.bottom textView.textContainerInset.top Growing Text View ςΩετ UITextView
Growing Text View • ྖҬ֦େ࣌ͷ࠷େΛߦͰࢦఆ • ࡉ͔͍ߴ͞ͷௐ͕Մೳ • ΫϥεΛར༻͢Δͷ͕ຖߴ͞Λ ҙࣝ͠ͳͯ͘ࡁΉ
վߦҐஔͷݻఆ վߦ࣌ ΧʔιϧΛৗʹ࠷ԼҐʹ ஔͰ͖ΔΑ͏ʹ͢Δ
վߦҐஔͷݻఆ • ࠷େߦҎԼ • Viewͷ੍ʢߴ͞ʣΛߋ৽͢Δ • ৗʹ࠷্෦εΫϩʔϧͤ͞Δ • ࠷େߦΛ͑ͨ •
Viewͷ੍ʢߴ͞ʣΛߋ৽͢Δ • ৗʹ࠷Լ෦εΫϩʔϧͤ͞Δ
վߦҐஔͷݻఆ [self.textView setContentOffset:CGPointMake(0.f, 0.f) animated:NO]; if (self.textView.selectedRange.location == self.textView.text.length) {
NSRange range = NSMakeRange(self.textView.text.length, 0.f); self.textView.selectedRange = range; CGFloat scrollY = self.textView.contentSize.height - self.textView.bounds.size.height; if (scrollY < 0.f) { scrollY = 0.f; } CGPoint scrollPoint = CGPointMake(0.f, scrollY); [self.textView setContentOffset:scrollPoint animated:YES]; } ࠷େߦҎ ࠷େߦҎ্
ϦΞϧλΠϜ ϓϨϏϡʔ
ϦΞϧλΠϜϓϨϏϡʔ
ϦΞϧλΠϜϓϨϏϡʔ
ϦΞϧλΠϜϓϨϏϡʔ
ϦΞϧλΠϜϓϨϏϡʔ
ϦΞϧλΠϜϓϨϏϡʔ • ը૾ߘͷϩδοΫΛվળ • ࠓ·Ͱ • ը૾બ࣌ʹαʔόʹPOST • TextViewʹදࣔ͞ΕΔͷͦͷURL
ϦΞϧλΠϜϓϨϏϡʔ • ը૾ߘͷϩδοΫΛվળ • ࠓ·Ͱ • ը૾બ࣌ʹαʔόʹPOST • TextViewʹදࣔ͞ΕΔͷͦͷURL ➡ϝοηʔδૹ৴࣌ʹ·ͱΊͯૹ৴
➡ߘͨ͠ը૾ΛϓϨϏϡʔͤ͞Δ
ϦΞϧλΠϜϓϨϏϡʔ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textInputView.textView.attributedText]; NSTextAttachment *textAttachment =
[[NSTextAttachment alloc] init]; textAttachment.image = image; textAttachment.bounds = CGRectMake(0.f,0.f,100.f,100.f); NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [attributedString replaceCharactersInRange:self.textInputView.textView.selectedRange withAttributedString:attrStringWithImage]; [self.textInputView setAttributedText:attributedString]; ը૾બ࣌
ϦΞϧλΠϜϓϨϏϡʔ NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textInputView.textView.attributedText]; NSTextAttachment *textAttachment =
[[NSTextAttachment alloc] init]; textAttachment.image = image; textAttachment.bounds = CGRectMake(0.f,0.f,100.f,100.f); NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [attributedString replaceCharactersInRange:self.textInputView.textView.selectedRange withAttributedString:attrStringWithImage]; [self.textInputView setAttributedText:attributedString]; ը૾બ࣌ ը૾ͷૠೖ
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithAttributedString:self.textInputView.textView.attributedText]; NSTextAttachment *textAttachment = [[NSTextAttachment
alloc] init]; textAttachment.image = image; textAttachment.bounds = CGRectMake(0.f,0.f,100.f,100.f); NSAttributedString *attrStringWithImage = [NSAttributedString attributedStringWithAttachment:textAttachment]; [attributedString replaceCharactersInRange:self.textInputView.textView.selectedRange withAttributedString:attrStringWithImage]; [self.textInputView setAttributedText:attributedString]; ը૾બ࣌ Χʔιϧʹ͋ΔҐஔʹը૾Λૠೖ ը૾બޙͷಈ͖
ը૾બޙͷಈ͖ ݱࡏͷߴ͞Λࢉग़ͯ͠ TextViewྖҬΛ͛Δ
TextViewͷྖҬΛ֦ு self.textViewHeightConstraint.constant = [self heightForLines:self.maxNumberOfLines];
ొॲཧ
ϝοηʔδૹ৴ - վળલ • ը૾બ࣌ʹ௨৴ͯ͠ը૾Λొ • ͦͷޙɺURLΛऔಘ͢Δ • ϝοηʔδొ •
ܗ͞Εͨϝοηʔδͷૹ৴
ϝοηʔδૹ৴ - վળޙ • ϝοηʔδొ࣌ͷྲྀΕ • ొ͞Εͨը૾ͷૹ৴ • औಘͨ͠ը૾URLͱը૾Λஔ •
ܗ͞Εͨϝοηʔδͷૹ৴
GCD
GCD • dispatch queue ͱ͍͏ΩϡʔʹॲཧΛߦ͍ͨ ͍༰(λεΫ)ΛՃͯ͠ߦ͘ • ΩϡʔʹՃ͞ΕͨλεΫFIFOͰॱ൪ʹ࣮ ߦ͞ΕΔ •
λεΫͷ࣮ߦผεϨου্Ͱ࣮ߦ͞ΕΔ IUUQTHJUIVCDPNNJYJJODJ045SBJOJOHXJLJ(SBOE$FOUSBM%JTQBUDI
dispatch group • ͍͔ͭ͘ͷॲཧΛࢄͯ͠ߦ͍͍ͨɺ ͦͯ͠ࢄͯ͠ॲཧΛߦͬͨ݁ՌΛ ·ͱΊͯར༻͍ͨ͠ɺͱ͍ͬͨ࣌ʹ ར༻Ͱ͖Δͷ IUUQTHJUIVCDPNNJYJJODJ045SBJOJOHXJLJ(SBOE$FOUSBM%JTQBUDI
ϝοηʔδૹ৴ ϝοηʔδૹ৴ - վળલ
άϧʔϓ։࢝ ऴྃॲཧ ؔ࿈͚։࢝ EJTQBUDI@HSPVQ@FOUFS ؔ࿈͚ऴྃ ը૾ొ ϝοηʔδૹ৴ EJTQBUDI@HSPVQ@MFBWF EJTQBUDI@HSPVQ@OPUJGZ EJTQBUDI@HSPVQ@DSFBUF
ϝοηʔδૹ৴ - վળޙ
άϧʔϓ։࢝ ऴྃॲཧ ؔ࿈͚։࢝ EJTQBUDI@HSPVQ@FOUFS ؔ࿈͚ऴྃ ը૾ొ ϝοηʔδૹ৴ EJTQBUDI@HSPVQ@MFBWF EJTQBUDI@HSPVQ@OPUJGZ EJTQBUDI@HSPVQ@DSFBUF
ϝοηʔδૹ৴ - վળޙ ը૾ͷஔҐஔΛอ͍࣋ͯ͠Ε ॱෆಉʹͳͬͯͳ͍
ϝοηʔδૹ৴ self.dispatchGroup = dispatch_group_create(); // attributedString͔Βը૾Λநग़ if (images.count > 0)
{ for (UIImage *tmpImage in images) { [self sendImage:tmpImage]; } } dispatch_group_notify(self.dispatchGroup, dispatch_get_main_queue(), ^{ [self operationDidFinishWithAttributedString:attributedString indexPath:indexPath]; }); dispatch_group_enter(self.dispatchGroup); // snip AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { // snip dispatch_group_leave(self.dispatchGroup); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { dispatch_group_leave(self.dispatchGroup); }]; [self.messageQueue addOperation:operation]; άϧʔϓͷ࡞
ϝοηʔδૹ৴ self.dispatchGroup = dispatch_group_create(); self.savedImageTags = [NSMutableArray array]; // snip
if (images.count > 0) { for (UIImage *tmpImage in images) { [self sendImage:tmpImage]; } } dispatch_group_notify(self.dispatchGroup, dispatch_get_main_queue(), ^{ [self operationDidFinishWithAttributedString:attributedString indexPath:indexPath]; }); dispatch_group_enter(self.dispatchGroup); // snip AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { // snip dispatch_group_leave(self.dispatchGroup); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { dispatch_group_leave(self.dispatchGroup); }]; [self.messageQueue addOperation:operation]; ؔ࿈͚։࢝ ؔ࿈͚ऴྃ
ϝοηʔδૹ৴ self.dispatchGroup = dispatch_group_create(); // attributedString͔Βը૾Λநग़ if (images.count > 0)
{ for (UIImage *tmpImage in images) { [self sendImage:tmpImage]; } } dispatch_group_notify(self.dispatchGroup, dispatch_get_main_queue(), ^{ [self operationDidFinishWithAttributedString:attributedString indexPath:indexPath]; }); dispatch_group_enter(self.dispatchGroup); // snip AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) { // snip dispatch_group_leave(self.dispatchGroup); } failure:^(AFHTTPRequestOperation *operation, NSError *error) { dispatch_group_leave(self.dispatchGroup); }]; [self.messageQueue addOperation:operation]; ϝοηʔδొॲཧ
࠶ૹॲཧ
࠶ૹॲཧ • λΠϜΞτ࣌ • ࣦഊͨ͜͠ͱΛ௨ • ࠶ૹͰ͖Δঢ়ଶʹ
Ұཡ
Ұཡ • ٯʹϖʔδϯά͢Δํ๏ • ௨ৗͷϖʔδϯά • ٯʹ͢Δͱͳʹ͕͔
ϖʔδϯά
௨ৗͷϖʔδϯά Loading
௨ৗͷϖʔδϯά - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { if (section ==
1) { self.indicatorView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"MIIndicatorFooterView"]; [self getReviews]; return self.indicatorView; } return nil; } FooterView͕දࣔ͞ΕͨλΠϛϯάͰߦ͏
௨ৗͷϖʔδϯά - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { if (section ==
1) { self.indicatorView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"MIIndicatorFooterView"]; [self getReviews]; return self.indicatorView; } return nil; } FooterView͕දࣔ͞ΕͨλΠϛϯάͰߦ͏ ϔομʹҠಈ͢Ε͍͍ͷͰʁ
௨ৗͷϖʔδϯά - (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section { if (section ==
1) { self.indicatorView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:@"MIIndicatorFooterView"]; [self getReviews]; return self.indicatorView; } return nil; } FooterView͕දࣔ͞ΕͨλΠϛϯάͰߦ͏ ϔομʹҠಈ͢Ε͍͍ͷͰʁ
Կ͕͔ • σʔλ্͕෦ʹՃ͞Ε͍ͯ͘ • reloadData()࣌ʹcontentOffsetͷ͕ ͣΕΔ • ׳ੑ͕͍ͬͯΔঢ়گͩͱڧ੍తʹ Ҡಈ͞Εͯ͠·͏
ௐࠪ • ඪ४ϝοηʔδΞϓϦͷಈ͖ • දࣔ࣌ʹϩʔσΟϯά։࢝ • ׳ੑεΫϩʔϧ͕ఀࢭͨ͠λΠϛϯ άͰऔಘ։࢝
ௐࠪ • ඪ४ϝοηʔδΞϓϦͷಈ͖ • දࣔ࣌ʹϩʔσΟϯά։࢝ • ׳ੑεΫϩʔϧ͕ఀࢭͨ͠λΠϛϯ άͰऔಘ։࢝
ղܾࡦ • ׳ੑεΫϩʔϧ͕ఀࢭͨ͠ • ScrollToTop࣌׳ੑεΫϩʔϧ͕ൃ ੜ͠ͳ͍ - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView -
(BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView
·ͱΊ
·ͱΊ • ࡉ͔͍UXͷվળ • ςΩετೖྗɺ௨৴ॲཧɺϖʔδϯά • ͍ΖΜͳΞϓϦͷUXΛૢ࡞ɾݕূɺϓ ϥάΠϯͷ࣮ΛಡΜͩΓ͢Δͱࢳޱ ͕ݟ͑Δ
ࢀߟ JSQMessagesViewController SlackTextViewController etc
Thanks.