diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 440621428..92abcd6e0 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -36,6 +36,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { var messageTextTopConstraint: NSLayoutConstraint? var messageTextHeightLayoutConstraint: NSLayoutConstraint? var messageTextProxyViewHeightConstraint: NSLayoutConstraint? + var bubbleViewWidthConstraint: NSLayoutConstraint? var scrollView: UIScrollView? var contentView: UIView? @@ -76,16 +77,6 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { self.view.layoutIfNeeded() - if mode == .focusOnMetadata { - if let bubbleView = self.bubbleView { - let showAtLeast: CGFloat = 50 - let middleCenter = CGPoint(x: bubbleView.frame.origin.x + bubbleView.frame.width / 2, - y: bubbleView.frame.origin.y + bubbleView.frame.height - showAtLeast) - let offset = bubbleView.superview!.convert(middleCenter, to: scrollView) - self.scrollView!.setContentOffset(offset, animated: false) - } - } - NotificationCenter.default.addObserver(self, selector: #selector(yapDatabaseModified), name: NSNotification.Name.YapDatabaseModified, @@ -98,6 +89,20 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { mediaMessageView?.viewWillAppear(animated) updateTextLayout() + + if mode == .focusOnMetadata { + if let bubbleView = self.bubbleView { + // Force layout. + view.setNeedsLayout() + view.layoutIfNeeded() + + let showAtLeast: CGFloat = 50 + let middleCenter = CGPoint(x: bubbleView.frame.origin.x + bubbleView.frame.width / 2, + y: bubbleView.frame.origin.y + bubbleView.frame.height - showAtLeast) + let offset = bubbleView.superview!.convert(middleCenter, to: scrollView) + self.scrollView!.setContentOffset(offset, animated: false) + } + } } override func viewWillDisappear(_ animated: Bool) { @@ -302,6 +307,10 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { return messageBody } + let bubbleViewHMargin: CGFloat = 10 + let messageTailEdgeMargin: CGFloat = 15 + let messageNoTailEdgeMargin: CGFloat = 10 + private func contentRows() -> [UIView] { var rows = [UIView]() @@ -343,25 +352,22 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { self.messageTextTopConstraint = messageTextView.autoPinEdge(toSuperviewEdge: .top, withInset: 0) self.messageTextHeightLayoutConstraint = messageTextView.autoSetDimension(.height, toSize:0) - let leadingMargin: CGFloat = isIncoming ? 15 : 10 - let trailingMargin: CGFloat = isIncoming ? 10 : 15 - let bubbleView = UIImageView(image: bubbleImageData.messageBubbleImage) self.bubbleView = bubbleView bubbleView.layer.cornerRadius = 10 bubbleView.addSubview(messageTextProxyView) - messageTextProxyView.autoPinEdge(toSuperviewEdge: .leading, withInset: leadingMargin) - messageTextProxyView.autoPinEdge(toSuperviewEdge: .trailing, withInset: trailingMargin) + messageTextProxyView.autoPinEdge(toSuperviewEdge: isIncoming ? .leading : .trailing, withInset: messageTailEdgeMargin) + messageTextProxyView.autoPinEdge(toSuperviewEdge: isIncoming ? .trailing : .leading, withInset: messageNoTailEdgeMargin) messageTextProxyView.autoPinHeightToSuperview(withMargin: 10) self.messageTextProxyViewHeightConstraint = messageTextProxyView.autoSetDimension(.height, toSize:0) let row = UIView() row.addSubview(bubbleView) bubbleView.autoPinHeightToSuperview() - bubbleView.autoPinLeadingToSuperview(withMargin: 10) - bubbleView.autoPinTrailingToSuperview(withMargin: 10) + bubbleView.autoPinEdge(toSuperviewEdge: isIncoming ? .leading : .trailing, withInset: bubbleViewHMargin) + self.bubbleViewWidthConstraint = bubbleView.autoSetDimension(.width, toSize:0) rows.append(row) } else if message.attachmentIds.count > 0 { rows += addAttachmentRows() @@ -617,24 +623,36 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { owsFail("\(TAG) Missing messageTextProxyView") return } + guard let scrollView = scrollView else { + owsFail("\(TAG) Missing scrollView") + return + } + guard let contentView = contentView else { + owsFail("\(TAG) Missing contentView") + return + } + guard let bubbleView = bubbleView else { + owsFail("\(TAG) Missing bubbleView") + return + } + guard let bubbleSuperview = bubbleView.superview else { + owsFail("\(TAG) Missing bubbleSuperview") + return + } guard let messageTextTopConstraint = messageTextTopConstraint else { - owsFail("\(TAG) Missing messageTextProxyView") + owsFail("\(TAG) Missing messageTextTopConstraint") return } guard let messageTextHeightLayoutConstraint = messageTextHeightLayoutConstraint else { - owsFail("\(TAG) Missing messageTextProxyView") + owsFail("\(TAG) Missing messageTextHeightLayoutConstraint") return } guard let messageTextProxyViewHeightConstraint = messageTextProxyViewHeightConstraint else { - owsFail("\(TAG) Missing messageTextProxyView") + owsFail("\(TAG) Missing messageTextProxyViewHeightConstraint") return } - guard let scrollView = scrollView else { - owsFail("\(TAG) Missing scrollView") - return - } - guard let contentView = contentView else { - owsFail("\(TAG) Missing contentView") + guard let bubbleViewWidthConstraint = bubbleViewWidthConstraint else { + owsFail("\(TAG) Missing bubbleViewWidthConstraint") return } @@ -642,8 +660,13 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { owsFail("\(TAG) messageTextView.width \(messageTextView.width) != messageTextProxyView.width \(messageTextProxyView.width)") } + let bubbleViewHMargin: CGFloat = 10 + let messageTailEdgeMargin: CGFloat = 15 + let messageNoTailEdgeMargin: CGFloat = 10 + let maxBubbleWidth = bubbleSuperview.width() - (bubbleViewHMargin * 2) + let maxTextWidth = maxBubbleWidth - (messageTailEdgeMargin + messageNoTailEdgeMargin) // Measure the total text size. - let textSize = messageTextView.sizeThatFits(CGSize(width:messageTextView.width(), height:CGFloat.greatestFiniteMagnitude)) + let textSize = messageTextView.sizeThatFits(CGSize(width:maxTextWidth, height:CGFloat.greatestFiniteMagnitude)) // Measure the size of the scroll view viewport. let scrollViewSize = scrollView.frame.size // Obtain the current scroll view content offset (scroll state). @@ -651,18 +674,21 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { // Obtain the location of the text view proxy relative to the content view. let textProxyOffset = contentView.convert(CGPoint.zero, from:messageTextProxyView) - // 1. The text proxy should always be sized large enough to hold the - // entire text content. - let messageTextProxyViewHeight = textSize.height + // 1. The bubble view's width should fit the text content. + let bubbleViewWidth = ceil(textSize.width + messageTailEdgeMargin + messageNoTailEdgeMargin) + bubbleViewWidthConstraint.constant = bubbleViewWidth + + // 2. The text proxy's height should reflect the entire text content. + let messageTextProxyViewHeight = ceil(textSize.height) messageTextProxyViewHeightConstraint.constant = messageTextProxyViewHeight - // 2. We only want to render a single screenful of text content at a time. + // 3. We only want to render a single screenful of text content at a time. // The height of the text view should reflect the height of the scrollview's // viewport. - let messageTextViewHeight = min(textSize.height, scrollViewSize.height) + let messageTextViewHeight = ceil(min(textSize.height, scrollViewSize.height)) messageTextHeightLayoutConstraint.constant = messageTextViewHeight - // 3. We want to move the text view around within the proxy in response to + // 4. We want to move the text view around within the proxy in response to // scroll state changes so that it can render the part of the proxy which // is on screen. let minMessageTextViewY = CGFloat(0) @@ -671,7 +697,7 @@ class MessageDetailViewController: OWSViewController, UIScrollViewDelegate { let messageTextViewY = max(minMessageTextViewY, min(maxMessageTextViewY, rawMessageTextViewY)) messageTextTopConstraint.constant = messageTextViewY - // 4. We want to scroll the text view's content so that the text view + // 5. We want to scroll the text view's content so that the text view // renders the appropriate content for the scrollview's scroll state. messageTextView.contentOffset = CGPoint(x:0, y:messageTextViewY) }