Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
minneのメッセージ機能改善活動
Search
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
サービスにおけるDesign Systemの構築
nakajijapan
11
4.2k
Markdownをリアルタイムに解析する
nakajijapan
5
4.6k
Firebase Authorization
nakajijapan
0
340
Intoducing Izumo
nakajijapan
1
2k
Practical CloudKit
nakajijapan
1
1.9k
Introducing to Ajimi - プロダクトを味見していこう
nakajijapan
0
2.4k
Shari
nakajijapan
1
3k
Japan Apple Pay Development
nakajijapan
0
340
業務で絶対必要にならない技術
nakajijapan
0
900
Other Decks in Technology
See All in Technology
地域 SRE コミュニティ最前線 / SRE NEXT 2026 Discussion Night Track C
muziyoshiz
0
130
ゼロをイチにする仕事が終わったあと
smasato
0
310
AI Driven AI Governance
pict3
0
160
Empower GenAI with Agile - あなたのアジャイルが生成AIのバフになる仕組み
hageyahhoo
0
130
美しいコードを書くためにF#を学んでみた話
yud0uhu
1
280
FinOps X 2026 Recap from Engineer Side #JapanFinOps
chacco38
0
260
なぜ人は自分のプロジェクトを 「なんちゃってアジャイル」と 自嘲するのか
kozotaira
0
260
SRE歴2ヶ月でも開発6年の知見を活かして、チームで止まっていた環境改善を前に進めた話
a_ono
1
210
金融の未来を考える / Thinking About the Future of Finance
ks91
PRO
0
180
Claude Codeとハーネスについて考えてみる
oikon48
18
8.7k
AI時代のエンジニアキャリアについて今一度考える
sakamoto_582
2
1.3k
“ID沼入口” - 基本とセキュリティから始める、考え続けるためのID管理技術勉強会 告知&イントロ
ritou
0
440
Featured
See All Featured
Technical Leadership for Architectural Decision Making
baasie
3
430
It's Worth the Effort
3n
188
29k
More Than Pixels: Becoming A User Experience Designer
marktimemedia
3
460
Being A Developer After 40
akosma
91
590k
Design in an AI World
tapps
1
260
Ten Tips & Tricks for a 🌱 transition
stuffmc
0
150
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
A Soul's Torment
seathinner
6
3k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
WENDY [Excerpt]
tessaabrams
11
38k
Skip the Path - Find Your Career Trail
mkilby
1
160
The Cost Of JavaScript in 2023
addyosmani
55
10k
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.