Skip to content

Commit bb48132

Browse files
committed
Fix emoji message truncation on iOS10
fixes signalapp#1368 Apple switched emoji fonts from AppleColorEmoji to AppleColorEmojiUI. The new font doesn't compute it's size correctly, causing containing rectangles to be too small. This commit scrubs strings of the new emoji font, and replaces it with the old. // FREEBIE
1 parent e3ec5f5 commit bb48132

File tree

3 files changed

+159
-4
lines changed

3 files changed

+159
-4
lines changed

Signal/Signal-Info.plist

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
</dict>
3939
</array>
4040
<key>CFBundleVersion</key>
41-
<string>2.6.0.7</string>
41+
<string>2.6.0.8</string>
4242
<key>ITSAppUsesNonExemptEncryption</key>
4343
<false/>
4444
<key>LOGS_EMAIL</key>

Signal/src/Models/OWSMessagesBubblesSizeCalculator.m

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,25 @@
44
#import "OWSDisplayedMessageCollectionViewCell.h"
55
#import "TSMessageAdapter.h"
66
#import "tgmath.h" // generic math allows fmax to handle CGFLoat correctly on 32 & 64bit.
7+
#import <JSQMessagesViewController/JSQMessagesCollectionViewFlowLayout.h>
8+
9+
NS_ASSUME_NONNULL_BEGIN
10+
11+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
12+
// superclass protected methods we need in order to compute bubble size.
13+
@interface OWSMessagesBubblesSizeCalculator (OWSiOS10EmojiBug)
14+
15+
@property (strong, nonatomic, readonly) NSCache *cache;
16+
@property (assign, nonatomic, readonly) NSUInteger minimumBubbleWidth;
17+
@property (assign, nonatomic, readonly) BOOL usesFixedWidthBubbles;
18+
@property (assign, nonatomic, readonly) NSInteger additionalInset;
19+
@property (assign, nonatomic) CGFloat layoutWidthForFixedWidthBubbles;
20+
21+
- (CGSize)jsq_avatarSizeForMessageData:(id<JSQMessageData>)messageData
22+
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout;
23+
- (CGFloat)textBubbleWidthForLayout:(JSQMessagesCollectionViewFlowLayout *)layout;
24+
@end
25+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
726

827
@implementation OWSMessagesBubblesSizeCalculator
928

@@ -22,11 +41,22 @@ - (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
2241
atIndexPath:(NSIndexPath *)indexPath
2342
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
2443
{
25-
CGSize superSize = [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout];
44+
CGSize size = [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout];
45+
46+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
47+
BOOL isIOS10OrGreater =
48+
[[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 10 }];
49+
if (isIOS10OrGreater) {
50+
size = [self withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:messageData
51+
atIndexPath:indexPath
52+
withLayout:layout];
53+
}
54+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
2655

2756
if ([messageData isKindOfClass:[TSMessageAdapter class]]) {
2857
TSMessageAdapter *message = (TSMessageAdapter *)messageData;
2958

59+
3060
if (message.messageType == TSInfoMessageAdapter || message.messageType == TSErrorMessageAdapter) {
3161
// DDLogVerbose(@"[OWSMessagesBubblesSizeCalculator] superSize.height:%f, superSize.width:%f",
3262
// superSize.height,
@@ -35,11 +65,106 @@ - (CGSize)messageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
3565
// header icon hangs ouside of the frame a bit.
3666
CGFloat headerIconProtrusion = 30.0f; // too much padding with normal font.
3767
// CGFloat headerIconProtrusion = 18.0f; // clips
38-
superSize.height = superSize.height + headerIconProtrusion;
68+
size.height += headerIconProtrusion;
3969
}
4070
}
4171

42-
return superSize;
72+
return size;
73+
}
74+
75+
/**
76+
* HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
77+
* iOS10 bug in rendering emoji requires to fudge some things in the middle of the super method.
78+
* Copy/pasted the superclass method and inlined (and marked) our hacks inline.
79+
*/
80+
- (CGSize)withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:(id<JSQMessageData>)messageData
81+
atIndexPath:(NSIndexPath *)indexPath
82+
withLayout:(JSQMessagesCollectionViewFlowLayout *)layout
83+
{
84+
NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])];
85+
if (cachedSize != nil) {
86+
return [cachedSize CGSizeValue];
87+
}
88+
89+
CGSize finalSize = CGSizeZero;
90+
91+
if ([messageData isMediaMessage]) {
92+
finalSize = [[messageData media] mediaViewDisplaySize];
93+
} else {
94+
CGSize avatarSize = [self jsq_avatarSizeForMessageData:messageData withLayout:layout];
95+
96+
// from the cell xibs, there is a 2 point space between avatar and bubble
97+
CGFloat spacingBetweenAvatarAndBubble = 2.0f;
98+
CGFloat horizontalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.left
99+
+ layout.messageBubbleTextViewTextContainerInsets.right;
100+
CGFloat horizontalFrameInsets
101+
= layout.messageBubbleTextViewFrameInsets.left + layout.messageBubbleTextViewFrameInsets.right;
102+
103+
CGFloat horizontalInsetsTotal
104+
= horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble;
105+
CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - avatarSize.width
106+
- layout.messageBubbleLeftRightMargin - horizontalInsetsTotal;
107+
108+
///////////////////
109+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
110+
111+
// //stringRect doesn't give the correct size with the new emoji font.
112+
// CGRect stringRect = [[messageData text] boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
113+
// options:(NSStringDrawingUsesLineFragmentOrigin |
114+
// NSStringDrawingUsesFontLeading)
115+
// attributes:@{ NSFontAttributeName :
116+
// layout.messageBubbleFont }
117+
// context:nil];
118+
119+
NSDictionary *attributes = @{ NSFontAttributeName : layout.messageBubbleFont };
120+
NSMutableAttributedString *string =
121+
[[NSMutableAttributedString alloc] initWithString:[messageData text] attributes:attributes];
122+
[string fixAttributesInRange:NSMakeRange(0, string.length)];
123+
[string enumerateAttribute:NSFontAttributeName
124+
inRange:NSMakeRange(0, string.length)
125+
options:0
126+
usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
127+
UIFont *font = (UIFont *)value;
128+
if ([font.fontName isEqualToString:@".AppleColorEmojiUI"]) {
129+
DDLogVerbose(@"Replacing new broken emoji font with old emoji font at location: %lu, "
130+
@"for length: %lu",
131+
(unsigned long)range.location,
132+
(unsigned long)range.length);
133+
[string addAttribute:NSFontAttributeName
134+
value:[UIFont fontWithName:@"AppleColorEmoji" size:font.pointSize]
135+
range:range];
136+
}
137+
}];
138+
CGRect stringRect =
139+
[string boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX)
140+
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
141+
context:nil];
142+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
143+
/////////////////////////
144+
145+
CGSize stringSize = CGRectIntegral(stringRect).size;
146+
147+
CGFloat verticalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.top
148+
+ layout.messageBubbleTextViewTextContainerInsets.bottom;
149+
CGFloat verticalFrameInsets
150+
= layout.messageBubbleTextViewFrameInsets.top + layout.messageBubbleTextViewFrameInsets.bottom;
151+
152+
// add extra 2 points of space (`self.additionalInset`), because `boundingRectWithSize:` is slightly off
153+
// not sure why. magix. (shrug) if you know, submit a PR
154+
CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + self.additionalInset;
155+
156+
// same as above, an extra 2 points of magix
157+
CGFloat finalWidth
158+
= MAX(stringSize.width + horizontalInsetsTotal, self.minimumBubbleWidth) + self.additionalInset;
159+
160+
finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets);
161+
}
162+
163+
[self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])];
164+
165+
return finalSize;
43166
}
44167

45168
@end
169+
170+
NS_ASSUME_NONNULL_END

Signal/src/view controllers/MessagesViewController.m

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,9 @@ - (JSQMessagesCollectionViewCell *)loadIncomingMessageCellForMessage:(id<OWSMess
741741
{
742742
JSQMessagesCollectionViewCell *cell =
743743
(JSQMessagesCollectionViewCell *)[super collectionView:self.collectionView cellForItemAtIndexPath:indexPath];
744+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
745+
[self fixupiOS10EmojiBugForTextView:cell.textView];
746+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
744747
if (!message.isMediaMessage) {
745748
cell.textView.textColor = [UIColor ows_blackColor];
746749
cell.textView.linkTextAttributes = @{
@@ -758,6 +761,11 @@ - (JSQMessagesCollectionViewCell *)loadOutgoingCellForMessage:(id<OWSMessageData
758761
OWSOutgoingMessageCollectionViewCell *cell
759762
= (OWSOutgoingMessageCollectionViewCell *)[super collectionView:self.collectionView
760763
cellForItemAtIndexPath:indexPath];
764+
765+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
766+
[self fixupiOS10EmojiBugForTextView:cell.textView];
767+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
768+
761769
if (!message.isMediaMessage) {
762770
cell.textView.textColor = [UIColor whiteColor];
763771
cell.textView.linkTextAttributes = @{
@@ -768,6 +776,28 @@ - (JSQMessagesCollectionViewCell *)loadOutgoingCellForMessage:(id<OWSMessageData
768776
return cell;
769777
}
770778

779+
// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
780+
- (void)fixupiOS10EmojiBugForTextView:(UITextView *)textView
781+
{
782+
BOOL isIOS10OrGreater =
783+
[[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 10 }];
784+
if (isIOS10OrGreater) {
785+
[textView.textStorage enumerateAttribute:NSFontAttributeName
786+
inRange:NSMakeRange(0, textView.textStorage.length)
787+
options:0
788+
usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) {
789+
UIFont *font = (UIFont *)value;
790+
if ([font.fontName isEqualToString:@".AppleColorEmojiUI"]) {
791+
[textView.textStorage addAttribute:NSFontAttributeName
792+
value:[UIFont fontWithName:@"AppleColorEmoji"
793+
size:font.pointSize]
794+
range:range];
795+
}
796+
}];
797+
}
798+
}
799+
// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368
800+
771801
- (OWSCallCollectionViewCell *)loadCallCellForCall:(OWSCall *)call atIndexPath:(NSIndexPath *)indexPath
772802
{
773803
OWSCallCollectionViewCell *callCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSCallCollectionViewCell cellReuseIdentifier]

0 commit comments

Comments
 (0)