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
This commit is contained in:
Michael Kirk 2016-10-06 19:33:34 -04:00
parent 9a9f24d8d1
commit a28fea8384
3 changed files with 167 additions and 4 deletions

View file

@ -38,7 +38,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>2.6.0.7</string>
<string>2.6.0.9</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LOGS_EMAIL</key>

View file

@ -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 <JSQMessagesViewController/JSQMessagesCollectionViewFlowLayout.h>
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<JSQMessageData>)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<JSQMessageData>)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

View file

@ -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]