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]