From 8b5d1d9e6973aef8499befba6b2c16b71a930313 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 12:34:57 -0600 Subject: [PATCH 01/22] Only add delete button once --- .../MediaPageViewController.swift | 2 +- .../AttachmentApprovalViewController.swift | 44 ++++++++++--------- SignalMessaging/Views/GalleryRailView.swift | 7 +-- 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Signal/src/ViewControllers/MediaPageViewController.swift b/Signal/src/ViewControllers/MediaPageViewController.swift index 02ac04b05..e7c0d7244 100644 --- a/Signal/src/ViewControllers/MediaPageViewController.swift +++ b/Signal/src/ViewControllers/MediaPageViewController.swift @@ -331,7 +331,7 @@ class MediaPageViewController: UIPageViewController, UIPageViewControllerDataSou return } - galleryRailView.configureCellViews(itemProvider: currentItem.album, focusedItem: currentItem) + galleryRailView.configureCellViews(itemProvider: currentItem.album, focusedItem: currentItem, cellViewDecoratorBlock: { _ in }) } // MARK: Actions diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index daf4bba9b..094e6e476 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -263,29 +263,27 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC }) } - func addDeleteIcon(cellViews: [GalleryRailCellView]) { - for cellView in cellViews { - guard let attachmentItem = cellView.item as? SignalAttachmentItem else { - owsFailDebug("attachmentItem was unexpectedly nil") - return - } + func addDeleteIcon(cellView: GalleryRailCellView) { + guard let attachmentItem = cellView.item as? SignalAttachmentItem else { + owsFailDebug("attachmentItem was unexpectedly nil") + return + } - let button = OWSButton { [weak self] in - guard let strongSelf = self else { return } - strongSelf.remove(attachmentItem: attachmentItem) - } - button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal) + let button = OWSButton { [weak self] in + guard let strongSelf = self else { return } + strongSelf.remove(attachmentItem: attachmentItem) + } + button.setImage(#imageLiteral(resourceName: "ic_small_x"), for: .normal) - let kInsetDistance: CGFloat = 5 - button.imageEdgeInsets = UIEdgeInsets(top: kInsetDistance, left: kInsetDistance, bottom: kInsetDistance, right: kInsetDistance) + let kInsetDistance: CGFloat = 5 + button.imageEdgeInsets = UIEdgeInsets(top: kInsetDistance, left: kInsetDistance, bottom: kInsetDistance, right: kInsetDistance) - cellView.addSubview(button) + cellView.addSubview(button) - let kButtonWidth: CGFloat = 9 + kInsetDistance * 2 - button.autoSetDimensions(to: CGSize(width: kButtonWidth, height: kButtonWidth)) - button.autoPinEdge(toSuperviewMargin: .top) - button.autoPinEdge(toSuperviewMargin: .trailing) - } + let kButtonWidth: CGFloat = 9 + kInsetDistance * 2 + button.autoSetDimensions(to: CGSize(width: kButtonWidth, height: kButtonWidth)) + button.autoPinEdge(toSuperviewMargin: .top) + button.autoPinEdge(toSuperviewMargin: .trailing) } var pagerScrollView: UIScrollView? @@ -428,8 +426,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return } - galleryRailView.configureCellViews(itemProvider: attachmentItemCollection, focusedItem: currentItem) - addDeleteIcon(cellViews: galleryRailView.cellViews) + let cellViewDecoratorBlock = { (cellView: GalleryRailCellView) in + self.addDeleteIcon(cellView: cellView) + } + galleryRailView.configureCellViews(itemProvider: attachmentItemCollection, + focusedItem: currentItem, + cellViewDecoratorBlock: cellViewDecoratorBlock) galleryRailView.isHidden = attachmentItemCollection.attachmentItems.count < 2 } diff --git a/SignalMessaging/Views/GalleryRailView.swift b/SignalMessaging/Views/GalleryRailView.swift index 42c72c854..5274f4aff 100644 --- a/SignalMessaging/Views/GalleryRailView.swift +++ b/SignalMessaging/Views/GalleryRailView.swift @@ -118,7 +118,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { // MARK: Public - public func configureCellViews(itemProvider: GalleryRailItemProvider?, focusedItem: GalleryRailItem?) { + public func configureCellViews(itemProvider: GalleryRailItemProvider?, focusedItem: GalleryRailItem?, cellViewDecoratorBlock: (GalleryRailCellView) -> Void) { let animationDuration: TimeInterval = 0.2 guard let itemProvider = itemProvider else { @@ -169,7 +169,7 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { self.isHidden = false } - let cellViews = buildCellViews(items: itemProvider.railItems) + let cellViews = buildCellViews(items: itemProvider.railItems, cellViewDecoratorBlock: cellViewDecoratorBlock) self.cellViews = cellViews let stackView = UIStackView(arrangedSubviews: cellViews) stackView.axis = .horizontal @@ -203,10 +203,11 @@ public class GalleryRailView: UIView, GalleryRailCellViewDelegate { return scrollView }() - private func buildCellViews(items: [GalleryRailItem]) -> [GalleryRailCellView] { + private func buildCellViews(items: [GalleryRailItem], cellViewDecoratorBlock: (GalleryRailCellView) -> Void) -> [GalleryRailCellView] { return items.map { item in let cellView = GalleryRailCellView() cellView.configure(item: item, delegate: self) + cellViewDecoratorBlock(cellView) return cellView } } From eed25580504b1dd6a8421ef4b5aca5d900523cb1 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 13:44:41 -0600 Subject: [PATCH 02/22] Avoid glitch in keyboard dismiss. iOS adjusts the inputAccessoryView's host input views layout margins when popping/dismissing the keyboard, which causes a noticeable glitch. --- .../ViewControllers/AttachmentApprovalViewController.swift | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 094e6e476..aeafd4f67 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -989,10 +989,7 @@ class BottomToolView: UIView { stackView.axis = .vertical addSubview(stackView) - stackView.autoPinEdge(toSuperviewEdge: .leading) - stackView.autoPinEdge(toSuperviewEdge: .trailing) - stackView.autoPinEdge(toSuperviewEdge: .top) - stackView.autoPinEdge(toSuperviewMargin: .bottom) + stackView.autoPinEdgesToSuperviewEdges() } required init?(coder aDecoder: NSCoder) { From 280664c763c4783a1c61e5dd225860e5c7373c5b Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 14:19:57 -0600 Subject: [PATCH 03/22] WIP: keyboard --- .../AttachmentApprovalViewController.swift | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index aeafd4f67..32541bfb7 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -718,9 +718,51 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // 1. when no keyboard is popped (e.g. initially) to be *just* above the rail // 2. when the CaptionTextView is first responder, to be *just* above the keyboard // 3. when the MessageTextView is first responder, to be behind the keyboard - captionView.autoPinEdge(toSuperviewMargin: .bottom, withInset: 136) + captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewMargin: .bottom, withInset: kDefaultCaptionViewBottomInset) + + NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil) + } + + @objc + func keyboardWillChangeFrame(notification: Notification) { + Logger.debug("") + + // NSDictionary *userInfo = [notification userInfo]; + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } + +// NSValue *_Nullable keyboardBeginFrameValue = userInfo[UIKeyboardFrameBeginUserInfoKey]; +// if (!keyboardBeginFrameValue) { +// OWSFailDebug(@"Missing keyboard begin frame"); +// return; +// } +// +// NSValue *_Nullable keyboardEndFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey]; +// if (!keyboardEndFrameValue) { +// OWSFailDebug(@"Missing keyboard end frame"); +// return; +// } +// +// CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue]; + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + Logger.debug("keyboardEndFrame: \(keyboardEndFrame)") + let totalInset = kDefaultCaptionViewBottomInset + keyboardEndFrame.size.height + captionViewBottomConstraint.constant = -totalInset + + captionView.superview?.layoutIfNeeded() +// +// UIEdgeInsets oldInsets = self.collectionView.contentInset; +// UIEdgeInsets newInsets = oldInsets; } + let kDefaultCaptionViewBottomInset: CGFloat = 0 var captionViewBottomConstraint: NSLayoutConstraint! override public func viewWillLayoutSubviews() { From 706dd3d0c149e6a4e12930743bcae5a9aa4bec29 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 14:56:13 -0600 Subject: [PATCH 04/22] initial layout of keyboard is correct across pages --- .../AttachmentApprovalViewController.swift | 93 +++++++++++-------- 1 file changed, 52 insertions(+), 41 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 32541bfb7..d3967042e 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -197,6 +197,11 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } self.setCurrentItem(firstItem, direction: .forward, animated: false) + + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillChangeFrame(notification:)), + name: .UIKeyboardWillChangeFrame, + object: nil) } override public func viewWillAppear(_ animated: Bool) { @@ -230,6 +235,35 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return true } + var lastKnownBottomToolbarInset: CGFloat = 0 + + @objc + func keyboardWillChangeFrame(notification: Notification) { + Logger.debug("") + + // NSDictionary *userInfo = [notification userInfo]; + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + lastKnownBottomToolbarInset = keyboardEndFrame.size.height + + viewControllers?.forEach { viewController in + guard let prepViewController = viewController as? AttachmentPrepViewController else { + owsFailDebug("unexpected prepViewController: \(viewController)") + return + } + + prepViewController.update(bottomToolbarInset: lastKnownBottomToolbarInset) + } + } + // MARK: - View Helpers func remove(attachmentItem: SignalAttachmentItem) { @@ -516,6 +550,10 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) { self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment) } + + var bottomToolbarInset: CGFloat { + return lastKnownBottomToolbarInset + } } // MARK: GalleryRail @@ -563,6 +601,8 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate { protocol AttachmentPrepViewControllerDelegate: class { func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) + + var bottomToolbarInset: CGFloat { get } } public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarDelegate, OWSVideoPlayerDelegate { @@ -718,53 +758,16 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // 1. when no keyboard is popped (e.g. initially) to be *just* above the rail // 2. when the CaptionTextView is first responder, to be *just* above the keyboard // 3. when the MessageTextView is first responder, to be behind the keyboard - captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewMargin: .bottom, withInset: kDefaultCaptionViewBottomInset) - NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillChangeFrame(notification:)), name: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil) - } - - @objc - func keyboardWillChangeFrame(notification: Notification) { - Logger.debug("") - - // NSDictionary *userInfo = [notification userInfo]; - guard let userInfo = notification.userInfo else { - owsFailDebug("userInfo was unexpectedly nil") + guard let prepDelegate = self.prepDelegate else { + owsFailDebug("prepDelegate was unexpectedly nil") return } -// NSValue *_Nullable keyboardBeginFrameValue = userInfo[UIKeyboardFrameBeginUserInfoKey]; -// if (!keyboardBeginFrameValue) { -// OWSFailDebug(@"Missing keyboard begin frame"); -// return; -// } -// -// NSValue *_Nullable keyboardEndFrameValue = userInfo[UIKeyboardFrameEndUserInfoKey]; -// if (!keyboardEndFrameValue) { -// OWSFailDebug(@"Missing keyboard end frame"); -// return; -// } -// -// CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue]; - - guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - Logger.debug("keyboardEndFrame: \(keyboardEndFrame)") - let totalInset = kDefaultCaptionViewBottomInset + keyboardEndFrame.size.height - captionViewBottomConstraint.constant = -totalInset - - captionView.superview?.layoutIfNeeded() -// -// UIEdgeInsets oldInsets = self.collectionView.contentInset; -// UIEdgeInsets newInsets = oldInsets; + let bottomToolbarInset = prepDelegate.bottomToolbarInset + captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewMargin: .bottom, withInset: bottomToolbarInset) } - let kDefaultCaptionViewBottomInset: CGFloat = 0 - var captionViewBottomConstraint: NSLayoutConstraint! - override public func viewWillLayoutSubviews() { Logger.debug("") super.viewWillLayoutSubviews() @@ -775,6 +778,14 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD ensureAttachmentViewScale(animated: false) } + // MARK: CaptionView lifts with keyboard + + var captionViewBottomConstraint: NSLayoutConstraint! + func update(bottomToolbarInset: CGFloat) { + captionViewBottomConstraint.constant = -bottomToolbarInset + captionView.superview?.layoutIfNeeded() + } + // MARK: - @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { From b98b3d1fdd701ddf61f4fad406e15071b49b5ec2 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 15:24:29 -0600 Subject: [PATCH 05/22] WIP: require dismiss before swipe --- .../AttachmentApprovalViewController.swift | 62 +++++++++++++++++-- 1 file changed, 58 insertions(+), 4 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index d3967042e..dd7e57844 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -551,6 +551,14 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment) } +// func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { +// self.touchInterceptorView.isHidden = false +// } +// +// func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) { +// self.touchInterceptorView.isHidden = true +// } + var bottomToolbarInset: CGFloat { return lastKnownBottomToolbarInset } @@ -602,6 +610,9 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate { protocol AttachmentPrepViewControllerDelegate: class { func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) +// func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) +// func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) + var bottomToolbarInset: CGFloat { get } } @@ -646,6 +657,15 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD return CaptionView(attachmentItem: attachmentItem) }() + lazy var touchInterceptorView: UIView = { + let touchInterceptorView = UIView() + touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) + touchInterceptorView.addGestureRecognizer(tapGesture) + + return touchInterceptorView + }() + override public func loadView() { self.view = UIView() @@ -749,6 +769,10 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // Caption + view.addSubview(touchInterceptorView) + touchInterceptorView.autoPinEdgesToSuperviewEdges() + touchInterceptorView.isHidden = true + view.addSubview(captionView) captionView.delegate = self @@ -786,15 +810,21 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD captionView.superview?.layoutIfNeeded() } - // MARK: - + // MARK: - Event Handlers - @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { + @objc + func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { + Logger.info("") + captionView.endEditing() + touchInterceptorView.isHidden = true + } + + @objc + public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { assert(self.videoPlayer != nil) self.pauseVideo() } - // MARK: - Event Handlers - @objc public func playButtonTapped() { self.playVideo() @@ -945,6 +975,21 @@ extension AttachmentPrepViewController: CaptionViewDelegate { attachment.captionText = captionText prepDelegate?.prepViewController(self, didUpdateCaptionForAttachmentItem: attachmentItem) } + + func captionViewDidBeginEditing(_ captionView: CaptionView) { + // Don't allow user to pan until they've dismissed the keyboard. + // This avoids a really ugly animation from simultaneously dismissing the keyboard + // while loading a new PrepViewController, and it's CaptionView, whose layout depends + // on the keyboard's position. + self.touchInterceptorView.isHidden = false + //self.prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) + } + + func captionViewDidEndEditing(_ captionView: CaptionView) { + self.touchInterceptorView.isHidden = true + +// self.prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) + } } extension AttachmentPrepViewController: UIScrollViewDelegate { @@ -1062,6 +1107,8 @@ class BottomToolView: UIView { protocol CaptionViewDelegate: class { func captionView(_ captionView: CaptionView, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) + func captionViewDidBeginEditing(_ captionView: CaptionView) + func captionViewDidEndEditing(_ captionView: CaptionView) } class CaptionView: UIView { @@ -1113,7 +1160,12 @@ class CaptionView: UIView { // MARK: + func endEditing() { + textView.resignFirstResponder() + } + override var inputAccessoryView: UIView? { + // Don't inherit the vc's inputAccessoryView return nil } @@ -1169,10 +1221,12 @@ class CaptionView: UIView { extension CaptionView: UITextViewDelegate { public func textViewDidBeginEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() + delegate?.captionViewDidBeginEditing(self) } public func textViewDidEndEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() + delegate?.captionViewDidEndEditing(self) } public func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool { From dd82803a10022b7b375534b44b4351ad68f20e26 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 15:50:05 -0600 Subject: [PATCH 06/22] second abandoned attempt to require dismiss before page --- .../AttachmentApprovalViewController.swift | 137 ++++++++++++++---- 1 file changed, 107 insertions(+), 30 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index dd7e57844..8a1437370 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -167,6 +167,78 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return bottomToolView }() +// - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event +// { +// UIView *hitView = [super hitTest:point withEvent:event]; +// +// // If the hitView is THIS view, return the view that you want to receive the touch instead: +// if (hitView == self) { +// return otherView; +// } +// // Else return the hitView (as it could be one of this view's buttons): +// return hitView; +// } +// + +// protocol TouchInterceptorViewDelegate: class { +// +// } + class TouchInterceptorView: UIView { +// weak var delegate: TouchInterceptorViewDelegate? + + override init(frame: CGRect) { + super.init(frame: frame) + +// let touchInterceptorView = TouchInterceptorView() +// touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) +// let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) +// touchInterceptorView.addGestureRecognizer(tapGesture) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - + + var deadZoneView: UIView? + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + guard let deadZoneView = self.deadZoneView else { + return super.hitTest(point, with: event) + } + + guard !self.isHidden else { + return super.hitTest(point, with: event) + } + + let convertedPoint = deadZoneView.convert(point, from: self) + if deadZoneView.point(inside: convertedPoint, with: event) { + return deadZoneView + } else { + return super.hitTest(point, with: event) + } + } + } + + lazy var touchInterceptorView: TouchInterceptorView = { + let touchInterceptorView = TouchInterceptorView() + touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) + touchInterceptorView.addGestureRecognizer(tapGesture) + + return touchInterceptorView + }() + + @objc + func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { + let point = gesture.location(in: currentPageController.captionView) + guard point + Logger.info("") + self.becomeFirstResponder() + touchInterceptorView.isHidden = true + } + // MARK: - View Lifecycle override public func viewDidLoad() { @@ -202,6 +274,11 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC selector: #selector(keyboardWillChangeFrame(notification:)), name: .UIKeyboardWillChangeFrame, object: nil) + + view.addSubview(touchInterceptorView) + touchInterceptorView.autoPinEdgesToSuperviewEdges() + touchInterceptorView.isHidden = true + touchInterceptorView.deadZoneView = currentPageController.captionView } override public func viewWillAppear(_ animated: Bool) { @@ -551,13 +628,13 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment) } -// func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { -// self.touchInterceptorView.isHidden = false -// } -// -// func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) { -// self.touchInterceptorView.isHidden = true -// } + func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { + self.touchInterceptorView.isHidden = false + } + + func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) { + self.touchInterceptorView.isHidden = true + } var bottomToolbarInset: CGFloat { return lastKnownBottomToolbarInset @@ -610,8 +687,8 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate { protocol AttachmentPrepViewControllerDelegate: class { func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) -// func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) -// func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) + func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) + func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) var bottomToolbarInset: CGFloat { get } } @@ -657,14 +734,14 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD return CaptionView(attachmentItem: attachmentItem) }() - lazy var touchInterceptorView: UIView = { - let touchInterceptorView = UIView() - touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) - touchInterceptorView.addGestureRecognizer(tapGesture) - - return touchInterceptorView - }() +// lazy var touchInterceptorView: UIView = { +// let touchInterceptorView = UIView() +// touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) +// let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) +// touchInterceptorView.addGestureRecognizer(tapGesture) +// +// return touchInterceptorView +// }() override public func loadView() { self.view = UIView() @@ -769,9 +846,9 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // Caption - view.addSubview(touchInterceptorView) - touchInterceptorView.autoPinEdgesToSuperviewEdges() - touchInterceptorView.isHidden = true +// view.addSubview(touchInterceptorView) +// touchInterceptorView.autoPinEdgesToSuperviewEdges() +// touchInterceptorView.isHidden = true view.addSubview(captionView) captionView.delegate = self @@ -812,12 +889,12 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // MARK: - Event Handlers - @objc - func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { - Logger.info("") - captionView.endEditing() - touchInterceptorView.isHidden = true - } +// @objc +// func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { +// Logger.info("") +// captionView.endEditing() +// touchInterceptorView.isHidden = true +// } @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { @@ -981,14 +1058,14 @@ extension AttachmentPrepViewController: CaptionViewDelegate { // This avoids a really ugly animation from simultaneously dismissing the keyboard // while loading a new PrepViewController, and it's CaptionView, whose layout depends // on the keyboard's position. - self.touchInterceptorView.isHidden = false - //self.prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) +// self.touchInterceptorView.isHidden = false + self.prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) } func captionViewDidEndEditing(_ captionView: CaptionView) { - self.touchInterceptorView.isHidden = true +// self.touchInterceptorView.isHidden = true -// self.prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) + self.prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) } } From 33750baf674973241348c3d39652f619ac22365a Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 16:02:36 -0600 Subject: [PATCH 07/22] finally got dismiss-before-swipe --- .../AttachmentApprovalViewController.swift | 135 +++++------------- 1 file changed, 36 insertions(+), 99 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 8a1437370..2a4801992 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -167,78 +167,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return bottomToolView }() -// - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event -// { -// UIView *hitView = [super hitTest:point withEvent:event]; -// -// // If the hitView is THIS view, return the view that you want to receive the touch instead: -// if (hitView == self) { -// return otherView; -// } -// // Else return the hitView (as it could be one of this view's buttons): -// return hitView; -// } -// - -// protocol TouchInterceptorViewDelegate: class { -// -// } - class TouchInterceptorView: UIView { -// weak var delegate: TouchInterceptorViewDelegate? - - override init(frame: CGRect) { - super.init(frame: frame) - -// let touchInterceptorView = TouchInterceptorView() -// touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) -// let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) -// touchInterceptorView.addGestureRecognizer(tapGesture) - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - // MARK: - - - var deadZoneView: UIView? - - override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - guard let deadZoneView = self.deadZoneView else { - return super.hitTest(point, with: event) - } - - guard !self.isHidden else { - return super.hitTest(point, with: event) - } - - let convertedPoint = deadZoneView.convert(point, from: self) - if deadZoneView.point(inside: convertedPoint, with: event) { - return deadZoneView - } else { - return super.hitTest(point, with: event) - } - } - } - - lazy var touchInterceptorView: TouchInterceptorView = { - let touchInterceptorView = TouchInterceptorView() - touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) - let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) - touchInterceptorView.addGestureRecognizer(tapGesture) - - return touchInterceptorView - }() - - @objc - func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { - let point = gesture.location(in: currentPageController.captionView) - guard point - Logger.info("") - self.becomeFirstResponder() - touchInterceptorView.isHidden = true - } - // MARK: - View Lifecycle override public func viewDidLoad() { @@ -274,11 +202,6 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC selector: #selector(keyboardWillChangeFrame(notification:)), name: .UIKeyboardWillChangeFrame, object: nil) - - view.addSubview(touchInterceptorView) - touchInterceptorView.autoPinEdgesToSuperviewEdges() - touchInterceptorView.isHidden = true - touchInterceptorView.deadZoneView = currentPageController.captionView } override public func viewWillAppear(_ animated: Bool) { @@ -629,16 +552,32 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate } func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { - self.touchInterceptorView.isHidden = false + // Disable paging while captions are being edited to avoid a clunky animation. + // + // Loading the next page causes the CaptionView to resign first responder, which in turn + // dismisses the keyboard, which in turn affects the vertical offset of both the CaptionView + // from the page we're leaving as well as the page we're entering. Instead we require the + // user to dismiss *then* swipe. + disablePaging() } func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) { - self.touchInterceptorView.isHidden = true + enablePaging() } var bottomToolbarInset: CGFloat { return lastKnownBottomToolbarInset } + + // MARK: Helpers + + func disablePaging() { + pagerScrollView?.panGestureRecognizer.isEnabled = false + } + + func enablePaging() { + self.pagerScrollView?.panGestureRecognizer.isEnabled = true + } } // MARK: GalleryRail @@ -734,14 +673,13 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD return CaptionView(attachmentItem: attachmentItem) }() -// lazy var touchInterceptorView: UIView = { -// let touchInterceptorView = UIView() -// touchInterceptorView.backgroundColor = UIColor.yellow.withAlphaComponent(0.6) -// let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) -// touchInterceptorView.addGestureRecognizer(tapGesture) -// -// return touchInterceptorView -// }() + lazy var touchInterceptorView: UIView = { + let touchInterceptorView = UIView() + let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapTouchInterceptorView(gesture:))) + touchInterceptorView.addGestureRecognizer(tapGesture) + + return touchInterceptorView + }() override public func loadView() { self.view = UIView() @@ -846,9 +784,9 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // Caption -// view.addSubview(touchInterceptorView) -// touchInterceptorView.autoPinEdgesToSuperviewEdges() -// touchInterceptorView.isHidden = true + view.addSubview(touchInterceptorView) + touchInterceptorView.autoPinEdgesToSuperviewEdges() + touchInterceptorView.isHidden = true view.addSubview(captionView) captionView.delegate = self @@ -889,12 +827,12 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // MARK: - Event Handlers -// @objc -// func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { -// Logger.info("") -// captionView.endEditing() -// touchInterceptorView.isHidden = true -// } + @objc + func didTapTouchInterceptorView(gesture: UITapGestureRecognizer) { + Logger.info("") + captionView.endEditing() + touchInterceptorView.isHidden = true + } @objc public func didTapPlayerView(_ gestureRecognizer: UIGestureRecognizer) { @@ -1058,13 +996,12 @@ extension AttachmentPrepViewController: CaptionViewDelegate { // This avoids a really ugly animation from simultaneously dismissing the keyboard // while loading a new PrepViewController, and it's CaptionView, whose layout depends // on the keyboard's position. -// self.touchInterceptorView.isHidden = false + self.touchInterceptorView.isHidden = false self.prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) } func captionViewDidEndEditing(_ captionView: CaptionView) { -// self.touchInterceptorView.isHidden = true - + self.touchInterceptorView.isHidden = true self.prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) } } From e65eeff0fd8f4e40d50ab01a6359c7ca1d5a7665 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 17:19:11 -0600 Subject: [PATCH 08/22] Keyboard should cover _Caption_ TextView when _Message_ TextView becomes first responder. --- .../AttachmentApprovalViewController.swift | 51 +++++++++++-------- 1 file changed, 29 insertions(+), 22 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 2a4801992..cff9ab1bb 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -235,7 +235,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return true } - var lastKnownBottomToolbarInset: CGFloat = 0 + var lastObservedKeyboardHeight: CGFloat = 0 @objc func keyboardWillChangeFrame(notification: Notification) { @@ -252,7 +252,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return } - lastKnownBottomToolbarInset = keyboardEndFrame.size.height + lastObservedKeyboardHeight = keyboardEndFrame.size.height viewControllers?.forEach { viewController in guard let prepViewController = viewController as? AttachmentPrepViewController else { @@ -260,7 +260,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return } - prepViewController.update(bottomToolbarInset: lastKnownBottomToolbarInset) + prepViewController.updateCaptionViewBottomInset() } } @@ -355,6 +355,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC // use compact scale when keyboard is popped. let scale: AttachmentPrepViewController.AttachmentViewScale = self.isFirstResponder ? .fullsize : .compact pendingPage.setAttachmentViewScale(scale, animated: false) + pendingPage.updateCaptionViewBottomInset() } } @@ -565,8 +566,21 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate enablePaging() } - var bottomToolbarInset: CGFloat { - return lastKnownBottomToolbarInset + var desiredCaptionViewBottomInset: CGFloat { + // CaptionView bottom offset scenarios: + // + // 1. when no keyboard is popped (e.g. initially) to be *just* above the rail + // 2. when the CaptionView becomes first responder, to be *just* above the keyboard, so the + // user can see what they're typing. + // + // For these cases we apply the observed `lastKnownBottomToolbar + guard bottomToolView.mediaMessageTextToolbar.textView.isFirstResponder else { + return lastObservedKeyboardHeight + } + + // 3. when the MessageTextView becomes first responder, the keyboard should shift up + // "in front" of the CaptionView + return 0 } // MARK: Helpers @@ -629,7 +643,7 @@ protocol AttachmentPrepViewControllerDelegate: class { func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) - var bottomToolbarInset: CGFloat { get } + var desiredCaptionViewBottomInset: CGFloat { get } } public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarDelegate, OWSVideoPlayerDelegate { @@ -792,19 +806,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD captionView.delegate = self captionView.autoPinWidthToSuperview() - - // MJK TODO ideal CaptionView placement - // 1. when no keyboard is popped (e.g. initially) to be *just* above the rail - // 2. when the CaptionTextView is first responder, to be *just* above the keyboard - // 3. when the MessageTextView is first responder, to be behind the keyboard - - guard let prepDelegate = self.prepDelegate else { - owsFailDebug("prepDelegate was unexpectedly nil") - return - } - - let bottomToolbarInset = prepDelegate.bottomToolbarInset - captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewMargin: .bottom, withInset: bottomToolbarInset) + captionViewBottomConstraint = captionView.autoPinEdge(toSuperviewMargin: .bottom) } override public func viewWillLayoutSubviews() { @@ -820,8 +822,13 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // MARK: CaptionView lifts with keyboard var captionViewBottomConstraint: NSLayoutConstraint! - func update(bottomToolbarInset: CGFloat) { - captionViewBottomConstraint.constant = -bottomToolbarInset + func updateCaptionViewBottomInset() { + guard let prepDelegate = self.prepDelegate else { + owsFailDebug("prepDelegate was unexpectedly nil") + return + } + + captionViewBottomConstraint.constant = -prepDelegate.desiredCaptionViewBottomInset captionView.superview?.layoutIfNeeded() } @@ -1294,7 +1301,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { weak var mediaMessageTextToolbarDelegate: MediaMessageTextToolbarDelegate? private let addMoreButton: UIButton private let sendButton: UIButton - private let textView: UITextView + let textView: UITextView var messageText: String? { get { return self.textView.text } From feb5a0c444e8e8b1d7f52f7ff068dbc4fd2f470b Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 17:42:03 -0600 Subject: [PATCH 09/22] fix initial CaptionView layout glitch --- .../AttachmentApprovalViewController.swift | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index cff9ab1bb..d3dd9014d 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -681,7 +681,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD fatalError("init(coder:) has not been implemented") } - // MARK: - View Lifecycle + // MARK: - Subviews lazy var captionView: CaptionView = { return CaptionView(attachmentItem: attachmentItem) @@ -695,6 +695,8 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD return touchInterceptorView }() + // MARK: - View Lifecycle + override public func loadView() { self.view = UIView() @@ -821,6 +823,7 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD // MARK: CaptionView lifts with keyboard + var hasLaidOutCaptionView: Bool = false var captionViewBottomConstraint: NSLayoutConstraint! func updateCaptionViewBottomInset() { guard let prepDelegate = self.prepDelegate else { @@ -828,8 +831,21 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD return } - captionViewBottomConstraint.constant = -prepDelegate.desiredCaptionViewBottomInset - captionView.superview?.layoutIfNeeded() + let changeBlock = { + self.captionViewBottomConstraint.constant = -prepDelegate.desiredCaptionViewBottomInset + self.captionView.superview?.layoutIfNeeded() + } + + // To avoid an animation glitch, we apply this update without animation before initial + // appearance. But after that, we want to apply the constraint change within the existing + // animation context, since we call this while handling a UIKeyboard notification, which + // allows us to slide up the CaptionView in lockstep with the keyboard. + if hasLaidOutCaptionView { + changeBlock() + } else { + hasLaidOutCaptionView = true + UIView.performWithoutAnimation { changeBlock() } + } } // MARK: - Event Handlers From 8776dd190900014c91fa3da82671edf23a2a0446 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 18:03:01 -0600 Subject: [PATCH 10/22] New "add caption" and "done" assets --- .../ic_add_caption.imageset/Contents.json | 23 ++++++++ .../add-caption-24@1x.png | Bin 0 -> 170 bytes .../add-caption-24@2x.png | Bin 0 -> 212 bytes .../add-caption-24@3x.png | Bin 0 -> 304 bytes .../CropScaleImageViewController.swift | 3 +- .../AttachmentApprovalViewController.swift | 53 +++++++++++++++--- SignalMessaging/Views/CommonStrings.swift | 2 + 7 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 Signal/Images.xcassets/ic_add_caption.imageset/Contents.json create mode 100644 Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@1x.png create mode 100644 Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@2x.png create mode 100644 Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@3x.png diff --git a/Signal/Images.xcassets/ic_add_caption.imageset/Contents.json b/Signal/Images.xcassets/ic_add_caption.imageset/Contents.json new file mode 100644 index 000000000..0e4bd3f5c --- /dev/null +++ b/Signal/Images.xcassets/ic_add_caption.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "add-caption-24@1x.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "add-caption-24@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "add-caption-24@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@1x.png b/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..24a60cdc729533e6d971f719b78d5c3386b8859e GIT binary patch literal 170 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR0pkR!r zi(`mKXY!x_|LvJ|8HIg)<+mnWVOqr?Hbb$erzhm3!2kdM(_NnN|Lt*`s?fyBeS+tU zLXu;+ORmFbg(i2Sm5wbkIuesSvR2IM+HmyX1^sTf>@9)Z8Z10KJPhybwG=g0tp(Y` N;OXk;vd$@?2>=v}Hr@aL literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@2x.png b/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..86a3534714c09123e384c8dce1c3b8cde60a28b1 GIT binary patch literal 212 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?2=RS;(M3{v?36m0W! zaSX|5e0%*MSA&88L!kMu|MBua zkaRF^*!KN=e@75|%bA{skB)xHKOii5mLY1U<`w_h#j>aU7e3^*5eV!Z literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@3x.png b/Signal/Images.xcassets/ic_add_caption.imageset/add-caption-24@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..ddc372a561a12e2cc60fa96878b7df06b54bdf50 GIT binary patch literal 304 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY1|&n@ZgvM!oCO|{#S9FJ<{->y95KHNDEP?J z#WAE}&fA*@xtbhA90JvU{g0owV5-1*?{0-Bb5gmUKJA>m`P)7A&BrTR%p02=92Y1A z2xxGKuy8T4;^xk~{BY|vUGp>F?!A6|m|G2~KF5|_7^dlA828ak!HRv;Ue4*{zopr0QNv-@&Et; literal 0 HcmV?d00001 diff --git a/Signal/src/ViewControllers/CropScaleImageViewController.swift b/Signal/src/ViewControllers/CropScaleImageViewController.swift index 08721b178..4ba967e14 100644 --- a/Signal/src/ViewControllers/CropScaleImageViewController.swift +++ b/Signal/src/ViewControllers/CropScaleImageViewController.swift @@ -444,8 +444,7 @@ import SignalMessaging cancelButton.autoPinEdge(toSuperviewEdge: .bottom) cancelButton.autoPinEdge(toSuperviewEdge: .left) - let doneButton = createButton(title: NSLocalizedString("BUTTON_DONE", - comment: "Label for generic done button."), + let doneButton = createButton(title: CommonStrings.doneButton, action: #selector(donePressed)) buttonRow.addSubview(doneButton) doneButton.autoPinEdge(toSuperviewEdge: .top) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index d3dd9014d..2c43e3c6e 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -1178,17 +1178,24 @@ class CaptionView: UIView { super.init(frame: .zero) + backgroundColor = UIColor.black.withAlphaComponent(0.6) + self.captionText = attachmentItem.captionText + textView.delegate = self - addSubview(placeholderTextView) - placeholderTextView.autoPinEdgesToSuperviewMargins() + let textContainer = UIView() + textContainer.addSubview(placeholderTextView) + placeholderTextView.autoPinEdgesToSuperviewEdges() - backgroundColor = UIColor.black.withAlphaComponent(0.6) - addSubview(textView) - textView.autoPinEdgesToSuperviewMargins() - textView.delegate = self + textContainer.addSubview(textView) + textView.autoPinEdgesToSuperviewEdges() + textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight) - self.textViewHeightConstraint = textView.autoSetDimension(.height, toSize: kMinTextViewHeight) + let hStack = UIStackView(arrangedSubviews: [addCaptionButton, textContainer, doneButton]) + doneButton.isHidden = true + + addSubview(hStack) + hStack.autoPinEdgesToSuperviewMargins() } required init?(coder aDecoder: NSCoder) { @@ -1236,8 +1243,8 @@ class CaptionView: UIView { placeholderTextView.backgroundColor = .clear placeholderTextView.keyboardAppearance = Theme.keyboardAppearance placeholderTextView.font = UIFont.ows_dynamicTypeBody - // MJK FIXME always dark theme - placeholderTextView.textColor = Theme.placeholderColor + + placeholderTextView.textColor = Theme.darkThemePrimaryColor placeholderTextView.returnKeyType = .done return placeholderTextView @@ -1253,16 +1260,44 @@ class CaptionView: UIView { return textView }() + + lazy var addCaptionButton: UIButton = { + let addCaptionButton = OWSButton { [weak self] in + self?.textView.becomeFirstResponder() + } + + let icon = #imageLiteral(resourceName: "ic_add_caption").withRenderingMode(.alwaysTemplate) + addCaptionButton.setImage(icon, for: .normal) + addCaptionButton.tintColor = Theme.darkThemePrimaryColor + + return addCaptionButton + }() + + lazy var doneButton: UIButton = { + let doneButton = OWSButton { [weak self] in + self?.textView.resignFirstResponder() + } + doneButton.setTitle(CommonStrings.doneButton, for: .normal) + doneButton.tintColor = Theme.darkThemePrimaryColor + + return doneButton + }() } extension CaptionView: UITextViewDelegate { public func textViewDidBeginEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() + doneButton.isHidden = false + addCaptionButton.isHidden = true + delegate?.captionViewDidBeginEditing(self) } public func textViewDidEndEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() + doneButton.isHidden = true + addCaptionButton.isHidden = false + delegate?.captionViewDidEndEditing(self) } diff --git a/SignalMessaging/Views/CommonStrings.swift b/SignalMessaging/Views/CommonStrings.swift index af4e0d778..ac35634ad 100644 --- a/SignalMessaging/Views/CommonStrings.swift +++ b/SignalMessaging/Views/CommonStrings.swift @@ -14,6 +14,8 @@ import Foundation @objc static public let cancelButton = NSLocalizedString("TXT_CANCEL_TITLE", comment: "Label for the cancel button in an alert or action sheet.") @objc + static public let doneButton = NSLocalizedString("BUTTON_DONE", comment: "Label for generic done button.") + @objc static public let retryButton = NSLocalizedString("RETRY_BUTTON_TEXT", comment: "Generic text for button that retries whatever the last action was.") @objc static public let openSettingsButton = NSLocalizedString("OPEN_SETTINGS_BUTTON", comment: "Button text which opens the settings app") From a946ec00569c82fdd4042c91c53aeb0f5a6726f8 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 23 Nov 2018 18:20:44 -0600 Subject: [PATCH 11/22] new icon assets per design --- .../album_add_more.imageset/Contents.json | 6 +++--- .../add-photo-24@1x.png | Bin 0 -> 262 bytes .../add-photo-24@2x.png | Bin 0 -> 392 bytes .../add-photo-24@3x.png | Bin 0 -> 549 bytes .../album_add_more@1x.png | Bin 1487 -> 0 bytes .../album_add_more@2x.png | Bin 1836 -> 0 bytes .../album_add_more@3x.png | Bin 2448 -> 0 bytes .../ic_small_x.imageset/x-shadow-12@1x.png | Bin 299 -> 345 bytes .../ic_small_x.imageset/x-shadow-12@2x.png | Bin 545 -> 563 bytes .../ic_small_x.imageset/x-shadow-12@3x.png | Bin 879 -> 979 bytes .../caption-shadow-24@1x.png | Bin 336 -> 236 bytes .../caption-shadow-24@2x.png | Bin 725 -> 330 bytes .../caption-shadow-24@3x.png | Bin 1196 -> 427 bytes .../AttachmentApprovalViewController.swift | 4 +++- 14 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png create mode 100644 Signal/Images.xcassets/album_add_more.imageset/add-photo-24@2x.png create mode 100644 Signal/Images.xcassets/album_add_more.imageset/add-photo-24@3x.png delete mode 100644 Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png delete mode 100644 Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png delete mode 100644 Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png diff --git a/Signal/Images.xcassets/album_add_more.imageset/Contents.json b/Signal/Images.xcassets/album_add_more.imageset/Contents.json index b4dcc1357..3b4a85bc9 100644 --- a/Signal/Images.xcassets/album_add_more.imageset/Contents.json +++ b/Signal/Images.xcassets/album_add_more.imageset/Contents.json @@ -2,17 +2,17 @@ "images" : [ { "idiom" : "universal", - "filename" : "album_add_more@1x.png", + "filename" : "add-photo-24@1x.png", "scale" : "1x" }, { "idiom" : "universal", - "filename" : "album_add_more@2x.png", + "filename" : "add-photo-24@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "album_add_more@3x.png", + "filename" : "add-photo-24@3x.png", "scale" : "3x" } ], diff --git a/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png b/Signal/Images.xcassets/album_add_more.imageset/add-photo-24@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..1f653a075979628e27a0c9018bc16d772945f26f GIT binary patch literal 262 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjoCO|{#S9GGLLkg|>2BR0px_Qq z7sn8f&a0t}d@Tw*w|n@%W`163Jmay`HD1=IezSF$qP?!2JFv=2*^oE&|54U26Fn|{ zYMXE*{Mb9j;$Iqv-}Zi};8RU{#>!`{5_EgkM87jXg|0CB&vA%fQkHolS(vXdzax87 z`W4Z`>ctuXm7m&0@8R53ksKw$1n{_}|EN_1qbLZd%*?5e0v0t(T zSRws4_Lm2S5TIo)O=s@{4B!IPi7qraQ5ScOwQkH$YF`^g#2Wy-j1Dl8g9+EQC!7T$ z78R(ekc}r;6OhZtT?i5T*ht4okTtM2c>uT%+i|)dB&artjfUT@Y*}dofS$43o{LT5 z#eOEfD4$INgycmLvv|_awQ2$~$&)07_^gP-1X!=oi*=xd3SI0%Rx9*k9S9-3$w6Z* z3eYT2rHBI3B4rs?K#k<%y<7T(xdtMjm60DIHY%~#g*cl&;yPpoTEhisv+I3;Be#~M z{;g}nXJC#rlC?e>q%-g&y$z84KTylRkGhWAs5b6GrPO+(^IJ%p|5*dlGmJJRGhneo mPdWLYdTm^QHZDLF0Qdk3yfx@OB4IHA0000y95KI&fr0V5 zr;B4q#hkZu8gmaT2)Mpq#Wz9FD#$#;Y_+?m*-JGSzsFh|E$wC`CI9eunrLw7;nN(x zM=o=VopSD-QGUY5%BaEN;K;zm6d=&h#30flA$)XY#|cjtz2ws7RGI59EB6?t>a7U1 z4Bokft12>UMb_une9!0TX)*7KZV$3vHFFOCJo8KjkSQBF6+&(GwfLP19amX(ocR9u zYsHyQ3--@BI9*spJyPkg4!>m0Td(PJ3PpvVOx)}>sbfy<*Us82su~LFo|l5{BPWJW zUBF=(ILYQO%k6eA(yeVco89QBfc8w3l&bQH%-RP8IJsx@!ib5!m2C6ErpD`-)gF>z18qFXsYU) z!x=2_^iNXSCBrFvzjgHuU&o}e2y%5b0X@&MK*8Z!nLFP~<20Y&LdLSnz>sOUtG98+ z>4RSPwx!59UbnsHa`p^sZO|p{ll#S2C)RFxy0c<38zks}x)0=aCh?foY(2{X4NkC$ zI@exZPFwZs;hf(SrwU&AmG$51@Rp00i_>zopr03j#U>;M1& literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@1x.png deleted file mode 100644 index bc89e5a4fafa18030dc9add6e0eaf086e4bb1abd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1487 zcmeAS@N?(olHy`uVBq!ia0vp^Vj#@H1|*Mc$*~4fjKx9jP7LeL$-D$|I14-?iy0WW zg+Z8+Vb&Z81_llpi<;HsXMd|v6mX?-X(Gs7c7{+3kj2o|M`E)8SrADBDCn&MGAmMZB3v?o0Sfko5ztjwCD1iG=jY@X z1s5bHr-B>?)`BF2t{QAjBra=^B#<cpt8_^$NwqUFFtpG$G}JXT4Kc8^GBmU@GPTi1Q-kCJkc@LtYGO%#QAmD% zjvd$+xgf5Bv7WgeR4=j$sAd~|P(DJ+SCC8#76s;7J1(HDuoA$Ii?@5~Hei9`;_2cT zVj;M7s$t$C0|~d}kkHf>0&^uK7)tf_vVP&1)5z_Rf56j%fw`bHPggi7l#|gZw##ag zYVCZML!x}0oHGKou^&MGja&w+#Zs&Yk>!qIgS$E4) z?y03#uh|&#kaxQ9MwNAqF<%R%wLY@F%blowv-sHksK+~X&#wHV_t8Xs=GvJt@A(hi z`fG6IfSg`(<%V)L#@V4Q{JBxbEmdKI;Vst0Qo@hBme*a diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@2x.png deleted file mode 100644 index b5102c6741cd9837af969c342c8088a72c3d832e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1836 zcmeAS@N?(olHy`uVBq!ia0vp^Iv~u!1|;QLq8Nb`V{wqX6T`Z5GB1G~&H|6fVg?3o zVGw3ym^DX&fq_LOGbExU!q>+tIX_n~F(p4KRj(qq0H~UQ!KT6r$jnVGNmQuF&B-ga zs<2f8tFQvHLBje<3ScEA*|tg%z5xo(`9-M;rg|oN21<5Z3JMA~MJZ`kK`w4k?LeNb zQbtKhft9{~d3m{Bxv^e;QM$gNrKP35fswwEkuFe$ZgFK^Nn(X=Ua>O75STeGsl~}f znFS@8`FRQ;a}$&DOG|8(lt3220mPjpnP~`{@`|C}0(wv%B%^PrXP}QwTWUon4s9SA zoZ3>7;l3&;Ey@A=DJ5AyH77MUHLs)?sLv3qb-=KNYeaEmMPdQOGH@V5{AL4kxm8eV zaehuICu1<^J5 z7i9u{nh0{2ogvf$WHEI0k=QIi7DUnj3VN%6%!<^U2$xJ?fP#HtVq&9@RRUe3bAC>K zQE)+Gaw^DSU@b^O=&HfiMB=grNdie@O0rdPX;M~datTsw0pkpu_MH;b^^t^a^s%b8 z0j2~i-~5!!v`Ux6l2kh*149d4LqlCd(+~qoD?>vo17jO~G&M*r0LeHPr6!i-7lq{K z=h%ULkqhD~80(qqnL_j;tAJ{@(Ff%tqV%a5c3e-w_5#ZbriGp^ zjv*18S7+M$i#SRgi%ja_^in>;&k!N;NIpwxYKG?mhAoY5_U*@>>}%khvh7x8rXX_W*S&P1^c=l8(@Ug{I76*_62$ieN2tUV8#nloRl>YjTc_pV>r-IdZA8#X7ozVAO#-Ohe?-^u&=d{Ixi zQezWZOISN!{$Ei3HQ=j{e8_4x{mAo&zGvQ@zQCaWyM+JAR+V^L1rygleR?lj`Z)|#TE24gu534*|COyf ze1*W{B@L!O`&5^)*vN6OjbODA6Z|lvp^T$n`tKyeS^JH&%qOOOTdI8EeZlW|6`>aw ze{3*(|`7;)D-_u8*D2jq(17fy$WWKxn7jbXVu60_D{sZ?edF0-!iJ0az6Hl#Dl;u ze63uS{bFoyt_obX-12l{)a|(9y`L&A{h97gxFu54>o+lbMei-KTM;?c1v~1LCS`Sp zdB>Jd6ieB1?BdSS>I0U!ck3MGFSyr5Z(SO#@sFXTa^n4;HOZ-&t;ucLK6VG C9idGC diff --git a/Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png b/Signal/Images.xcassets/album_add_more.imageset/album_add_more@3x.png deleted file mode 100644 index 87e6bf6f2af7ebc42c45b5270fa6894491b01b5f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2448 zcmZ`*2~<gL<|N(l1B+RF;xNxMcRV3 z=m(KP)u0rsfJI~yq!a`y^lL3BA|hg-Qh#Wv{3M{}v+%9;hI`K5cb|RkyKlYRkRU%D zO=C?M45qWg9}JV;y<{gsO?n-t!k$ZSO8hWCFIa7#>6CP!k>nrEhrzTAWTz7BOc@FW zQ?X=GqoAlj3W32%z|dnk`gAoM;>0<&DqN4=~>_k36Kmz0lg7jGy#sX+L z1Uf_lq5?zE-W(ni?SgT}I09r%G#X9h#U3Dpfxatr>4*dzgrFn>7Mqfif=O}0aCmW8 zoV&X_))9}z;~k_32YzZIL>D+D@-3Dpc{dNpmv27#ZvKc2~e6jn>R zX7YF6iyntn2oteuqKVQsfe^@vWgSQb=@65Qcf`3l;BXE&7pkK>0f!^Fx)ZUhFbWmO zGTuBU9pdn)91fc-cab0)qMb0?FfPknMZ^l3h?VUlg?+WWY2{Jrt|ep- z-27|)m`BJbdGp`1q|KMjqU_TcGLr$HJoFiD<+*a}UxK7@~C!cC_5+H>HtQ>=+=?fF^j2 zCKoaSFo+qqw{B^r8SBf90LjRz`&PQa@ApSPR~wTAR_(-H#g7H#X3Y)Y9$f zJe*^;&W@w)}x!aS<}(d}*k}4y9FWyA#(`(A_}olY9?)w6O{Q zRtqi(FHUCwkA1rDy?GQttX8w6ly_||g7lv@T$h+X{$euzNq~x(p`E%X+&}-?c(*#O zuOUcBC@9sv*-Wr%pzUlFQBFApnFk2dv*TD7s^ZG(xdA=f$NUUpiMI1y3GwVIPg~0v z$|*zR4Kc?yG>s7cH1bgyxSFmO=NZ~uKeQCFLF=<7)b@Q*x{g;&JpRJFJZj zHyV}WLN5-VtxNt)?bo2!3}^1xbX%6^tZ7QXq%8L$u*=yqBUw82M zeBoUGUp^8m>Vgf5p__fm*A&T{16-p<0Be9&ji>6jp&xXi3RFDnA8Z6@uLtZzfm=rp z#ejc{hp(kgwO_G{uN2;k53Tm{Ws+M%wg9vqTctZQUEN#tko>^F6GPU>fzx#5lhaNh z*lfpi6B!{5@RW3J^Q%x^1j(NNO1I!yz+gPYeRkzpPjVTs9~1;LvW0IQy9@LNf;-I4 zT0r8UZ}RdkoN1gQ+E6Pm0pseQJcr=6vrpfK>&>ax zyijI0#NLVVt`fC7zwDki#pU$|76(l@Ss(VvJaXNdWCzm!YM6b&f z35#A+%WUjYGrYRO>e{Lx>CSTD?rEdHV=XEV~bV^lJ7Tawnx$~8mUbI16_7CqH_ zT#bs+`|<&=^HLuIuVzkk}gs9oW z>fik^Zfsj^HI<&tNbLKPqBA74!{;ul>iuJczfTeIgb9 QQudp)!zT!=^@_>-AKHkiZU6uP diff --git a/Signal/Images.xcassets/ic_small_x.imageset/x-shadow-12@1x.png b/Signal/Images.xcassets/ic_small_x.imageset/x-shadow-12@1x.png index 7cd37f5fe264719badef40acba6bbb12897fd69f..99e23b1131f56bbd016eb98d6f30afaf91e5ff32 100644 GIT binary patch delta 318 zcmV-E0m1&O0@(tPB!3BTNLh0L01FZT01FZU(%pXi0003CNkl{nO{euCgPq_K+F+gN5GY?C4gt7WDTl7E;kg+;2^SS@TJh!&2#Fe_us zYr?#HZZhw_1lC%DAUFb!fe&l#C!_N9nH-9uxX@bP0!t_UmVca$G1qCDUIGj0pJ=V` zt+lo+%Qdh9#1zQW7;__9p63t1IZj*&JTlGquu|#~xElI0!<4D6>$`ujaAKG;No85~ zw$s(Ojz_^@f3UE8+XVL5JtOE+Ne{~m7hqAh^_2LRv7D`L)V Q;s5{u07*qoM6N<$f)%Qg(f|Me delta 272 zcmV+r0q_3V0;>X$B!2;OQb$4nuFf3k0002xNkl3L1fBR-TO|AwS@DZD0unwpuq1xKo=C5DBe)<0W`=>*P4ut^4F-(SOAc+QfkZ327>}l4v zwzeWr2%&ipY%9?7l_0s>w{N?_+zg61Ct$>R0R>Pz2;_q_fg&yz7IAP*Kn77s0|4Z_ We6321w0!^o00{s|MNUMnLSTZQs&fqh diff --git a/Signal/Images.xcassets/ic_small_x.imageset/x-shadow-12@2x.png b/Signal/Images.xcassets/ic_small_x.imageset/x-shadow-12@2x.png index c1a2af037b89792b63de80ebce249031d8fbe11c..7f4edb311705cfb6963b100832cfc3f394256653 100644 GIT binary patch delta 538 zcmV+#0_FXo1hWK?B!3BTNLh0L02UE^4XJyG;3GoDd5~i#Sc>o4F(1nc&Xp>49eaT&Pb?h_} z!qU~PeBaNxJ|~g2mZB)?Q#JN@UgE=An^T9;hVOMD_{-?A`hO9}z!-ByM8^oMDR+&b z*2{dx4unsz_&7z(=kuradi^<@&8~^)jMw_uMqkJ}a)ALB9Ky$IbU;Lx#+aKt&%dp; zwkV3P$z*cRB4>&y;;4xc$OQ&iaDWS(%v}kv$W)8CmG}gZa+|wLVePxq#+P+Zd?Ih zpZF@Fj?TSzQO8Oufh0-d7Uw|$UnStu0`+yBW!W>XT#xhMb*(D_799Gzj#yy8ZE+sQ z@nM>#ulzgX`ORshU7qo%#3=D`9G|3V`awi5f)_Vc zPcTL+AtIqI@u>vlzkKqicqJMIyd+!-foH{Ynr_ps<)UdPIhjs(=giFR>~1U;u`GD_ z7x7J-`WqNK8fvD{kBB=nzo6+qCK-i;%Ro}s^)&RBV~cT^6MsbzCyK@55oV=;b-?G# z$V$Cle;5vj&j@k@`v6#zC&Km$$(I17TrOYGw^pk?!af9609WFekRFXjPh*H>S&dvS zr(twj5HI;vzu$kQ?_e-^$z(ET@MUtYu$)A-T0Q3kPVDu1&3rzu<9|>Pm%LOese+t7 zg+k#LKDz*y+K3rfCKof|@|^Ow}UDj$s&Dx7&RuF{RyZf2yi_4}b4t;Mbw@vd&$S*C~u|k-ZYd z61n6~D~7tsGP!+-!fMk%a-2}P75{zE?5n~w%}a5&AoD+671J)r$S=Fduj|Mk9B%L` zkiwmI3cc;v^BhFc9GiGDPvEx?YykHA%t0X}qfH&!qc6xK(?85K!w}Rd>+1jj002ov KPDHLkU;%gRO$HNn4s;6ABB8mmYf1#dFSuLJ=vH5(@39mtJ}*=t(FDf@e>jMRVz;rxd0B zVb>&$t(9PiLAFfTXFqq}+hjK_bzoq4KQmwE+j(zZgyT5GB7cPFqCe6Nc2ydV*EiXw zSeA8zeRB+*r!H6Tvd+)XTlIRq;y8|TbaeF9vaB=_-E&p0?c!o+rnsK>v#)BknrGh} zBb&`Wm2q8dWPh~J)kmzQQt7h;QZAQwlgZ>0BFX@;d!Aqd^a2Q}?1O!w@132UO&QnK zJ|Cb|Dm7Is7WXxvA_3yec)@UtL?ZFBDIiu}r~;*?d_^>1BxtN)yEiuzEP>FwasY4qa-jx zn5kCiGflHM3QZ`w+a8bkv@{IEFmk!vvw?wuK{5%9ObKgLnovXE_A2@~W0*BJGjpxdgc7O!aI0?)L3zP6ttO3hs)y+m z>;tgRTM;3|2x&wJaYjUEp4J8ZoT_(5%n(=8`uh4b(jEQsx6%UcK5doxz~}&} zS_lSU7{+TNdd}Je)ZYW7Y9Rp33kiso$5fznt&1h4pC7EoI)L>sLOwLw$Ujxh>-z6?dBNhe|H!O7c2k!=uB!2;OQb$4nuFf3k0009iNklBFjH z=OSzng(jdWXdD`WQV11BXq$b}CT%C7?2CXhQ0vMEMc9Bav)OF+X1m?a$K&yN+@H1U zWrVm5By7il5n?VDi=|=vRW6sif%`L10Yh;hBqdBblgT{q^?E<{A!zs>2G&r25qcV` zPA6`Cg)lMbEPr$<8jap-D#6AVLFxx{jxVLYXk@ zl}cq>?DE>}c3;Ep9MlzvBbld10JW($1k`3gC^wiL)n=#Dd6!5eR?sIyLShxgfv}Qu zx(L_aGe;8u$`{7=`Ze@*0m@Ke8;7uxu9Nwui?va~(0@lCPr1SDs-Ce2CUMCX=NXry zLb2UpVFJgaui;j!WqOL9Raet`tyVKVW47`14D4y*3x$-hg;--Qxdc52Jr7-uL?ZVA zvu8|GC>Yqa!~s>UR?UvK0d*TsUxls$=79<0;>|#wIY1fwI-txBks|hx(VszIU=e&$ zJwq@0EPsMQPXS6Al-rb)N~JHwV)5%FV1!$RS^?CC=?xX8(P->qDsQ3j0(6uuNBZ@= z5{kPuH{3N$(aS=i@J$6_xm+%PMh1S2XO^MN!d!venhquGoM)1fi7}lgZ?U34i>*5~BkK9cg>hB)1L+3<6k#aT)d4 zHdn%Uo{3ywxHqw#V<4ti103>T?!cfY%%l%!Q+0%8YM-h@{#3pOP1TDcgIWT55wFru zk!RjuN56vlG&IZ+LRKGl{>wP9t%r75en`P^RhI&q6nW~<705AX00_*{hB!3BTNLh0L01FZT01FZU(%pXi0001)1JFxyQ3Xa`pCB;4D~SF;(n&K3&45|7CVqK~4t0@P zF&JTltv1cEXt81z(azpKYLT}hx%m`8<|OZBz9~=w_qzUd0RR&PB4r7`gS8<50000< KMNUMnLSTZYx>6GW delta 309 zcmV-50m}aD0nh@FB!2;OQb$4nuFf3k0003BNklLR6y2Hyk_16S&pq@9 z`v3n&B8qYm#mdgH-7!c(rH3E~$8){ApGTD%&Y-~0E1>;^WMT#w@9RqsB%XniNTMq9 zvMl$}dv8-~y+HDT1xCIKryqO!8^l_bI`IY;owvA{%4LsElz-&{Sjnxr;Dx-j*5=MR z1*zn|6JtP?B#FwhY=y)f5WWE$=rm1x7ne!cgta_E13IlSri(;R<^b`!w68z`RHteO zi@bz1`P<#@iD}n}JVw!M;DEd5uz{=;#kMkUh^pHnY!&}S1t!`cAq8Z!rY<7I2?3Of zs6A4M%w`kM;x4aEg7ZU)ra&nav)D~J;%5_)LRjtw{wD=qa@0X`SFLpn00000NkvXX Hu0mjf zcj*6r&iaGWaXYW9XjORYW_T#TNMCUZ+orki$}$9F5gEVJC z@ScX+OUY>r6H3-KOt@JoHotNETayMYv;L29s__LG$ybim$IW~(@3X2%{4TY*E(~*u zE*@7c=?I^@=j3v^p7qDiGBkW%Fy)3x+@D9+8|qSW?wBfw|FSPqRJ&cOxVWyT5QXKRx+u~LNP-@r zt1bhi_uu9UJwuzu`6=4OcHYNo2#TWJ$gsmKW`HM>BZ_=)I2x&G+&lsv0gr%3z$4%h z@CbMWJOZ^MFv&Ky@l^hqKnrTxTAPDa*TQAP1vA2FAHpAnbAREJfRLI7@mt}Ca3>%J z3nJ$K`n~HrjXrNSo9{hu<-ue!`63E`Iz%>kdT=6$YU7Nz(}5G*(ySTeH#ZSgJ8eW^ zVD?V_R{**7d+>OD2b|#66Hs~wcxi5wbKlgS2@9dtiNNKr@Kd-E822m}fWj2K@r-GE z$~8W=84u%NV}IIcB>_acT`rebtJSIjsz?FxsaNPI0~#=!%>vrC{i;1>g%+NCK=1y_aurbWGgS?4_gyyMK)PkHU3_(Nze=wqZ}X{QDZ~ z-3yQ+s@p`cH$VWt5PSy^fG=8p0^AH$NR{U*&N@Ozsd1@g5dyiAnE*%wgjg`+{9FJx zNSBj1<9Ho@8u>`s7q+yMiGa9a=LuMvSX)~3P(Fw}2^QIH>zW8_d-L)WfH)J$&0`z? z_6Q)dgMXJZ*&IH(MPDv}i7a-l9KBF`Dsa??szQ*ps>1&b0xYtTlzcC?G*#$B6j7AU z4_%7@?xk}z9BoE4tt!00b_#n5$WMSph!QUseKw#f9a6x~s-nF{*){y95KI&fq^mB z)5S5QV$R#!hJJ?~1RO5Pwgd$%UHn|^&VQkpM!hYWWdG*edq)-X-l{=?V!1;Rm~YFk_p`T4 z^WOa_v0kxt+w-5M8NU|ZfA@sX<%qg%Ycx-a_KU@q|6Z+ocl_@Q~b=|HNEW41ESqS3j3^ HP6fuA0$u~U%bB+5%jPdiYMa6~*(4*)s6IQ=XT^3?RUttj2n2y35Cnoi5C{T6AP5A3 zAP@wCKoAH5K_CbOfglhBg1{&UEHYO3dh!>hq`p2gbQ0BjvwsrZHK~lIt|cjlecjS} z-);1crIdZU6(=V^*qZ({{n038Nej|SwN2`sCl8z_rq4_tH)TbW6(=W(8o*qcel)!_ z{bnjK_^o`^1!QdpAzJR4KHu;69}cxM$}AR(7v>JIh^AjD*NBFcK#?{85kYeme8b1= zTaf|;9Fz!R-+#GpI#Re zK)#xOGUbTJM@5lk+fo9=goGdyelZ0Kld-8GrRclvF@LUKLEuRLZi-L%hL8UoK^g!A zl*r+t$gV$!2x5@|!(~wmEuXFGkCY;aflRdCY&P$<+ikVm?P|4)V}(vhY@4Xii;Ig| zm&;|fUaw!859~vNbmUSB5T0vC2hhXSYE@4PAP;9MkN|^pA>o^;whX!)t$GTO;6Szh zvkGm<3xCd3z~HeL0a9vh{MW=sMG(HXAf*6VpMx-90UShNl-89K`tgy}m|go@$xeUQd0N#^|ER_n%FpXSpbH6n_Fk7e(^g*epAU;G{0DtU4Dy%BKo& zm_uW^O{4fkeNu7}qya!cxgb6=h~p zHh=1MNv^&#QSGFtBFU#}bHmc7nBWjWyiOQ1Ho-0&A_90-lFnIuSJcm^iXiHaBo#rv zlVJI}G30Ru#{g8^LpsI^f=|h--07Z85pu0e)nQ zPkV=hUvk*{*sb$rpZ8;i$_o(B^Z8WyEH>pF&efmgp*>8}h1J6&V#**81cE>i2m(PM q2n2y35Cnoi5C{T6APCHYz;8h1T@kxQ+f^3;0000 Date: Sat, 24 Nov 2018 09:19:45 -0600 Subject: [PATCH 12/22] white tint for attachment approval textview cursors --- .../ViewControllers/AttachmentApprovalViewController.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 4cb4f52fa..bd18168bd 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -1245,6 +1245,7 @@ class CaptionView: UIView { placeholderTextView.font = UIFont.ows_dynamicTypeBody placeholderTextView.textColor = Theme.darkThemePrimaryColor + placeholderTextView.tintColor = Theme.darkThemePrimaryColor placeholderTextView.returnKeyType = .done return placeholderTextView @@ -1256,7 +1257,7 @@ class CaptionView: UIView { textView.keyboardAppearance = Theme.keyboardAppearance textView.font = UIFont.ows_dynamicTypeBody textView.textColor = Theme.darkThemePrimaryColor - textView.returnKeyType = .done + textView.tintColor = Theme.darkThemePrimaryColor return textView }() @@ -1395,7 +1396,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { init(isAddMoreVisible: Bool) { self.addMoreButton = UIButton(type: .custom) self.sendButton = UIButton(type: .system) - self.textView = MessageTextView() + self.textView = MessageTextView() self.textViewHeight = kMinTextViewHeight super.init(frame: CGRect.zero) @@ -1409,6 +1410,7 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { textView.delegate = self textView.keyboardAppearance = Theme.keyboardAppearance textView.backgroundColor = Theme.darkThemeBackgroundColor + textView.tintColor = Theme.darkThemePrimaryColor textView.layer.borderColor = Theme.darkThemePrimaryColor.cgColor textView.layer.borderWidth = 0.5 textView.layer.cornerRadius = kMinTextViewHeight / 2 From 838012d1ec4d3e597efb2d87a983f4bcf9f9351d Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 09:38:53 -0600 Subject: [PATCH 13/22] Caption length limit and label --- .../translations/en.lproj/Localizable.strings | 5 +- .../AttachmentApprovalViewController.swift | 86 ++++++++++++++----- 2 files changed, 67 insertions(+), 24 deletions(-) diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 69677fcf4..7c0197027 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -99,7 +99,7 @@ "ATTACHMENT" = "Attachment"; /* One-line label indicating the user can add no more text to the attachment caption. */ -"ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED" = "Message limit reached."; +"ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED" = "Caption limit reached."; /* placeholder text for an empty captioning field */ "ATTACHMENT_APPROVAL_CAPTION_PLACEHOLDER" = "Add a caption…"; @@ -110,6 +110,9 @@ /* Format string for file size label in call interstitial view. Embeds: {{file size as 'N mb' or 'N kb'}}. */ "ATTACHMENT_APPROVAL_FILE_SIZE_FORMAT" = "Size: %@"; +/* One-line label indicating the user can add no more text to the media message field. */ +"ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED" = "Message limit reached."; + /* Label for 'send' button in the 'attachment approval' dialog. */ "ATTACHMENT_APPROVAL_SEND_BUTTON" = "Send"; diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index bd18168bd..4c2e6490f 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -1168,8 +1168,22 @@ class CaptionView: UIView { private let kMinTextViewHeight: CGFloat = 38 private var textViewHeightConstraint: NSLayoutConstraint! - // TODO show length limit label - private let lengthLimitLabel: UILabel = UILabel() + private lazy var lengthLimitLabel: UILabel = { + let lengthLimitLabel = UILabel() + + // Length Limit Label shown when the user inputs too long of a message + lengthLimitLabel.textColor = .white + lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the attachment caption.") + lengthLimitLabel.textAlignment = .center + + // Add shadow in case overlayed on white content + lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor + lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + lengthLimitLabel.layer.shadowOpacity = 0.8 + lengthLimitLabel.isHidden = true + + return lengthLimitLabel + }() // MARK: Initializers @@ -1196,6 +1210,13 @@ class CaptionView: UIView { addSubview(hStack) hStack.autoPinEdgesToSuperviewMargins() + + addSubview(lengthLimitLabel) + lengthLimitLabel.autoPinEdge(toSuperviewMargin: .left) + lengthLimitLabel.autoPinEdge(toSuperviewMargin: .right) + lengthLimitLabel.autoPinEdge(.bottom, to: .top, of: textView, withOffset: -9) + lengthLimitLabel.setContentHuggingHigh() + lengthLimitLabel.setCompressionResistanceHigh() } required init?(coder aDecoder: NSCoder) { @@ -1285,6 +1306,7 @@ class CaptionView: UIView { }() } +let kMaxCaptionCharacterCount = 240 extension CaptionView: UITextViewDelegate { public func textViewDidBeginEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() @@ -1306,8 +1328,9 @@ extension CaptionView: UITextViewDelegate { let existingText: String = textView.text ?? "" let proposedText: String = (existingText as NSString).replacingCharacters(in: range, with: text) - guard proposedText.utf8.count <= kOversizeTextMessageSizeThreshold else { - Logger.debug("long text was truncated") + let kMaxCaptionByteCount = kOversizeTextMessageSizeThreshold / 4 + guard proposedText.utf8.count <= kMaxCaptionByteCount else { + Logger.debug("hit caption byte count limit") self.lengthLimitLabel.isHidden = false // `range` represents the section of the existing text we will replace. We can re-use that space. @@ -1324,16 +1347,29 @@ extension CaptionView: UITextViewDelegate { return false } - self.lengthLimitLabel.isHidden = true - // Though we can wrap the text, we don't want to encourage multline captions, plus a "done" button - // allows the user to get the keyboard out of the way while in the attachment approval view. - if text == "\n" { - textView.resignFirstResponder() + // After verifying the byte-length is sufficiently small, verify the character count is within bounds. + // Normally this character count should entail *much* less byte count. + guard proposedText.count <= kMaxCaptionCharacterCount else { + Logger.debug("hit caption character count limit") + + self.lengthLimitLabel.isHidden = false + + // `range` represents the section of the existing text we will replace. We can re-use that space. + let charsAfterDelete: Int = (existingText as NSString).replacingCharacters(in: range, with: "").count + + // Accept as much of the input as we can + let charBudget: Int = Int(kMaxCaptionCharacterCount) - charsAfterDelete + if charBudget >= 0 { + let acceptableNewText = String(text.prefix(charBudget)) + textView.text = (existingText as NSString).replacingCharacters(in: range, with: acceptableNewText) + } + return false - } else { - return true } + + self.lengthLimitLabel.isHidden = true + return true } public func textViewDidChange(_ textView: UITextView) { @@ -1360,7 +1396,22 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { set { self.textView.text = newValue } } - private let lengthLimitLabel: UILabel = UILabel() + private lazy var lengthLimitLabel: UILabel = { + let lengthLimitLabel = UILabel() + + // Length Limit Label shown when the user inputs too long of a message + lengthLimitLabel.textColor = .white + lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_MESSAGE_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the media message field.") + lengthLimitLabel.textAlignment = .center + + // Add shadow in case overlayed on white content + lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor + lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) + lengthLimitLabel.layer.shadowOpacity = 0.8 + lengthLimitLabel.isHidden = true + + return lengthLimitLabel + }() // Layout Constants @@ -1437,17 +1488,6 @@ class MediaMessageTextToolbar: UIView, UITextViewDelegate { // Increase hit area of send button sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 8, bottom: 6, right: 8) - // Length Limit Label shown when the user inputs too long of a message - lengthLimitLabel.textColor = .white - lengthLimitLabel.text = NSLocalizedString("ATTACHMENT_APPROVAL_CAPTION_LENGTH_LIMIT_REACHED", comment: "One-line label indicating the user can add no more text to the attachment caption.") - lengthLimitLabel.textAlignment = .center - - // Add shadow in case overlayed on white content - lengthLimitLabel.layer.shadowColor = UIColor.black.cgColor - lengthLimitLabel.layer.shadowOffset = CGSize(width: 0.0, height: 0.0) - lengthLimitLabel.layer.shadowOpacity = 0.8 - self.lengthLimitLabel.isHidden = true - let contentView = UIView() contentView.addSubview(sendButton) contentView.addSubview(textView) From b108f284bdf2b82ed2ed0d8f4881603f32ba1770 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 10:37:47 -0600 Subject: [PATCH 14/22] WIP: hide caption keyboard It's tricky because we're hopping from one first responder to another. Specifically, from the CaptionView.textView, which shows the keyboard, to making the AttachmentApprovalViewController first responder, which shows the BottomToolbar message text field, so in short order, we're getting multiple notifications. User hit's "Done" with caption - Point A - CaptionView is positioned at the top of the keyboard - Hide keyboard (frame change details must be calculated by y offset, since willChanage notification doesn't "shrink" the keyboard frame, it just offsets it to be non-visible. - Point B - caption view is positioned at the bottom of the screen, input accessory view not visible - Show Keyboard (not actually showing the *keyboard* here, but rather the VC's input accessory view) - Point C - caption view is positioned atop the input accessory view We want to animated smoothly from A->C, skipping B. But how do we do that robustly? We could track something like "last known input accessory view height" and never present the captionView below that. But I'm worried it won't be very robust since the input accessory view can change height, e.g. text view grows with text content or dynamic text changes. --- .../AttachmentApprovalViewController.swift | 138 +++++++++++++++++- 1 file changed, 131 insertions(+), 7 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 4c2e6490f..0975eeb40 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -198,12 +198,98 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC self.setCurrentItem(firstItem, direction: .forward, animated: false) - NotificationCenter.default.addObserver(self, - selector: #selector(keyboardWillChangeFrame(notification:)), - name: .UIKeyboardWillChangeFrame, - object: nil) +// NotificationCenter.default.addObserver(self, +// selector: #selector(keyboardWillChangeFrame(notification:)), +// name: .UIKeyboardWillChangeFrame, +// object: nil) + + observers = [ + NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in + guard let strongSelf = self else { return } + + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } + + guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + Logger.debug("UIKeyboardWillShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") + strongSelf.keyboardWillShow(notification: notification) + }, + +// NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] notification in +// guard let userInfo = notification.userInfo else { +// owsFailDebug("userInfo was unexpectedly nil") +// return +// } +// +// guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { +// owsFailDebug("keyboardEndFrame was unexpectedly nil") +// return +// } +// +// guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { +// owsFailDebug("keyboardEndFrame was unexpectedly nil") +// return +// } +// +// Logger.debug("UIKeyboardDidShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") +// }, + + NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in + guard let strongSelf = self else { return } + + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } + + guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + Logger.debug("UIKeyboardWillHide frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") + strongSelf.keyboardWillHide(notification: notification) + } + +// NotificationCenter.default.addObserver(forName: .UIKeyboardDidHide, object: nil, queue: nil) { [weak self] notification in +// guard let userInfo = notification.userInfo else { +// owsFailDebug("userInfo was unexpectedly nil") +// return +// } +// +// guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { +// owsFailDebug("keyboardEndFrame was unexpectedly nil") +// return +// } +// +// guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { +// owsFailDebug("keyboardEndFrame was unexpectedly nil") +// return +// } +// +// Logger.debug("UIKeyboardDidHide frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") +// }, + ] } + var observers: [NSObjectProtocol] = [] + override public func viewWillAppear(_ animated: Bool) { Logger.debug("") super.viewWillAppear(animated) @@ -238,21 +324,59 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC var lastObservedKeyboardHeight: CGFloat = 0 @objc - func keyboardWillChangeFrame(notification: Notification) { - Logger.debug("") + func keyboardWillShow(notification: Notification) { + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } - // NSDictionary *userInfo = [notification userInfo]; + guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") + + lastObservedKeyboardHeight = keyboardEndFrame.size.height + + viewControllers?.forEach { viewController in + guard let prepViewController = viewController as? AttachmentPrepViewController else { + owsFailDebug("unexpected prepViewController: \(viewController)") + return + } + + prepViewController.updateCaptionViewBottomInset() + } + } + + @objc + func keyboardWillHide(notification: Notification) { guard let userInfo = notification.userInfo else { owsFailDebug("userInfo was unexpectedly nil") return } + guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { owsFailDebug("keyboardEndFrame was unexpectedly nil") return } + Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") + lastObservedKeyboardHeight = keyboardEndFrame.size.height + if keyboardStartFrame.height == keyboardEndFrame.height { + lastObservedKeyboardHeight -= keyboardEndFrame.maxY - keyboardStartFrame.maxY + } viewControllers?.forEach { viewController in guard let prepViewController = viewController as? AttachmentPrepViewController else { From 3bfda7ea815b47577892d436a3aac080dd1fa912 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 11:04:56 -0600 Subject: [PATCH 15/22] Smooth kbd dismiss: avoid bouncing CaptionView due to quick transition of firstResponder --- .../AttachmentApprovalViewController.swift | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 0975eeb40..9216a477d 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -322,6 +322,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } var lastObservedKeyboardHeight: CGFloat = 0 + var firstObservedKeyboardHeight: CGFloat? @objc func keyboardWillShow(notification: Notification) { @@ -343,6 +344,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") lastObservedKeyboardHeight = keyboardEndFrame.size.height + if firstObservedKeyboardHeight == nil { + firstObservedKeyboardHeight = keyboardEndFrame.size.height + } viewControllers?.forEach { viewController in guard let prepViewController = viewController as? AttachmentPrepViewController else { @@ -699,6 +703,16 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate // // For these cases we apply the observed `lastKnownBottomToolbar guard bottomToolView.mediaMessageTextToolbar.textView.isFirstResponder else { + // 3. between dismissing the CaptionView and the ViewController regaining first + // responder there is an instant where the lastObservedKeyboardHeight is effectively + // 0. Rather than animate the CaptionView all the way to the bottom screen edge, and + // then immediately back up as the inputAccessoryView becomes visible, we never inset + // the CaptionView nearer to the bottom edge than it's initial position, which should + // be the height of the inputAccessoryView. + if let firstObservedKeyboardHeight = firstObservedKeyboardHeight { + return max(firstObservedKeyboardHeight, lastObservedKeyboardHeight) + } + return lastObservedKeyboardHeight } @@ -956,7 +970,8 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD } let changeBlock = { - self.captionViewBottomConstraint.constant = -prepDelegate.desiredCaptionViewBottomInset + let offset: CGFloat = -1 * prepDelegate.desiredCaptionViewBottomInset + self.captionViewBottomConstraint.constant = offset self.captionView.superview?.layoutIfNeeded() } From 4f1f09f231d4c900ff9b4acc274685ce71365811 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 12:43:04 -0600 Subject: [PATCH 16/22] Use snapshot view to avoid momentary missing bottomToolbar while switching firstResponder from CaptionView to AttachmentApprovalViewController. --- .../AttachmentApprovalViewController.swift | 125 +++++++++++++++--- 1 file changed, 103 insertions(+), 22 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 9216a477d..1754d8db1 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -226,24 +226,27 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC strongSelf.keyboardWillShow(notification: notification) }, -// NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] notification in -// guard let userInfo = notification.userInfo else { -// owsFailDebug("userInfo was unexpectedly nil") -// return -// } -// -// guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { -// owsFailDebug("keyboardEndFrame was unexpectedly nil") -// return -// } -// -// guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { -// owsFailDebug("keyboardEndFrame was unexpectedly nil") -// return -// } -// -// Logger.debug("UIKeyboardDidShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") -// }, + NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] notification in + guard let strongSelf = self else { return } + + guard let userInfo = notification.userInfo else { + owsFailDebug("userInfo was unexpectedly nil") + return + } + + guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { + owsFailDebug("keyboardEndFrame was unexpectedly nil") + return + } + + Logger.debug("UIKeyboardDidShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") + strongSelf.keyboardDidShow(notification: notification) + }, NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in guard let strongSelf = self else { return } @@ -323,9 +326,38 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC var lastObservedKeyboardHeight: CGFloat = 0 var firstObservedKeyboardHeight: CGFloat? + var inputAccessorySnapshotView: UIView? + + @objc + func keyboardDidShow(notification: Notification) { + if self.isFirstResponder { + if self.inputAccessorySnapshotView != nil { + removeToolbarSnapshot() + } else { + Logger.verbose("nothing to remove") + } + } else { + if self.inputAccessorySnapshotView == nil { + // addToolbarSnapshot() + Logger.verbose("no-op") + } else { + Logger.verbose("nothing to show") + } + } + } @objc func keyboardWillShow(notification: Notification) { + if self.isFirstResponder { + Logger.verbose("no-op") + } else { + if self.inputAccessorySnapshotView == nil { +// addToolbarSnapshot() + Logger.verbose("no-op") + } else { + Logger.verbose("nothing to show") + } + } guard let userInfo = notification.userInfo else { owsFailDebug("userInfo was unexpectedly nil") return @@ -680,6 +712,10 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate self.approvalDelegate?.attachmentApproval?(self, changedCaptionOfAttachment: attachmentItem.attachment) } + func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView) { + addToolbarSnapshot() + } + func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { // Disable paging while captions are being edited to avoid a clunky animation. // @@ -690,6 +726,39 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate disablePaging() } + func addToolbarSnapshot() { + assert(inputAccessorySnapshotView == nil) + // To fix a layout glitch where the snapshot view is 1/2 the width of the screen, it's key + // that we use `bottomToolView` and not `inputAccessoryView` which can trigger a layout of + // the `bottomToolView`. + // Presumably the frame of the inputAccessoryView has just changed because we're in the + // middle of switching first responders. We want a snapshot as it *was*, not reflecting any + // just-applied superview layout changes. + inputAccessorySnapshotView = bottomToolView.snapshotView(afterScreenUpdates: true) + guard let inputAccessorySnapshotView = inputAccessorySnapshotView else { + owsFailDebug("inputAccessorySnapshotView was unexpectedly nil") + return + } + + guard let firstObservedKeyboardHeight = firstObservedKeyboardHeight else { + owsFailDebug("firstObservedKeyboardHeight was unexpectedly nil") + return + } + + view.addSubview(inputAccessorySnapshotView) + inputAccessorySnapshotView.autoSetDimension(.height, toSize: firstObservedKeyboardHeight) + inputAccessorySnapshotView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) + } + + func removeToolbarSnapshot() { + guard let inputAccessorySnapshotView = self.inputAccessorySnapshotView else { + owsFailDebug("inputAccessorySnapshotView was unexpectedly nil") + return + } + inputAccessorySnapshotView.removeFromSuperview() + self.inputAccessorySnapshotView = nil + } + func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) { enablePaging() } @@ -778,6 +847,7 @@ extension AttachmentApprovalViewController: GalleryRailViewDelegate { protocol AttachmentPrepViewControllerDelegate: class { func prepViewController(_ prepViewController: AttachmentPrepViewController, didUpdateCaptionForAttachmentItem attachmentItem: SignalAttachmentItem) + func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView) func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) func prepViewController(_ prepViewController: AttachmentPrepViewController, didEndEditingCaptionView captionView: CaptionView) @@ -1147,6 +1217,10 @@ public class AttachmentPrepViewController: OWSViewController, PlayerProgressBarD } extension AttachmentPrepViewController: CaptionViewDelegate { + func captionViewWillBeginEditing(_ captionView: CaptionView) { + prepDelegate?.prepViewController(self, willBeginEditingCaptionView: captionView) + } + func captionView(_ captionView: CaptionView, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) { let attachment = attachmentItem.attachment attachment.captionText = captionText @@ -1158,13 +1232,13 @@ extension AttachmentPrepViewController: CaptionViewDelegate { // This avoids a really ugly animation from simultaneously dismissing the keyboard // while loading a new PrepViewController, and it's CaptionView, whose layout depends // on the keyboard's position. - self.touchInterceptorView.isHidden = false - self.prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) + touchInterceptorView.isHidden = false + prepDelegate?.prepViewController(self, didBeginEditingCaptionView: captionView) } func captionViewDidEndEditing(_ captionView: CaptionView) { - self.touchInterceptorView.isHidden = true - self.prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) + touchInterceptorView.isHidden = true + prepDelegate?.prepViewController(self, didEndEditingCaptionView: captionView) } } @@ -1283,6 +1357,7 @@ class BottomToolView: UIView { protocol CaptionViewDelegate: class { func captionView(_ captionView: CaptionView, didChangeCaptionText captionText: String?, attachmentItem: SignalAttachmentItem) + func captionViewWillBeginEditing(_ captionView: CaptionView) func captionViewDidBeginEditing(_ captionView: CaptionView) func captionViewDidEndEditing(_ captionView: CaptionView) } @@ -1447,6 +1522,12 @@ class CaptionView: UIView { let kMaxCaptionCharacterCount = 240 extension CaptionView: UITextViewDelegate { + + public func textViewShouldBeginEditing(_ textView: UITextView) -> Bool { + delegate?.captionViewWillBeginEditing(self) + return true + } + public func textViewDidBeginEditing(_ textView: UITextView) { updatePlaceholderTextViewVisibility() doneButton.isHidden = false From 0808458392108387f464b0e4cc97ce5a71df46cd Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 12:47:20 -0600 Subject: [PATCH 17/22] fix caption dismiss animation/placeholder for multiline message body --- .../AttachmentApprovalViewController.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 1754d8db1..829143bae 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -740,13 +740,8 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate return } - guard let firstObservedKeyboardHeight = firstObservedKeyboardHeight else { - owsFailDebug("firstObservedKeyboardHeight was unexpectedly nil") - return - } - view.addSubview(inputAccessorySnapshotView) - inputAccessorySnapshotView.autoSetDimension(.height, toSize: firstObservedKeyboardHeight) + inputAccessorySnapshotView.autoSetDimension(.height, toSize: bottomToolView.bounds.height) inputAccessorySnapshotView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .top) } @@ -779,7 +774,7 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate // the CaptionView nearer to the bottom edge than it's initial position, which should // be the height of the inputAccessoryView. if let firstObservedKeyboardHeight = firstObservedKeyboardHeight { - return max(firstObservedKeyboardHeight, lastObservedKeyboardHeight) + return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) } return lastObservedKeyboardHeight From 279694e704871a2e74f92f8779196f2b0b3cba8b Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 13:55:39 -0600 Subject: [PATCH 18/22] keyboard animation cleanup --- .../AttachmentApprovalViewController.swift | 155 +++--------------- 1 file changed, 23 insertions(+), 132 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 829143bae..35ed84236 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -198,101 +198,20 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC self.setCurrentItem(firstItem, direction: .forward, animated: false) -// NotificationCenter.default.addObserver(self, -// selector: #selector(keyboardWillChangeFrame(notification:)), -// name: .UIKeyboardWillChangeFrame, -// object: nil) - - observers = [ - NotificationCenter.default.addObserver(forName: .UIKeyboardWillShow, object: nil, queue: nil) { [weak self] notification in - guard let strongSelf = self else { return } - - guard let userInfo = notification.userInfo else { - owsFailDebug("userInfo was unexpectedly nil") - return - } - - guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - Logger.debug("UIKeyboardWillShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") - strongSelf.keyboardWillShow(notification: notification) - }, - - NotificationCenter.default.addObserver(forName: .UIKeyboardDidShow, object: nil, queue: nil) { [weak self] notification in - guard let strongSelf = self else { return } - - guard let userInfo = notification.userInfo else { - owsFailDebug("userInfo was unexpectedly nil") - return - } - - guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - Logger.debug("UIKeyboardDidShow frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") - strongSelf.keyboardDidShow(notification: notification) - }, - - NotificationCenter.default.addObserver(forName: .UIKeyboardWillHide, object: nil, queue: nil) { [weak self] notification in - guard let strongSelf = self else { return } - - guard let userInfo = notification.userInfo else { - owsFailDebug("userInfo was unexpectedly nil") - return - } - - guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { - owsFailDebug("keyboardEndFrame was unexpectedly nil") - return - } - - Logger.debug("UIKeyboardWillHide frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") - strongSelf.keyboardWillHide(notification: notification) - } - -// NotificationCenter.default.addObserver(forName: .UIKeyboardDidHide, object: nil, queue: nil) { [weak self] notification in -// guard let userInfo = notification.userInfo else { -// owsFailDebug("userInfo was unexpectedly nil") -// return -// } -// -// guard let keyboardStartFrame = userInfo[UIKeyboardFrameBeginUserInfoKey] as? CGRect else { -// owsFailDebug("keyboardEndFrame was unexpectedly nil") -// return -// } -// -// guard let keyboardEndFrame = userInfo[UIKeyboardFrameEndUserInfoKey] as? CGRect else { -// owsFailDebug("keyboardEndFrame was unexpectedly nil") -// return -// } -// -// Logger.debug("UIKeyboardDidHide frame: \(keyboardStartFrame) -> \(keyboardEndFrame)") -// }, - ] + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillShow(notification:)), + name: .UIKeyboardWillShow, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardDidShow(notification:)), + name: .UIKeyboardDidShow, + object: nil) + NotificationCenter.default.addObserver(self, + selector: #selector(keyboardWillHide(notification:)), + name: .UIKeyboardWillHide, + object: nil) } - var observers: [NSObjectProtocol] = [] - override public func viewWillAppear(_ animated: Bool) { Logger.debug("") super.viewWillAppear(animated) @@ -325,39 +244,20 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } var lastObservedKeyboardHeight: CGFloat = 0 - var firstObservedKeyboardHeight: CGFloat? var inputAccessorySnapshotView: UIView? @objc func keyboardDidShow(notification: Notification) { - if self.isFirstResponder { - if self.inputAccessorySnapshotView != nil { - removeToolbarSnapshot() - } else { - Logger.verbose("nothing to remove") - } - } else { - if self.inputAccessorySnapshotView == nil { - // addToolbarSnapshot() - Logger.verbose("no-op") - } else { - Logger.verbose("nothing to show") - } + // If this is a result of the vc becoming first responder, they keyboard isn't actually + // showing, rather the inputAccessoryView is now showing, so we want to remove any + // previously added toolbar snapshot. + if isFirstResponder, inputAccessorySnapshotView != nil { + removeToolbarSnapshot() } } @objc func keyboardWillShow(notification: Notification) { - if self.isFirstResponder { - Logger.verbose("no-op") - } else { - if self.inputAccessorySnapshotView == nil { -// addToolbarSnapshot() - Logger.verbose("no-op") - } else { - Logger.verbose("nothing to show") - } - } guard let userInfo = notification.userInfo else { owsFailDebug("userInfo was unexpectedly nil") return @@ -374,11 +274,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") - lastObservedKeyboardHeight = keyboardEndFrame.size.height - if firstObservedKeyboardHeight == nil { - firstObservedKeyboardHeight = keyboardEndFrame.size.height - } viewControllers?.forEach { viewController in guard let prepViewController = viewController as? AttachmentPrepViewController else { @@ -713,7 +609,7 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate } func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView) { - addToolbarSnapshot() + addInputAccessorySnapshot() } func prepViewController(_ prepViewController: AttachmentPrepViewController, didBeginEditingCaptionView captionView: CaptionView) { @@ -726,7 +622,7 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate disablePaging() } - func addToolbarSnapshot() { + func addInputAccessorySnapshot() { assert(inputAccessorySnapshotView == nil) // To fix a layout glitch where the snapshot view is 1/2 the width of the screen, it's key // that we use `bottomToolView` and not `inputAccessoryView` which can trigger a layout of @@ -771,18 +667,13 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate // responder there is an instant where the lastObservedKeyboardHeight is effectively // 0. Rather than animate the CaptionView all the way to the bottom screen edge, and // then immediately back up as the inputAccessoryView becomes visible, we never inset - // the CaptionView nearer to the bottom edge than it's initial position, which should - // be the height of the inputAccessoryView. - if let firstObservedKeyboardHeight = firstObservedKeyboardHeight { - return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) - } - - return lastObservedKeyboardHeight + // the CaptionView nearer to the bottom edge than the `bottomToolView.height` + return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) } - // 3. when the MessageTextView becomes first responder, the keyboard should shift up + // 4. when the MessageTextView becomes first responder, the keyboard should shift up // "in front" of the CaptionView - return 0 + return bottomToolView.bounds.height } // MARK: Helpers From 55807f9a4ddfa600c1730188f74ac1fe68d11116 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 14:15:35 -0600 Subject: [PATCH 19/22] iPhoneX compatible keyboard animations --- .../AttachmentApprovalViewController.swift | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 35ed84236..307a81981 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -306,9 +306,7 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") lastObservedKeyboardHeight = keyboardEndFrame.size.height - if keyboardStartFrame.height == keyboardEndFrame.height { - lastObservedKeyboardHeight -= keyboardEndFrame.maxY - keyboardStartFrame.maxY - } + lastObservedKeyboardHeight -= keyboardEndFrame.maxY - keyboardStartFrame.maxY viewControllers?.forEach { viewController in guard let prepViewController = viewController as? AttachmentPrepViewController else { @@ -655,6 +653,14 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate } var desiredCaptionViewBottomInset: CGFloat { + + let safeAreaInset: CGFloat + if #available(iOS 11, *) { + safeAreaInset = view.safeAreaInsets.bottom + } else { + safeAreaInset = 0 + } + // CaptionView bottom offset scenarios: // // 1. when no keyboard is popped (e.g. initially) to be *just* above the rail @@ -668,12 +674,12 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate // 0. Rather than animate the CaptionView all the way to the bottom screen edge, and // then immediately back up as the inputAccessoryView becomes visible, we never inset // the CaptionView nearer to the bottom edge than the `bottomToolView.height` - return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) + return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) - safeAreaInset } // 4. when the MessageTextView becomes first responder, the keyboard should shift up // "in front" of the CaptionView - return bottomToolView.bounds.height + return bottomToolView.bounds.height - safeAreaInset } // MARK: Helpers From 0562619ca9f5be85bd9fcd2f8f7a3fafe246ef65 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 14:18:52 -0600 Subject: [PATCH 20/22] smaller margins between rail images, avoid choppy change as the margin updates are not being animated smoothly. --- SignalMessaging/Views/GalleryRailView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SignalMessaging/Views/GalleryRailView.swift b/SignalMessaging/Views/GalleryRailView.swift index 5274f4aff..96bc1f0a6 100644 --- a/SignalMessaging/Views/GalleryRailView.swift +++ b/SignalMessaging/Views/GalleryRailView.swift @@ -66,7 +66,7 @@ public class GalleryRailCellView: UIView { func setIsSelected(_ isSelected: Bool) { self.isSelected = isSelected if isSelected { - layoutMargins = UIEdgeInsets(top: 0, left: 6, bottom: 0, right: 6) + layoutMargins = UIEdgeInsets(top: 0, left: 2, bottom: 0, right: 2) imageView.layer.borderColor = Theme.galleryHighlightColor.cgColor imageView.layer.borderWidth = 2 imageView.layer.cornerRadius = 2 From e3120a5b876e3b916e632f12b88e05efd70790ba Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Sat, 24 Nov 2018 14:31:00 -0600 Subject: [PATCH 21/22] cleanup keyboard animation code --- .../AttachmentApprovalViewController.swift | 49 ++++++++----------- 1 file changed, 21 insertions(+), 28 deletions(-) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 307a81981..96cd95411 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -243,12 +243,12 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC return true } - var lastObservedKeyboardHeight: CGFloat = 0 + var lastObservedKeyboardTop: CGFloat = 0 var inputAccessorySnapshotView: UIView? @objc func keyboardDidShow(notification: Notification) { - // If this is a result of the vc becoming first responder, they keyboard isn't actually + // If this is a result of the vc becoming first responder, the keyboard isn't actually // showing, rather the inputAccessoryView is now showing, so we want to remove any // previously added toolbar snapshot. if isFirstResponder, inputAccessorySnapshotView != nil { @@ -274,16 +274,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC } Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") - lastObservedKeyboardHeight = keyboardEndFrame.size.height + lastObservedKeyboardTop = keyboardEndFrame.size.height - viewControllers?.forEach { viewController in - guard let prepViewController = viewController as? AttachmentPrepViewController else { - owsFailDebug("unexpected prepViewController: \(viewController)") - return - } - - prepViewController.updateCaptionViewBottomInset() - } + currentPageController.updateCaptionViewBottomInset() } @objc @@ -305,17 +298,9 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC Logger.debug("\(keyboardStartFrame) -> \(keyboardEndFrame)") - lastObservedKeyboardHeight = keyboardEndFrame.size.height - lastObservedKeyboardHeight -= keyboardEndFrame.maxY - keyboardStartFrame.maxY + lastObservedKeyboardTop = keyboardEndFrame.size.height + keyboardStartFrame.minY - keyboardEndFrame.minY - viewControllers?.forEach { viewController in - guard let prepViewController = viewController as? AttachmentPrepViewController else { - owsFailDebug("unexpected prepViewController: \(viewController)") - return - } - - prepViewController.updateCaptionViewBottomInset() - } + currentPageController.updateCaptionViewBottomInset() } // MARK: - View Helpers @@ -607,6 +592,9 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate } func prepViewController(_ prepViewController: AttachmentPrepViewController, willBeginEditingCaptionView captionView: CaptionView) { + // When the CaptionView becomes first responder, the AttachmentApprovalViewController will + // consequently resignFirstResponder, which means the bottomToolView would disappear from + // the screen, so before that happens, we add a snapshot to holds it's place. addInputAccessorySnapshot() } @@ -667,14 +655,19 @@ extension AttachmentApprovalViewController: AttachmentPrepViewControllerDelegate // 2. when the CaptionView becomes first responder, to be *just* above the keyboard, so the // user can see what they're typing. // - // For these cases we apply the observed `lastKnownBottomToolbar + // For both these cases we apply the `lastObservedKeyboardTop` guard bottomToolView.mediaMessageTextToolbar.textView.isFirstResponder else { - // 3. between dismissing the CaptionView and the ViewController regaining first - // responder there is an instant where the lastObservedKeyboardHeight is effectively - // 0. Rather than animate the CaptionView all the way to the bottom screen edge, and - // then immediately back up as the inputAccessoryView becomes visible, we never inset - // the CaptionView nearer to the bottom edge than the `bottomToolView.height` - return max(bottomToolView.bounds.height, lastObservedKeyboardHeight) - safeAreaInset + // 3. Immediately after dismissing the CaptionView but before the ViewController + // regains firstResponder, there is an instant where the inputAccessoryView is + // not shown, so the lastObservedKeyboardTop is effectively 0. A moment later + // when the ViewController regains firstResponder, the inputAccessoryView will be + // presented. Naively, this would result in the CaptionView undesirably bouncing to + // the bottom of the ViewController, and then immediately back up as the + // inputAccessoryView is presented. + // Instead, we position the CaptionView where it will end up, by using + // `bottomToolView.height`, which will only be greater than + // `lastObserveredKeyboardTop` when the keyboard is not presented. + return max(bottomToolView.bounds.height, lastObservedKeyboardTop) - safeAreaInset } // 4. when the MessageTextView becomes first responder, the keyboard should shift up From 9317ee9c99c10fb96375c7cccb42dabc93692a34 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 26 Nov 2018 12:57:50 -0700 Subject: [PATCH 22/22] design comment --- .../AttachmentApprovalViewController.swift | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift index 96cd95411..5552cb0c7 100644 --- a/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift +++ b/SignalMessaging/ViewControllers/AttachmentApprovalViewController.swift @@ -198,6 +198,36 @@ public class AttachmentApprovalViewController: UIPageViewController, UIPageViewC self.setCurrentItem(firstItem, direction: .forward, animated: false) + // As a refresher, the _Information Architecture_ here is: + // + // You are approving an "Album", which has multiple "Attachments" + // + // The "media message text" and the "media rail" belong to the Album as a whole, whereas + // each caption belongs to the individual Attachment. + // + // The _UI Architecture_ reflects this hierarchy by putting the MediaRail and + // MediaMessageText input into the bottomToolView which is then the AttachmentApprovalView's + // inputAccessoryView. + // + // Whereas a CaptionView lives in each page of the PageViewController, per Attachment. + // + // So as you page, the CaptionViews move out of view with its page, whereas the input + // accessory view (rail/media message text) will remain fixed in the viewport. + // + // However (and here's the kicker), at rest, the media's CaptionView rests just above the + // input accessory view. So when things are static, they appear as a single piece of + // interface. + // + // I'm not totally sure if this is what Myles had in mind, but the screenshots left a lot of + // behavior ambiguous, and this was my best interpretation. + // + // Because of this complexity, it is insufficient to observe only the + // KeyboardWillChangeFrame, since the keyboard could be changing frame when the CaptionView + // became/resigned first responder, when AttachmentApprovalViewController became/resigned + // first responder, or when the AttachmentApprovalView's inputAccessoryView.textView + // became/resigned first responder, and because these things can happen in immediatre + // sequence, getting a single smooth animation requires handling each notification slightly + // differently. NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: .UIKeyboardWillShow,