| 
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -10,7 +10,7 @@ enum MessageMetadataViewMode: UInt {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    case focusOnMetadata
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				class MessageDetailViewController: OWSViewController, UIScrollViewDelegate {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    static let TAG = "[MessageDetailViewController]"
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    let TAG = "[MessageDetailViewController]"
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -30,6 +30,13 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var mediaMessageView: MediaMessageView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // See comments on updateTextLayout.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var messageTextView: UITextView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var messageTextProxyView: UIView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var messageTextTopConstraint: NSLayoutConstraint?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var messageTextHeightLayoutConstraint: NSLayoutConstraint?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var messageTextProxyViewHeightConstraint: NSLayoutConstraint?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var scrollView: UIScrollView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    var contentView: UIView?
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -89,6 +96,8 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        super.viewWillAppear(animated)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        mediaMessageView?.viewWillAppear(animated)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        updateTextLayout()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    override func viewWillDisappear(_ animated: Bool) {
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -103,6 +112,7 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        view.backgroundColor = UIColor.white
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let scrollView = UIScrollView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        scrollView.delegate = self
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        self.scrollView = scrollView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        view.addSubview(scrollView)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        scrollView.autoPinWidthToSuperview(withMargin: 0)
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -305,23 +315,34 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // on the size of its backing buffer, especially when we're 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // embedding it "full-size' within a UIScrollView as we do in this view.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // TODO: We could use CoreText instead, or we could dynamically
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            //       manipulate the size/position of our UITextView to
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            //       reflect scroll state.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let bodyLabel = UITextView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.font = UIFont.ows_dynamicTypeBody()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.backgroundColor = UIColor.clear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.isOpaque = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.isEditable = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.isSelectable = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.textContainerInset = UIEdgeInsets.zero
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.contentInset = UIEdgeInsets.zero
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.isScrollEnabled = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.textColor = isIncoming ? UIColor.black : UIColor.white
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.text = messageBody
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // Therefore we're doing something unusual here.  
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            // See comments on updateTextLayout.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let messageTextView = UITextView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.messageTextView = messageTextView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.font = UIFont.ows_dynamicTypeBody()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.backgroundColor = UIColor.clear
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.isOpaque = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.isEditable = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.isSelectable = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.textContainerInset = UIEdgeInsets.zero
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.contentInset = UIEdgeInsets.zero
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.isScrollEnabled = true
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.showsHorizontalScrollIndicator = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.showsVerticalScrollIndicator = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.isUserInteractionEnabled = false
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.textColor = isIncoming ? UIColor.black : UIColor.white
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.text = messageBody
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let bubbleImageData = isIncoming ? bubbleFactory.incoming : bubbleFactory.outgoing
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let messageTextProxyView = UIView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextProxyView.layoutMargins = UIEdgeInsets.zero
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.messageTextProxyView = messageTextProxyView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextProxyView.addSubview(messageTextView)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextView.autoPinWidthToSuperview()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            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
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
							
								
							
						
					 | 
				
			
			 | 
			 | 
			
				@ -329,11 +350,12 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.bubbleView = bubbleView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bubbleView.layer.cornerRadius = 10
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bubbleView.addSubview(bodyLabel)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bubbleView.addSubview(messageTextProxyView)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.autoPinEdge(toSuperviewEdge: .leading, withInset: leadingMargin)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.autoPinEdge(toSuperviewEdge: .trailing, withInset: trailingMargin)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            bodyLabel.autoPinHeightToSuperview(withMargin: 10)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextProxyView.autoPinEdge(toSuperviewEdge: .leading, withInset: leadingMargin)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextProxyView.autoPinEdge(toSuperviewEdge: .trailing, withInset: trailingMargin)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            messageTextProxyView.autoPinHeightToSuperview(withMargin: 10)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            self.messageTextProxyViewHeightConstraint = messageTextProxyView.autoSetDimension(.height, toSize:0)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            let row = UIView()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            row.addSubview(bubbleView)
 | 
			
		
		
	
	
		
			
				
					| 
						
							
								
							
						
						
							
								
							
						
						
					 | 
				
			
			 | 
			 | 
			
				@ -567,4 +589,93 @@ class MessageDetailViewController: OWSViewController {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				                              comment: "Status label for messages which are failed.")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // MARK: - Text Layout
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // UITextView can't render extremely long text due to constraints on the size
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // of its backing buffer, especially when we're embedding it "full-size' 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // within a UIScrollView as we do in this view.  Therefore if we do the naive
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // thing and embed a full-size UITextView inside our UIScrollView, it will 
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // fail to render any text if the text message is sufficiently long.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // Therefore we're doing something unusual.  
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // * We use an empty UIView "messageTextProxyView" as a placeholder for the
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //   the UITextView.  It has the size and position of where the UITextView
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //   would be normally.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    // * We use a UITextView inside that proxy that is just large enough to
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //   render the content onscreen. We then move it around within the proxy
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    //   bounds to render the parts of the proxy which are onscreen.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    private func updateTextLayout() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let messageTextView = messageTextView else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let messageTextProxyView = messageTextProxyView else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFail("\(TAG) Missing messageTextProxyView")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let messageTextTopConstraint = messageTextTopConstraint else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFail("\(TAG) Missing messageTextProxyView")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let messageTextHeightLayoutConstraint = messageTextHeightLayoutConstraint else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFail("\(TAG) Missing messageTextProxyView")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            return
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        guard let messageTextProxyViewHeightConstraint = messageTextProxyViewHeightConstraint else {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            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
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        if messageTextView.width() != messageTextProxyView.width() {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				            owsFail("\(TAG) messageTextView.width \(messageTextView.width) != messageTextProxyView.width \(messageTextProxyView.width)")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // Measure the total text size.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let textSize = messageTextView.sizeThatFits(CGSize(width:messageTextView.width(), 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).
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let scrollViewContentOffset = scrollView.contentOffset
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 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
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        messageTextProxyViewHeightConstraint.constant = messageTextProxyViewHeight
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 2. 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)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        messageTextHeightLayoutConstraint.constant = messageTextViewHeight
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        // 3. 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)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let maxMessageTextViewY = messageTextProxyViewHeight - messageTextViewHeight
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        let rawMessageTextViewY = -textProxyOffset.y + scrollViewContentOffset.y
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        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
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        //    renders the appropriate content for the scrollview's scroll state.
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        messageTextView.contentOffset = CGPoint(x:0, y:messageTextViewY)
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    public func scrollViewDidScroll(_ scrollView: UIScrollView) {
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        Logger.verbose("\(TAG) scrollViewDidScroll")
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				        updateTextLayout()
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				    }
 | 
			
		
		
	
		
			
				 | 
				 | 
			
			 | 
			 | 
			
				}
 | 
			
		
		
	
	
		
			
				
					| 
						
						
						
					 | 
				
			
			 | 
			 | 
			
				
 
 |