From 65efa7f8366bffd252e51a3369b1a8c80e050f64 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 16 Oct 2017 10:39:52 -0400 Subject: [PATCH] Lazy load, eagerly unload & cache cell media. // FREEBIE --- .../Cells/OWSAudioMessageView.h | 2 +- .../Cells/OWSAudioMessageView.m | 76 ++++---- .../Cells/OWSGenericAttachmentView.h | 2 +- .../Cells/OWSGenericAttachmentView.m | 72 ++++---- .../ConversationView/Cells/OWSMessageCell.m | 170 +++++++++++++----- .../ConversationView/ConversationViewItem.h | 7 + 6 files changed, 203 insertions(+), 126 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.h index fc7465fbb..7200788e3 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN isIncoming:(BOOL)isIncoming viewItem:(ConversationViewItem *)viewItem; -- (void)createContentsForSize:(CGSize)viewSize; +- (void)createContents; + (CGFloat)bubbleHeight; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m index 11e0aa03e..d4120221a 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSAudioMessageView.m @@ -193,34 +193,46 @@ NS_ASSUME_NONNULL_BEGIN return (self.attachmentStream.isVoiceMessage || self.attachmentStream.sourceFilename.length < 1); } -- (void)createContentsForSize:(CGSize)viewSize +- (void)createContents { UIColor *textColor = [self audioTextColor]; self.backgroundColor = self.bubbleBackgroundColor; + self.layoutMargins = UIEdgeInsetsZero; + // TODO: Verify that this layout works in RTL. const CGFloat kBubbleTailWidth = 6.f; - CGRect contentFrame = CGRectMake(self.isIncoming ? kBubbleTailWidth : 0.f, - self.audioIconVMargin, - viewSize.width - kBubbleTailWidth - self.audioIconHMargin, - viewSize.height - self.audioIconVMargin * 2); - - CGRect iconFrame = CGRectMake((CGFloat)round(contentFrame.origin.x + self.audioIconHMargin), - (CGFloat)round(contentFrame.origin.y + (contentFrame.size.height - self.iconSize) * 0.5f), - self.iconSize, - self.iconSize); - _audioPlayPauseButton = [[UIButton alloc] initWithFrame:iconFrame]; - _audioPlayPauseButton.enabled = NO; - [self addSubview:_audioPlayPauseButton]; + + UIView *contentView = [UIView containerView]; + [self addSubview:contentView]; + [contentView autoPinLeadingToSuperviewWithMargin:self.isIncoming ? kBubbleTailWidth : 0.f]; + [contentView autoPinTrailingToSuperviewWithMargin:self.isIncoming ? 0.f : kBubbleTailWidth]; + [contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.audioIconVMargin]; + [contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.audioIconVMargin]; + + _audioPlayPauseButton = [UIButton buttonWithType:UIButtonTypeCustom]; + self.audioPlayPauseButton.enabled = NO; + [contentView addSubview:self.audioPlayPauseButton]; + [self.audioPlayPauseButton autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:self.audioIconHMargin]; + [self.audioPlayPauseButton autoVCenterInSuperview]; + [self.audioPlayPauseButton autoSetDimension:ALDimensionWidth toSize:self.iconSize]; + [self.audioPlayPauseButton autoSetDimension:ALDimensionHeight toSize:self.iconSize]; const CGFloat kLabelHSpacing = self.audioIconHSpacing; + + UIView *labelsView = [UIView containerView]; + [contentView addSubview:labelsView]; + [labelsView autoPinLeadingToTrailingOfView:self.audioPlayPauseButton margin:kLabelHSpacing]; + [labelsView autoPinEdgeToSuperviewEdge:ALEdgeRight]; + [labelsView autoVCenterInSuperview]; + const CGFloat kLabelVSpacing = 2; NSString *filename = self.attachmentStream.sourceFilename; if (!filename) { filename = [[self.attachmentStream filePath] lastPathComponent]; } NSString *topText = [[filename stringByDeletingPathExtension] - stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; + stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; if (topText.length < 1) { topText = [MIMETypeUtil fileExtensionForMIMEType:self.attachmentStream.contentType].uppercaseString; } @@ -235,13 +247,19 @@ NS_ASSUME_NONNULL_BEGIN topLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; topLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; topLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; - [topLabel sizeToFit]; - [self addSubview:topLabel]; + topLabel.textAlignment = NSTextAlignmentLeft; + [labelsView addSubview:topLabel]; + [topLabel autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [topLabel autoPinWidthToSuperview]; + const CGFloat kAudioProgressViewHeight = 12.f; AudioProgressView *audioProgressView = [AudioProgressView new]; self.audioProgressView = audioProgressView; [self updateAudioProgressView]; - [self addSubview:audioProgressView]; + [labelsView addSubview:audioProgressView]; + [audioProgressView autoPinWidthToSuperview]; + [audioProgressView autoSetDimension:ALDimensionHeight toSize:kAudioProgressViewHeight]; + [audioProgressView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:topLabel withOffset:kLabelVSpacing]; UILabel *bottomLabel = [UILabel new]; self.audioBottomLabel = bottomLabel; @@ -249,25 +267,11 @@ NS_ASSUME_NONNULL_BEGIN bottomLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; bottomLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; bottomLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; - [bottomLabel sizeToFit]; - [self addSubview:bottomLabel]; - - const CGFloat topLabelHeight = (CGFloat)ceil(topLabel.font.lineHeight); - const CGFloat kAudioProgressViewHeight = 12.f; - const CGFloat bottomLabelHeight = (CGFloat)ceil(bottomLabel.font.lineHeight); - CGRect labelsBounds = CGRectZero; - labelsBounds.origin.x = (CGFloat)round(iconFrame.origin.x + iconFrame.size.width + kLabelHSpacing); - labelsBounds.size.width = contentFrame.origin.x + contentFrame.size.width - labelsBounds.origin.x; - labelsBounds.size.height = topLabelHeight + kAudioProgressViewHeight + bottomLabelHeight + kLabelVSpacing * 2; - labelsBounds.origin.y - = (CGFloat)round(contentFrame.origin.y + (contentFrame.size.height - labelsBounds.size.height) * 0.5f); - - CGFloat y = labelsBounds.origin.y; - topLabel.frame = CGRectMake(labelsBounds.origin.x, labelsBounds.origin.y, labelsBounds.size.width, topLabelHeight); - y += topLabelHeight + kLabelVSpacing; - audioProgressView.frame = CGRectMake(labelsBounds.origin.x, y, labelsBounds.size.width, kAudioProgressViewHeight); - y += kAudioProgressViewHeight + kLabelVSpacing; - bottomLabel.frame = CGRectMake(labelsBounds.origin.x, y, labelsBounds.size.width, bottomLabelHeight); + bottomLabel.textAlignment = NSTextAlignmentLeft; + [labelsView addSubview:bottomLabel]; + [bottomLabel autoPinWidthToSuperview]; + [bottomLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:audioProgressView withOffset:kLabelVSpacing]; + [bottomLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom]; [self updateContents]; } diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.h b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.h index f3b095709..73b9bcaba 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.h +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.h @@ -10,7 +10,7 @@ NS_ASSUME_NONNULL_BEGIN - (instancetype)initWithAttachment:(TSAttachmentStream *)attachmentStream isIncoming:(BOOL)isIncoming; -- (void)createContentsForSize:(CGSize)viewSize; +- (void)createContents; + (CGFloat)bubbleHeight; diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m index f6831a6fa..97544e6b2 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSGenericAttachmentView.m @@ -98,32 +98,43 @@ NS_ASSUME_NONNULL_BEGIN return [self.textColor blendWithColor:self.bubbleBackgroundColor alpha:alpha]; } -- (void)createContentsForSize:(CGSize)viewSize +- (void)createContents { UIColor *textColor = (self.isIncoming ? [UIColor colorWithWhite:0.2 alpha:1.f] : [UIColor whiteColor]); self.backgroundColor = self.bubbleBackgroundColor; + self.layoutMargins = UIEdgeInsetsZero; + // TODO: Verify that this layout works in RTL. const CGFloat kBubbleTailWidth = 6.f; - CGRect contentFrame = CGRectMake(self.isIncoming ? kBubbleTailWidth : 0.f, - self.vMargin, - viewSize.width - kBubbleTailWidth - self.iconHMargin, - viewSize.height - self.vMargin * 2); + + UIView *contentView = [UIView containerView]; + [self addSubview:contentView]; + [contentView autoPinLeadingToSuperviewWithMargin:self.isIncoming ? kBubbleTailWidth : 0.f]; + [contentView autoPinTrailingToSuperviewWithMargin:self.isIncoming ? 0.f : kBubbleTailWidth]; + [contentView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:self.vMargin]; + [contentView autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:self.vMargin]; UIImage *image = [UIImage imageNamed:@"generic-attachment-small"]; OWSAssert(image); UIImageView *imageView = [UIImageView new]; - CGRect iconFrame = CGRectMake(round(contentFrame.origin.x + self.iconHMargin), - round(contentFrame.origin.y + (contentFrame.size.height - self.iconSize) * 0.5f), - self.iconSize, - self.iconSize); - imageView.frame = iconFrame; imageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; imageView.tintColor = self.bubbleBackgroundColor; imageView.backgroundColor = (self.isIncoming ? [UIColor colorWithRGBHex:0x9e9e9e] : [self foregroundColorWithOpacity:0.15f]); imageView.layer.cornerRadius = MIN(imageView.bounds.size.width, imageView.bounds.size.height) * 0.5f; - [self addSubview:imageView]; + [contentView addSubview:imageView]; + [imageView autoPinEdgeToSuperviewEdge:ALEdgeLeft withInset:self.iconHMargin]; + [imageView autoVCenterInSuperview]; + [imageView autoSetDimension:ALDimensionWidth toSize:self.iconSize]; + [imageView autoSetDimension:ALDimensionHeight toSize:self.iconSize]; + + const CGFloat kLabelHSpacing = self.iconHSpacing; + UIView *labelsView = [UIView containerView]; + [contentView addSubview:labelsView]; + [labelsView autoPinLeadingToTrailingOfView:imageView margin:kLabelHSpacing]; + [labelsView autoPinEdgeToSuperviewEdge:ALEdgeRight]; + [labelsView autoVCenterInSuperview]; NSString *filename = self.attachmentStream.sourceFilename; if (!filename) { @@ -145,19 +156,11 @@ NS_ASSUME_NONNULL_BEGIN fileTypeLabel.font = [UIFont ows_mediumFontWithSize:20.f]; fileTypeLabel.adjustsFontSizeToFitWidth = YES; fileTypeLabel.textAlignment = NSTextAlignmentCenter; - CGRect fileTypeLabelFrame = CGRectZero; - fileTypeLabelFrame.size = [fileTypeLabel sizeThatFits:CGSizeZero]; - // This dimension depends on the space within the icon boundaries. - fileTypeLabelFrame.size.width = 15.f; // Center on icon. - fileTypeLabelFrame.origin.x - = round(iconFrame.origin.x + (iconFrame.size.width - fileTypeLabelFrame.size.width) * 0.5f); - fileTypeLabelFrame.origin.y - = round(iconFrame.origin.y + (iconFrame.size.height - fileTypeLabelFrame.size.height) * 0.5f); - fileTypeLabel.frame = fileTypeLabelFrame; - [self addSubview:fileTypeLabel]; + [imageView addSubview:fileTypeLabel]; + [imageView autoCenterInSuperview]; + [imageView autoSetDimension:ALDimensionWidth toSize:15.f]; - const CGFloat kLabelHSpacing = self.iconHSpacing; const CGFloat kLabelVSpacing = 2; NSString *topText = [self.attachmentStream.sourceFilename stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; @@ -172,8 +175,10 @@ NS_ASSUME_NONNULL_BEGIN topLabel.textColor = textColor; topLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; topLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(13.f, 15.f)]; - [topLabel sizeToFit]; - [self addSubview:topLabel]; + topLabel.textAlignment = NSTextAlignmentLeft; + [labelsView addSubview:topLabel]; + [topLabel autoPinEdgeToSuperviewEdge:ALEdgeTop]; + [topLabel autoPinWidthToSuperview]; NSError *error; unsigned long long fileSize = @@ -185,21 +190,10 @@ NS_ASSUME_NONNULL_BEGIN bottomLabel.textColor = [textColor colorWithAlphaComponent:0.85f]; bottomLabel.lineBreakMode = NSLineBreakByTruncatingMiddle; bottomLabel.font = [UIFont ows_regularFontWithSize:ScaleFromIPhone5To7Plus(11.f, 13.f)]; - [bottomLabel sizeToFit]; - [self addSubview:bottomLabel]; - - CGRect topLabelFrame = CGRectZero; - topLabelFrame.size = topLabel.bounds.size; - topLabelFrame.origin.x = round(iconFrame.origin.x + iconFrame.size.width + kLabelHSpacing); - topLabelFrame.origin.y = round(contentFrame.origin.y - + (contentFrame.size.height - (topLabel.frame.size.height + bottomLabel.frame.size.height + kLabelVSpacing)) - * 0.5f); - topLabelFrame.size.width = round((contentFrame.origin.x + contentFrame.size.width) - topLabelFrame.origin.x); - topLabel.frame = topLabelFrame; - - CGRect bottomLabelFrame = topLabelFrame; - bottomLabelFrame.origin.y += topLabelFrame.size.height + kLabelVSpacing; - bottomLabel.frame = bottomLabelFrame; + [labelsView addSubview:bottomLabel]; + [bottomLabel autoPinWidthToSuperview]; + [bottomLabel autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:topLabel withOffset:kLabelVSpacing]; + [bottomLabel autoPinEdgeToSuperviewEdge:ALEdgeBottom]; } @end diff --git a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m index a2fcaa804..001ca4789 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m +++ b/Signal/src/ViewControllers/ConversationView/Cells/OWSMessageCell.m @@ -243,7 +243,7 @@ NS_ASSUME_NONNULL_BEGIN case OWSMessageCellType_GenericAttachment: { self.attachmentView = [[OWSGenericAttachmentView alloc] initWithAttachment:self.attachmentStream isIncoming:self.isIncoming]; - [self.attachmentView createContentsForSize:self.bounds.size]; + [self.attachmentView createContents]; [self replaceBubbleWithView:self.attachmentView]; [self addAttachmentUploadViewIfNecessary:self.attachmentView]; break; @@ -254,6 +254,8 @@ NS_ASSUME_NONNULL_BEGIN } } + [self ensureViewMediaState]; + // dispatch_async(dispatch_get_main_queue(), ^{ // NSLog(@"---- %@", self.viewItem.interaction.debugDescription); // NSLog(@"cell: %@", NSStringFromCGRect(self.frame)); @@ -263,6 +265,116 @@ NS_ASSUME_NONNULL_BEGIN // }); } +- (nullable id)tryToLoadCellMedia:(nullable id (^)())loadCellMediaBlock mediaView:(UIView *)mediaView +{ + OWSAssert(self.attachmentStream); + OWSAssert(mediaView); + + if (self.viewItem.didCellMediaFailToLoad) { + return nil; + } + id _Nullable cellMedia = self.viewItem.cachedCellMedia; + if (cellMedia) { + DDLogVerbose(@"%@ cell media cache hit", self.logTag); + return cellMedia; + } + cellMedia = loadCellMediaBlock(); + if (cellMedia) { + DDLogVerbose(@"%@ cell media cache miss", self.logTag); + self.viewItem.cachedCellMedia = cellMedia; + } else { + DDLogError(@"%@ Failed to load cell media: %@", [self logTag], [self.attachmentStream mediaURL]); + self.viewItem.didCellMediaFailToLoad = YES; + [mediaView removeFromSuperview]; + // TODO: We need to hide/remove the media view. + [self showAttachmentErrorView]; + } + return cellMedia; +} + +// We want to lazy-load expensive view contents and eagerly unload if the +// cell is no longer visible. +- (void)ensureViewMediaState +{ + if (!self.isCellVisible) { + // Eagerly unload. + if (self.stillImageView.image || self.animatedImageView.image) { + DDLogError(@"%@ ---- ensureViewMediaState unloading[%zd]: %@", + self.logTag, + self.viewItem.row, + self.viewItem.interaction.description); + } + self.stillImageView.image = nil; + self.animatedImageView.image = nil; + return; + } + + switch (self.cellType) { + case OWSMessageCellType_StillImage: { + if (self.stillImageView.image) { + return; + } + DDLogError(@"%@ ---- ensureViewMediaState loading[%zd]: %@", + self.logTag, + self.viewItem.row, + self.viewItem.interaction.description); + self.stillImageView.image = [self tryToLoadCellMedia:^{ + OWSAssert([self.attachmentStream isImage]); + return self.attachmentStream.image; + } + mediaView:self.stillImageView]; + break; + } + case OWSMessageCellType_AnimatedImage: { + if (self.animatedImageView.image) { + return; + } + DDLogError(@"%@ ---- ensureViewMediaState loading[%zd]: %@", + self.logTag, + self.viewItem.row, + self.viewItem.interaction.description); + self.animatedImageView.image = [self tryToLoadCellMedia:^{ + OWSAssert([self.attachmentStream isAnimated]); + + NSString *_Nullable filePath = [self.attachmentStream filePath]; + YYImage *_Nullable animatedImage = nil; + if (filePath && [NSData ows_isValidImageAtPath:filePath]) { + animatedImage = [YYImage imageWithContentsOfFile:filePath]; + } + return animatedImage; + } + mediaView:self.animatedImageView]; + break; + } + case OWSMessageCellType_Audio: + // TODO: Lazy load audio length in audio cells. + // [self loadForAudioDisplay]; + break; + case OWSMessageCellType_Video: { + if (self.stillImageView.image) { + return; + } + DDLogError(@"%@ ---- ensureViewMediaState loading[%zd]: %@", + self.logTag, + self.viewItem.row, + self.viewItem.interaction.description); + self.stillImageView.image = [self tryToLoadCellMedia:^{ + OWSAssert([self.attachmentStream isVideo]); + + return self.attachmentStream.image; + } + mediaView:self.stillImageView]; + break; + } + case OWSMessageCellType_TextMessage: + case OWSMessageCellType_OversizeTextMessage: + case OWSMessageCellType_GenericAttachment: + case OWSMessageCellType_DownloadingAttachment: + // Inexpensive cell types don't need to lazy-load or eagerly-unload. + break; + } +} + - (void)updateDateHeader { OWSAssert(self.contentWidth > 0); @@ -454,14 +566,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(self.attachmentStream); OWSAssert([self.attachmentStream isImage]); - UIImage *_Nullable image = self.attachmentStream.image; - if (!image) { - DDLogError(@"%@ Could not load image: %@", [self logTag], [self.attachmentStream mediaURL]); - [self showAttachmentErrorView]; - return; - } - - self.stillImageView = [[UIImageView alloc] initWithImage:image]; + self.stillImageView = [UIImageView new]; // We need to specify a contentMode since the size of the image // might not match the aspect ratio of the view. self.stillImageView.contentMode = UIViewContentModeScaleAspectFill; @@ -478,19 +583,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(self.attachmentStream); OWSAssert([self.attachmentStream isAnimated]); - NSString *_Nullable filePath = [self.attachmentStream filePath]; - YYImage *_Nullable animatedImage = nil; - if (filePath && [NSData ows_isValidImageAtPath:filePath]) { - animatedImage = [YYImage imageWithContentsOfFile:filePath]; - } - if (!animatedImage) { - DDLogError(@"%@ Could not load animated image: %@", [self logTag], [self.attachmentStream mediaURL]); - [self showAttachmentErrorView]; - return; - } - self.animatedImageView = [[YYAnimatedImageView alloc] init]; - self.animatedImageView.image = animatedImage; // We need to specify a contentMode since the size of the image // might not match the aspect ratio of the view. self.animatedImageView.contentMode = UIViewContentModeScaleAspectFill; @@ -507,7 +600,7 @@ NS_ASSUME_NONNULL_BEGIN isIncoming:self.isIncoming viewItem:self.viewItem]; self.viewItem.lastAudioMessageView = self.audioMessageView; - [self.audioMessageView createContentsForSize:self.bounds.size]; + [self.audioMessageView createContents]; [self replaceBubbleWithView:self.audioMessageView]; [self addAttachmentUploadViewIfNecessary:self.audioMessageView]; } @@ -517,16 +610,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert(self.attachmentStream); OWSAssert([self.attachmentStream isVideo]); - // CGSize size = [self mediaViewDisplaySize]; - - UIImage *_Nullable image = self.attachmentStream.image; - if (!image) { - DDLogError(@"%@ Could not load image: %@", [self logTag], [self.attachmentStream mediaURL]); - [self showAttachmentErrorView]; - return; - } - - self.stillImageView = [[UIImageView alloc] initWithImage:image]; + self.stillImageView = [UIImageView new]; // We need to specify a contentMode since the size of the image // might not match the aspect ratio of the view. self.stillImageView.contentMode = UIViewContentModeScaleAspectFill; @@ -616,25 +700,11 @@ NS_ASSUME_NONNULL_BEGIN [self.payloadView updateMask]; } -//// TODO: -//- (void)setFrame:(CGRect)frame { -// [super setFrame:frame]; -// -// DDLogError(@"setFrame: %@ %@ %@", self.viewItem.interaction.uniqueId, self.viewItem.interaction.description, -// NSStringFromCGRect(frame)); -//} -// -//// TODO: -//- (void)setBounds:(CGRect)bounds { -// [super setBounds:bounds]; -// -// DDLogError(@"setBounds: %@ %@ %@", self.viewItem.interaction.uniqueId, self.viewItem.interaction.description, -// NSStringFromCGRect(bounds)); -//} - - (void)showAttachmentErrorView { - // TODO: We could do a better job of indicating that the image could not be loaded. + OWSAssert(!self.customView); + + // TODO: We could do a better job of indicating that the media could not be loaded. self.customView = [UIView new]; self.customView.backgroundColor = [UIColor colorWithWhite:0.85f alpha:1.f]; self.customView.userInteractionEnabled = NO; @@ -862,6 +932,8 @@ NS_ASSUME_NONNULL_BEGIN return; } + [self ensureViewMediaState]; + if (isCellVisible) { if (self.message.shouldStartExpireTimer) { [self.expirationTimerView ensureAnimations]; diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h index e48a1f12f..1827a7a6d 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewItem.h @@ -86,6 +86,13 @@ NSString *NSStringForOWSMessageCellType(OWSMessageCellType cellType); - (nullable TSAttachmentPointer *)attachmentPointer; - (CGSize)contentSize; +// A generic property that cells can use to cache their loaded +// media. This cache is volatile and will get evacuated based +// on scroll state, so that we only retain state for a sliding +// window of cells that are almost on-screen. +@property (nonatomic) id cachedCellMedia; +@property (nonatomic) BOOL didCellMediaFailToLoad; + // TODO: //// Cells will request that this adapter clear its cached media views, //// but the adapter should only honor requests from the last cell to