From a28fea8384c6d67b26931bde3e056a62f02e48e0 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Thu, 6 Oct 2016 19:33:34 -0400 Subject: [PATCH] Fix emoji message truncation on iOS10 fixes #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 --- Signal/Signal-Info.plist | 2 +- .../Models/OWSMessagesBubblesSizeCalculator.m | 139 +++++++++++++++++- .../view controllers/MessagesViewController.m | 30 ++++ 3 files changed, 167 insertions(+), 4 deletions(-) diff --git a/Signal/Signal-Info.plist b/Signal/Signal-Info.plist index 07c6c66aa..6588ebe00 100644 --- a/Signal/Signal-Info.plist +++ b/Signal/Signal-Info.plist @@ -38,7 +38,7 @@ CFBundleVersion - 2.6.0.7 + 2.6.0.9 ITSAppUsesNonExemptEncryption LOGS_EMAIL diff --git a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m index bec016ac8..53923183c 100644 --- a/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m +++ b/Signal/src/Models/OWSMessagesBubblesSizeCalculator.m @@ -4,6 +4,25 @@ #import "OWSDisplayedMessageCollectionViewCell.h" #import "TSMessageAdapter.h" #import "tgmath.h" // generic math allows fmax to handle CGFLoat correctly on 32 & 64bit. +#import + +NS_ASSUME_NONNULL_BEGIN + +// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 +// superclass protected methods we need in order to compute bubble size. +@interface OWSMessagesBubblesSizeCalculator (OWSiOS10EmojiBug) + +@property (strong, nonatomic, readonly) NSCache *cache; +@property (assign, nonatomic, readonly) NSUInteger minimumBubbleWidth; +@property (assign, nonatomic, readonly) BOOL usesFixedWidthBubbles; +@property (assign, nonatomic, readonly) NSInteger additionalInset; +@property (assign, nonatomic) CGFloat layoutWidthForFixedWidthBubbles; + +- (CGSize)jsq_avatarSizeForMessageData:(id)messageData + withLayout:(JSQMessagesCollectionViewFlowLayout *)layout; +- (CGFloat)textBubbleWidthForLayout:(JSQMessagesCollectionViewFlowLayout *)layout; +@end +// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 @implementation OWSMessagesBubblesSizeCalculator @@ -22,11 +41,24 @@ atIndexPath:(NSIndexPath *)indexPath withLayout:(JSQMessagesCollectionViewFlowLayout *)layout { - CGSize superSize = [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout]; + CGSize size; + + // BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + BOOL isIOS10OrGreater = + [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 10 }]; + if (isIOS10OrGreater) { + size = [self withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:messageData + atIndexPath:indexPath + withLayout:layout]; + } else { + size = [super messageBubbleSizeForMessageData:messageData atIndexPath:indexPath withLayout:layout]; + } + // END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 if ([messageData isKindOfClass:[TSMessageAdapter class]]) { TSMessageAdapter *message = (TSMessageAdapter *)messageData; + if (message.messageType == TSInfoMessageAdapter || message.messageType == TSErrorMessageAdapter) { // DDLogVerbose(@"[OWSMessagesBubblesSizeCalculator] superSize.height:%f, superSize.width:%f", // superSize.height, @@ -35,11 +67,112 @@ // header icon hangs ouside of the frame a bit. CGFloat headerIconProtrusion = 30.0f; // too much padding with normal font. // CGFloat headerIconProtrusion = 18.0f; // clips - superSize.height = superSize.height + headerIconProtrusion; + size.height += headerIconProtrusion; } } - return superSize; + return size; +} + +/** + * HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + * iOS10 bug in rendering emoji requires to fudge some things in the middle of the super method. + * Copy/pasted the superclass method and inlined (and marked) our hacks inline. + */ +- (CGSize)withiOS10EmojiFixSuperMessageBubbleSizeForMessageData:(id)messageData + atIndexPath:(NSIndexPath *)indexPath + withLayout:(JSQMessagesCollectionViewFlowLayout *)layout +{ + NSValue *cachedSize = [self.cache objectForKey:@([messageData messageHash])]; + if (cachedSize != nil) { + return [cachedSize CGSizeValue]; + } + + CGSize finalSize = CGSizeZero; + + if ([messageData isMediaMessage]) { + finalSize = [[messageData media] mediaViewDisplaySize]; + } else { + CGSize avatarSize = [self jsq_avatarSizeForMessageData:messageData withLayout:layout]; + + // from the cell xibs, there is a 2 point space between avatar and bubble + CGFloat spacingBetweenAvatarAndBubble = 2.0f; + CGFloat horizontalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.left + + layout.messageBubbleTextViewTextContainerInsets.right; + CGFloat horizontalFrameInsets + = layout.messageBubbleTextViewFrameInsets.left + layout.messageBubbleTextViewFrameInsets.right; + + CGFloat horizontalInsetsTotal + = horizontalContainerInsets + horizontalFrameInsets + spacingBetweenAvatarAndBubble; + CGFloat maximumTextWidth = [self textBubbleWidthForLayout:layout] - avatarSize.width + - layout.messageBubbleLeftRightMargin - horizontalInsetsTotal; + + /////////////////// + // BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + + // //stringRect doesn't give the correct size with the new emoji font. + // CGRect stringRect = [[messageData text] boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX) + // options:(NSStringDrawingUsesLineFragmentOrigin | + // NSStringDrawingUsesFontLeading) + // attributes:@{ NSFontAttributeName : + // layout.messageBubbleFont } + // context:nil]; + + CGRect stringRect; + if (!messageData.text) { + stringRect = CGRectZero; + } else { + NSDictionary *attributes = @{ NSFontAttributeName : layout.messageBubbleFont }; + NSMutableAttributedString *string = + [[NSMutableAttributedString alloc] initWithString:[messageData text] attributes:attributes]; + [string fixAttributesInRange:NSMakeRange(0, string.length)]; + [string + enumerateAttribute:NSFontAttributeName + inRange:NSMakeRange(0, string.length) + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + UIFont *font = (UIFont *)value; + if ([font.fontName isEqualToString:@".AppleColorEmojiUI"]) { + DDLogVerbose(@"Replacing new broken emoji font with old emoji font at location: %lu, " + @"for length: %lu", + (unsigned long)range.location, + (unsigned long)range.length); + [string addAttribute:NSFontAttributeName + value:[UIFont fontWithName:@"AppleColorEmoji" size:font.pointSize] + range:range]; + } + }]; + stringRect = + [string boundingRectWithSize:CGSizeMake(maximumTextWidth, CGFLOAT_MAX) + options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) + context:nil]; + } + // END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + ///////////////////////// + + CGSize stringSize = CGRectIntegral(stringRect).size; + + CGFloat verticalContainerInsets = layout.messageBubbleTextViewTextContainerInsets.top + + layout.messageBubbleTextViewTextContainerInsets.bottom; + CGFloat verticalFrameInsets + = layout.messageBubbleTextViewFrameInsets.top + layout.messageBubbleTextViewFrameInsets.bottom; + + // add extra 2 points of space (`self.additionalInset`), because `boundingRectWithSize:` is slightly off + // not sure why. magix. (shrug) if you know, submit a PR + CGFloat verticalInsets = verticalContainerInsets + verticalFrameInsets + self.additionalInset; + + // same as above, an extra 2 points of magix + CGFloat finalWidth + = MAX(stringSize.width + horizontalInsetsTotal, self.minimumBubbleWidth) + self.additionalInset; + + finalSize = CGSizeMake(finalWidth, stringSize.height + verticalInsets); + } + + [self.cache setObject:[NSValue valueWithCGSize:finalSize] forKey:@([messageData messageHash])]; + + return finalSize; } @end + +NS_ASSUME_NONNULL_END diff --git a/Signal/src/view controllers/MessagesViewController.m b/Signal/src/view controllers/MessagesViewController.m index 20a01f958..32a7d0161 100644 --- a/Signal/src/view controllers/MessagesViewController.m +++ b/Signal/src/view controllers/MessagesViewController.m @@ -741,6 +741,9 @@ typedef enum : NSUInteger { { JSQMessagesCollectionViewCell *cell = (JSQMessagesCollectionViewCell *)[super collectionView:self.collectionView cellForItemAtIndexPath:indexPath]; + // BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + [self fixupiOS10EmojiBugForTextView:cell.textView]; + // END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 if (!message.isMediaMessage) { cell.textView.textColor = [UIColor ows_blackColor]; cell.textView.linkTextAttributes = @{ @@ -758,6 +761,11 @@ typedef enum : NSUInteger { OWSOutgoingMessageCollectionViewCell *cell = (OWSOutgoingMessageCollectionViewCell *)[super collectionView:self.collectionView cellForItemAtIndexPath:indexPath]; + + // BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + [self fixupiOS10EmojiBugForTextView:cell.textView]; + // END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + if (!message.isMediaMessage) { cell.textView.textColor = [UIColor whiteColor]; cell.textView.linkTextAttributes = @{ @@ -768,6 +776,28 @@ typedef enum : NSUInteger { return cell; } +// BEGIN HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 +- (void)fixupiOS10EmojiBugForTextView:(UITextView *)textView +{ + BOOL isIOS10OrGreater = + [[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){.majorVersion = 10 }]; + if (isIOS10OrGreater) { + [textView.textStorage enumerateAttribute:NSFontAttributeName + inRange:NSMakeRange(0, textView.textStorage.length) + options:0 + usingBlock:^(id _Nullable value, NSRange range, BOOL *_Nonnull stop) { + UIFont *font = (UIFont *)value; + if ([font.fontName isEqualToString:@".AppleColorEmojiUI"]) { + [textView.textStorage addAttribute:NSFontAttributeName + value:[UIFont fontWithName:@"AppleColorEmoji" + size:font.pointSize] + range:range]; + } + }]; + } +} +// END HACK iOS10EmojiBug see: https://github.com/WhisperSystems/Signal-iOS/issues/1368 + - (OWSCallCollectionViewCell *)loadCallCellForCall:(OWSCall *)call atIndexPath:(NSIndexPath *)indexPath { OWSCallCollectionViewCell *callCell = [self.collectionView dequeueReusableCellWithReuseIdentifier:[OWSCallCollectionViewCell cellReuseIdentifier]