From 17befbeeeefab951fc08f5c273cb4956229794ff Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 6 Jan 2021 12:58:24 -0300 Subject: [PATCH] Fix handling of max content widths in the message cell layout. --- .../CV/CVComponents/CVComponentMessage.swift | 47 ++++++++++++++----- .../Cells/CVMediaAlbumView.swift | 24 +++++++++- 2 files changed, 56 insertions(+), 15 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentMessage.swift b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentMessage.swift index 0aa68aa6a0..b988e99f02 100644 --- a/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentMessage.swift +++ b/Signal/src/ViewControllers/ConversationView/CV/CVComponents/CVComponentMessage.swift @@ -1,5 +1,5 @@ // -// Copyright (c) 2020 Open Whisper Systems. All rights reserved. +// Copyright (c) 2021 Open Whisper Systems. All rights reserved. // import Foundation @@ -515,6 +515,12 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { } } + if let contentWidth = cellMeasurement.value(key: contentWidthKey) { + componentView.layoutConstraints.append(outerContentView.autoSetDimension(.width, toSize: contentWidth)) + } else { + owsFailDebug("Missing contentWidth.") + } + if let subview = outerAvatarView { subview.setContentHuggingHigh() subview.setCompressionResistanceHigh() @@ -694,21 +700,41 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { return CVStackView.measure(config: config, subviewSizes: subviewSizes) } + private let contentWidthKey = "contentWidthKey" + public func measure(maxWidth maxWidthChatHistory: CGFloat, measurementBuilder: CVCellMeasurement.Builder) -> CGSize { owsAssertDebug(maxWidthChatHistory > 0) - let maxWidth = min(maxWidthChatHistory, conversationStyle.maxMessageWidth) - let outerStackViewMaxWidth = max(0, maxWidth - cellLayoutMargins.totalWidth) + let outerStackViewMaxWidth = max(0, maxWidthChatHistory - cellLayoutMargins.totalWidth) var cellHStackSize = CGSize.zero var outerStackViewSize: CGSize = .zero + if hasSenderAvatarLayout { + // Sender avatar in groups. + outerStackViewSize.width += ConversationStyle.groupMessageAvatarDiameter + ConversationStyle.messageStackSpacing + outerStackViewSize.height = max(outerStackViewSize.height, ConversationStyle.groupMessageAvatarDiameter) + } + if isShowingSelectionUI { + outerStackViewSize.width += selectionViewWidth + selectionViewSpacing + } + if !isIncoming, hasSendFailureBadge { + outerStackViewSize.width += sendFailureBadgeSize + sendFailureBadgeSpacing + } + // The message cell's "outer" stack can contain many views: + // sender avatar, selection UI, send failure badge. + // The message cell's "content" stack must fit within the + // remaining space in the "outer" stack. + let contentMaxWidth = max(0, + min(conversationStyle.maxMessageWidth, + outerStackViewMaxWidth - outerStackViewSize.width)) + func measureStackView(config: CVStackViewConfig, componentKeys keys: [CVComponentKey]) -> CGSize { - let contentMaxWidth = max(0, outerStackViewMaxWidth - outerStackViewSize.width) + let maxStackWidth = max(0, contentMaxWidth - config.layoutMargins.totalWidth) return self.measureStackView(config: config, measurementBuilder: measurementBuilder, componentKeys: keys, - maxWidth: contentMaxWidth) + maxWidth: maxStackWidth) } let topFullWidthSubcomponents = subcomponents(forKeys: topFullWidthCVComponentKeys) @@ -720,6 +746,8 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { func applyContentMeasurement(_ size: CGSize) { outerStackViewSize.width += size.width outerStackViewSize.height = max(outerStackViewSize.height, size.height) + + measurementBuilder.setValue(key: contentWidthKey, value: size.width) } if nil != stickerOverlaySubcomponent { @@ -772,7 +800,7 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { componentKeys: bottomNestedCVComponentKeys)) } if let bottomButtons = bottomButtons { - let subviewSize = bottomButtons.measure(maxWidth: maxWidth, + let subviewSize = bottomButtons.measure(maxWidth: contentMaxWidth, measurementBuilder: measurementBuilder) // We store the measured size so that we can pin the @@ -787,12 +815,6 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { subviewSizes: subviewSizes)) } - if hasSenderAvatarLayout { - // Sender avatar in groups. - outerStackViewSize.width += ConversationStyle.groupMessageAvatarDiameter + ConversationStyle.messageStackSpacing - outerStackViewSize.height = max(outerStackViewSize.height, ConversationStyle.groupMessageAvatarDiameter) - } - cellHStackSize.width += outerStackViewSize.width let minBubbleWidth = kOWSMessageCellCornerRadius_Large * 2 cellHStackSize.width = max(cellHStackSize.width, minBubbleWidth) @@ -1140,7 +1162,6 @@ public class CVComponentMessage: CVComponentBase, CVRootComponent { // MARK: - override required init() { - avatarView.autoSetDimensions(to: CGSize(square: ConversationStyle.groupMessageAvatarDiameter)) bubbleView.layoutMargins = .zero diff --git a/Signal/src/ViewControllers/ConversationView/Cells/CVMediaAlbumView.swift b/Signal/src/ViewControllers/ConversationView/Cells/CVMediaAlbumView.swift index 031d5ebc0b..67187cdfb6 100644 --- a/Signal/src/ViewControllers/ConversationView/Cells/CVMediaAlbumView.swift +++ b/Signal/src/ViewControllers/ConversationView/Cells/CVMediaAlbumView.swift @@ -71,6 +71,7 @@ public class CVMediaAlbumView: UIStackView { maxMessageWidth: CGFloat) { if let measuredSize = cellMeasurement.size(key: Self.measurementKey) { + Logger.verbose("---- measuredSize.1: \(measuredSize), aspect: \(measuredSize.aspectRatio)") layoutConstraints.append(self.autoSetDimension(.height, toSize: measuredSize.height)) } else { owsFailDebug("Missing measuredSize.") @@ -78,12 +79,25 @@ public class CVMediaAlbumView: UIStackView { for (index, itemView) in itemViews.enumerated() { if let measuredSize = cellMeasurement.size(key: Self.measurementKey(imageIndex: index)) { - layoutConstraints.append(contentsOf: itemView.autoSetDimensions(to: measuredSize)) + Logger.verbose("---- measuredSize.2: \(measuredSize), aspect: \(measuredSize.aspectRatio)") + itemView.logFrameLater(withLabel: "---- itemView") + // The item heights should always exactly match the layout. + layoutConstraints.append(itemView.autoSetDimension(.height, toSize: measuredSize.height)) + // The media album view's width might be larger than + // expected due to other components in the message. + // + // Therefore item widths might need to adjust and + // should not be required. + NSLayoutConstraint.autoSetPriority(UILayoutPriority.defaultHigh) { + layoutConstraints.append(itemView.autoSetDimension(.width, toSize: measuredSize.width)) + } } else { owsFailDebug("Missing measuredSize for image.") } } + logFrameLater(withLabel: "---- media album view") + switch itemViews.count { case 0: owsFailDebug("No item views.") @@ -279,9 +293,12 @@ public class CVMediaAlbumView: UIStackView { let mediaSize = mediaAlbumItem.mediaSize guard mediaSize.width > 0 && mediaSize.height > 0 else { - owsFailDebug("Invalid mediaSize.") +// owsFailDebug("Invalid mediaSize.") return nil } + Logger.verbose("---- maxWidth: \(maxWidth)") + Logger.verbose("---- mediaSize: \(mediaSize), aspect: \(mediaSize.width / mediaSize.height)") + // Honor the content aspect ratio for single media. var contentAspectRatio = mediaSize.width / mediaSize.height // Clamp the aspect ratio so that very thin/wide content is presented @@ -318,6 +335,9 @@ public class CVMediaAlbumView: UIStackView { } let size = buildSingleMediaSize() ?? CGSize(square: maxWidth) + + Logger.verbose("---- size: \(size), aspect: \(size.aspectRatio)") + measurementBuilder.setSize(key: measurementKey(imageIndex: 0), size: size) measurementBuilder.setSize(key: measurementKey, size: size)