From 0964c16417a89a54683435474b10639a68456eea Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 15 Dec 2017 12:52:14 -0500 Subject: [PATCH] cleanup, plus ill fated attempt to offset content beyond keyboard height. Probably should just pin scrollview above initial toolbar height. I think it's somewhat convoluted because of the scroll view zoom functionality that we already use in this view. // FREEBIE --- .../AttachmentApprovalViewController.swift | 243 +++++------------- 1 file changed, 67 insertions(+), 176 deletions(-) diff --git a/SignalMessaging/attachments/AttachmentApprovalViewController.swift b/SignalMessaging/attachments/AttachmentApprovalViewController.swift index 6bfdd940b..277654377 100644 --- a/SignalMessaging/attachments/AttachmentApprovalViewController.swift +++ b/SignalMessaging/attachments/AttachmentApprovalViewController.swift @@ -173,59 +173,11 @@ public class AttachmentApprovalViewController: OWSViewController, MessagingToolb let messagingToolbar = MessagingToolbar() messagingToolbar.messagingToolbarDelegate = self self.bottomToolbar = messagingToolbar -//// let bottomToolbar: UIToolbar = makeClearToolbar() -// self.bottomToolbar = bottomToolbar -// self.textView = UITextView() -// textView.delegate = self -// self.textView.backgroundColor = UIColor.white -// self.textView.layer.cornerRadius = 4.0 -// -// let textViewItem = UIBarButtonItem(customView: textView) -// // textView.autoresizingMask = [.flexibleWidth, .flexibleHeight]; -// textView.translatesAutoresizingMaskIntoConstraints = false -// -// let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.") -// let sendButton = UIBarButtonItem(title: sendTitle, -// style: .plain, -// target: self, -// action: #selector(sendPressed)) -// sendButton.tintColor = UIColor.white -// -// let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) -// bottomToolbar.items = [textViewItem, sendButton] -//// bottomToolbar.items = [flexibleSpace, sendButton] -// bottomToolbar.setBackgroundImage(UIImage(), forToolbarPosition: .any, barMetrics: .default) -//// bottomToolbar.backgroundColor = UIColor.clear -// bottomToolbar.backgroundColor = UIColor.yellow -// bottomToolbar.autoSetDimension(.height, toSize: 40, relation: .greaterThanOrEqual) -// -// let kToolbarMargin: CGFloat = 4.0 -// // textView.autoSetDimensions(to: CGSize(width: 200, height: 40)) -// textView.autoPinEdge(toSuperviewEdge: .leading, withInset: kToolbarMargin) -// textView.autoPinEdge(toSuperviewEdge: .top, withInset: kToolbarMargin) -// // TODO get actualy offset based on button size. -// let kTrailingOffset: CGFloat = 80 -// textView.autoPinEdge(toSuperviewEdge: .trailing, withInset: kTrailingOffset) -// textView.autoPinEdge(toSuperviewEdge: .bottom, withInset: kToolbarMargin) -// -// self.textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight) - -// self.bottomToolbar = MessagingToolbar() - // Making a toolbar transparent requires setting an empty uiimage - -// self.view.addSubview(bottomToolbar) -// bottomToolbar.autoPin(toBottomLayoutGuideOf: self, withInset: 0) -// bottomToolbar.autoPinWidthToSuperview() -// bottomToolbar.setCompressionResistanceVerticalHigh() -// bottomToolbar.setContentHuggingVerticalHigh() } override public var inputAccessoryView: UIView? { self.bottomToolbar.layoutIfNeeded() return self.bottomToolbar -// let toolbar = UIView(frame: CGRect(origin: CGPoint.zero, size: CGSize(width: 50, height: 100))) -// toolbar.backgroundColor = UIColor.purple -// return toolbar } override public var canBecomeFirstResponder: Bool { @@ -246,29 +198,6 @@ public class AttachmentApprovalViewController: OWSViewController, MessagingToolb return toolbar } -// // MARK: - UITextViewDelegate -// -// public func textViewDidChange(_ textView: UITextView) { -// Logger.debug("\(self.logTag) in \(#function)") -//// CGFloat fixedWidth = textView.frame.size.width; -//// CGSize newSize = [textView sizeThatFits:CGSizeMake(fixedWidth, MAXFLOAT)]; -//// CGRect newFrame = textView.frame; -//// newFrame.size = CGSizeMake(fmaxf(newSize.width, fixedWidth), newSize.height); -//// textView.frame = newFrame; -// let fixedWidth = textView.frame.size.width -// let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)) -//// let newFrame = CGRect(x: textView.frame.origin.x, y: textView.frame.origin.y, width: fixedWidth, height: newSize.height) -//// Logger.debug("\(self.logTag) oldFrame: \(textView.frame), newFrame: \(newFrame)") -// -// Logger.debug("\(self.logTag) oldHeight: \(self.textViewHeightConstraint.constant), newHeight: \(newSize.height)") -// // TODO clamp to a max. -// self.textViewHeightConstraint.constant = max(kMinTextViewHeight, newSize.height) -// self.bottomToolbar.setNeedsLayout() -// self.bottomToolbar.layoutIfNeeded() -//// textView.frame = newFrame -// } -// - (void)textViewDidChange:(UITextView *)textView - // MARK: - Event Handlers @objc @@ -286,6 +215,10 @@ public class AttachmentApprovalViewController: OWSViewController, MessagingToolb self.sendAttachment() } + func messagingToolbar(_ messagingToolbar: MessagingToolbar, didChangeHeight newHeight: CGFloat) { + self.scrollView.contentInset.bottom = newHeight + } + func sendAttachment() { // disable controls after send was tapped. self.bottomToolbar.isUserInteractionEnabled = false @@ -393,25 +326,28 @@ private class GradientView: UIView { protocol MessagingToolbarDelegate: class { func messagingToolbarDidTapSend(_ messagingToolbar: MessagingToolbar) + func messagingToolbar(_ messagingToolbar: MessagingToolbar, didChangeHeight newHeight: CGFloat) } class MessagingToolbar: UIView, UITextViewDelegate { - // let toolbar: UIToolbar - // var sendButton: UIBarButtonItem! - private var sendButton: UIButton! weak var messagingToolbarDelegate: MessagingToolbarDelegate? - + private let sendButton: UIButton private let textView: UITextView - private let kToolbarMargin: CGFloat = 4 - private let kTextViewPadding: CGFloat = 4.0 - private var textViewHeightConstraint: NSLayoutConstraint! - // private(set) var heightConstraint: NSLayoutConstraint! + // Layout Constants + var maxTextViewHeight: CGFloat { + // About ~4 lines in portrait and ~3 lines in landscape. + // Otherwise we risk obscuring too much of the content. + return UIDevice.current.orientation.isPortrait ? 160 : 100 + } + let kMinTextViewHeight: CGFloat = 38 - private var kMinTextViewHeight: CGFloat { - // return UIFont.ows_dynamicTypeBody().lineHeight - return 38 + var textViewHeight: CGFloat { + didSet { + // TODO magic numbers + self.messagingToolbarDelegate?.messagingToolbar(self, didChangeHeight: textViewHeight + 2 * 4) + } } required init?(coder aDecoder: NSCoder) { @@ -429,77 +365,35 @@ class MessagingToolbar: UIView, UITextViewDelegate { } init() { - let textView = MessageTextView() - self.textView = textView + self.textView = MessageTextView() + self.sendButton = UIButton(type: .system) + self.textViewHeight = kMinTextViewHeight super.init(frame: CGRect.zero) - let kSendButtonWidth: CGFloat = 100 - let kMinToolbarHeight: CGFloat = 40 - self.backgroundColor = UIColor.ows_inputToolbarBackground() + textView.delegate = self textView.backgroundColor = UIColor.white textView.layer.cornerRadius = 4.0 textView.addBorder(with: UIColor.lightGray) textView.font = UIFont.ows_dynamicTypeBody() - let textViewItem = UIBarButtonItem(customView: textView) let sendTitle = NSLocalizedString("ATTACHMENT_APPROVAL_SEND_BUTTON", comment: "Label for 'send' button in the 'attachment approval' dialog.") - - let sendButton = UIButton(type: .system) sendButton.setTitle(sendTitle, for: .normal) sendButton.addTarget(self, action: #selector(didTapSend), for: .touchUpInside) - // let sendButton = UIBarButtonItem(title: sendTitle, - // style: .plain, - // target: self, - // action: #selector(sendPressed)) - // sendButton.width = kSendButtonWidth - self.sendButton = sendButton - // TODO - // self.sendButton.titleLabel.font = [UIFont ows_mediumFontWithSize:16.f]; - // center text alignment sendButton.titleLabel?.font = UIFont.ows_mediumFont(withSize: 16) sendButton.titleLabel?.textAlignment = .center sendButton.tintColor = UIColor.ows_materialBlue() + // Increase hit area of send button sendButton.contentEdgeInsets = UIEdgeInsets(top: 20, left: 8, bottom: 4, right: 8) addSubview(sendButton) addSubview(textView) -// -// let sendButtonItem = UIBarButtonItem(customView: sendButton) -// -// // self.items = [textViewItem, sendButton] -// self.items = [textViewItem, sendButtonItem] -// -// // toolbar doesn't render without some minimum height set. -// // self.heightConstraint = self.autoSetDimension(.height, -// self.autoSetDimension(.height, -// toSize: kMinTextViewHeight + kToolbarMargin * 2, -// relation: .greaterThanOrEqual) -// - self.autoMatch(.height, to: .height, of: textView, withMultiplier:1, relation: .greaterThanOrEqual) - - // Adding textView to a toolbar item inserts it into a "hostView" - // This isn't really documentd, but I've verified it works on iOS9 and iOS10 - self.textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight) - textView.autoPinEdge(toSuperviewEdge: .leading, withInset: kToolbarMargin) - textView.autoPinEdge(toSuperviewEdge: .top, withInset: kToolbarMargin) - textView.autoPinEdge(toSuperviewEdge: .bottom, withInset: kToolbarMargin) -// -// // let kTrailingOffset: CGFloat = kSendButtonWidth -// // textView.autoPinEdge(toSuperviewEdge: .trailing, withInset: kTrailingOffset) -// - textView.autoPinEdge(.trailing, to: .leading, of: sendButton, withOffset: -8) - sendButton.sizeToFit() - sendButton.autoPinEdge(toSuperviewEdge: .trailing, withInset: kToolbarMargin) - sendButton.autoPinEdge(toSuperviewEdge: .bottom, withInset: kToolbarMargin) - - textView.delegate = self } func didTapSend() { @@ -511,60 +405,57 @@ class MessagingToolbar: UIView, UITextViewDelegate { public func textViewDidChange(_ textView: UITextView) { Logger.debug("\(self.logTag) in \(#function)") - // We don't want the textView to grow indefinitely - let kMaxTextViewHeight: CGFloat = 160 - - let fixedWidth = textView.frame.size.width - let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)) - let newHeight = Clamp(newSize.height, kMinTextViewHeight, kMaxTextViewHeight) + // compute new height assuming width is unchanged + let currentSize = textView.frame.size + let newHeight = clampedTextViewHeight(fixedWidth: currentSize.width) - if newHeight != self.textViewHeightConstraint.constant { - Logger.debug("\(self.logTag) oldHeight: \(self.textViewHeightConstraint.constant), newHeight: \(newHeight)") - self.textViewHeightConstraint.constant = newHeight - self.textView.frame = CGRect(x: 0, y: 0, width: self.textView.frame.size.width, height: newHeight) - // UIView.animate(withDuration: 0.1) { + if newHeight != self.textViewHeight { + Logger.debug("\(self.logTag) TextView height changed: \(self.textViewHeight) -> \(newHeight)") + self.textViewHeight = newHeight self.setNeedsLayout() self.layoutIfNeeded() - self.textView.reloadInputViews() - } else { - Logger.debug("\(self.logTag) height unchanged: \(self.textViewHeightConstraint.constant)") } } + private func clampedTextViewHeight(fixedWidth: CGFloat) -> CGFloat { + let contentSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude)) + return Clamp(contentSize.height, kMinTextViewHeight, maxTextViewHeight) + } + + // We do progammatic layout, explicitly computing and setting frames since autoLayout does + // not seem to work with inputAccessory views, even when forcing a layout. override func layoutSubviews() { super.layoutSubviews() - Logger.info("\(self.logTag) in \(#function)") - Logger.info("textView: \(self.textView.frame), sendButton:\(sendButton.frame)") - - // Updating the autoLayout constraints was not sufficient to properly set the frame of the inputAccessoryView, - // so we manually update the relevant frames here. - let originalTextViewFrame = self.textView.frame - let newTextViewFrame = CGRect(x: originalTextViewFrame.origin.x, y: originalTextViewFrame.origin.y, width: originalTextViewFrame.width, height: self.textViewHeightConstraint.constant) - self.textView.frame = newTextViewFrame - - let diffY = newTextViewFrame.height - originalTextViewFrame.height - let originalFrame = self.frame - let newFrame = CGRect(x: originalFrame.origin.x, y: originalFrame.origin.y - diffY, width: originalFrame.width, height: originalFrame.size.height + diffY) - self.frame = newFrame - - // - // let kMargin = 4 - // let kTextViewHeight = 40 - // let kTextViewWidth = 200 - // - // let kSendButtonHeight = 40 - // let kSendButtonWidth = 100 - // - // self.textView.frame = CGRect(x: kMargin, y: kMargin, width: kTextViewWidth, height: kTextViewHeight) - // self.sendButton.frame = CGRect(x: kMargin * 2 + kTextViewWidth, y: kMargin, width: kSendButtonWidth, height: kSendButtonHeight) - // self.frame = CGRect(x: 0, y: 0, width: 320, height: kTextViewHeight + 2 * kMargin) - // self.bounds = self.frame - // - //// self.textView.sizeToFit() - // - //// let maxHeight = max(self.sendButton.frame.size.height, self.textView.frame.size.height) - //// let fittedFrame = CGRect(x: frame.origin.x, y: frame.origin.y, width: frame.size.width, height: maxHeight) - //// self.frame = fittedFrame - //// self.bounds = fittedFrame + Logger.debug("\(self.logTag) in \(#function)") + Logger.debug("Before layout >>> self: \(self.frame) textView: \(self.textView.frame), sendButton:\(sendButton.frame)") + + let kToolbarMargin: CGFloat = 4 + + let sendButtonWidth = sendButton.frame.size.width + + let kOriginalToolbarHeight = kMinTextViewHeight + 2 * kToolbarMargin + // Assume send button has proper size. + let textViewWidth = frame.size.width - 3 * kToolbarMargin - sendButtonWidth + + // determine height given a fixed width + let textViewHeight = clampedTextViewHeight(fixedWidth: textViewWidth) + textView.frame = CGRect(x: kToolbarMargin, y: kToolbarMargin, width: textViewWidth, height: textViewHeight) + assert(self.textViewHeight == textViewHeight, "textView.height inconsistent with what was computed in textViewDidChange") + + let newToolbarHeight = textViewHeight + 2 * kToolbarMargin + + // frame origin is with respect to the initial height of the toolbar, so we must offset the toolbar frame + // by the difference, else the toolbar will extend into and behind the keyboard. + let toolbarHeightOffset = kOriginalToolbarHeight - newToolbarHeight + self.frame = CGRect(x: 0, y: toolbarHeightOffset, width: frame.size.width, height: newToolbarHeight) + + // Send Button + + // position in bottom right corner + let sendButtonX = frame.size.width - kToolbarMargin - sendButton.frame.size.width + let sendButtonY = frame.size.height - kToolbarMargin - sendButton.frame.size.height + sendButton.frame = CGRect(origin: CGPoint(x: sendButtonX, y: sendButtonY), size: sendButton.frame.size) + + Logger.debug("After layout >>> self: \(self.frame) textView: \(self.textView.frame), sendButton:\(sendButton.frame)") } }