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
0 commit comments