From 97603e64cc8966f9468d3c95eb11567651610dfd Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 5 Mar 2019 10:06:39 -0800 Subject: [PATCH 1/6] Deconflict "bottom view" layout and keyboard animations. --- .../ViewControllers/OWSViewController.m | 41 ++++++++++++++++--- 1 file changed, 36 insertions(+), 5 deletions(-) diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index b4d8a06ec..4019173e7 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -23,6 +23,7 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) @property (nonatomic, weak) UIView *bottomLayoutView; @property (nonatomic) NSLayoutConstraint *bottomLayoutConstraint; +@property (nonatomic) BOOL shouldAnimatedBottomLayout; @end @@ -64,6 +65,22 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) return self; } +#pragma mark - View Lifecycle + +- (void)viewDidAppear:(BOOL)animated +{ + [super viewDidAppear:animated]; + + self.shouldAnimatedBottomLayout = YES; +} + +- (void)viewDidDisappear:(BOOL)animated +{ + [super viewDidDisappear:animated]; + + self.shouldAnimatedBottomLayout = NO; +} + - (void)viewDidLoad { [super viewDidLoad]; @@ -73,6 +90,8 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) } } +#pragma mark - + - (void)autoPinViewToBottomOfViewControllerOrKeyboard:(UIView *)view avoidNotch:(BOOL)avoidNotch { OWSAssertDebug(view); @@ -185,11 +204,23 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) // bar. CGFloat offset = -MAX(0, (self.view.height - self.bottomLayoutGuide.length - keyboardEndFrameConverted.origin.y)); - // There's no need to use: [UIView animateWithDuration:...]. - // Any layout changes made during these notifications are - // automatically animated. - self.bottomLayoutConstraint.constant = offset; - [self.bottomLayoutView.superview layoutIfNeeded]; + // UIKit by default animates all changes in response to keyboard events. + // We want to suppress those animations if the view isn't visible, + // otherwise presentation animations don't work properly. + dispatch_block_t updateLayout = ^{ + // There's no need to use: [UIView animateWithDuration:...]. + // Any layout changes made during these notifications are + // automatically animated. + self.bottomLayoutConstraint.constant = offset; + [self.bottomLayoutView.superview layoutIfNeeded]; + }; + + + if (self.shouldAnimatedBottomLayout) { + updateLayout(); + } else { + [UIView performWithoutAnimation:updateLayout]; + } } #pragma mark - Orientation From 53802d1a48bef63a05dbdfdd21e064087ff71276 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 5 Mar 2019 10:24:22 -0800 Subject: [PATCH 2/6] Deconflict "bottom view" layout and keyboard animations. --- SignalMessaging/ViewControllers/OWSViewController.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index 4019173e7..31350bd91 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -23,7 +23,7 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) @property (nonatomic, weak) UIView *bottomLayoutView; @property (nonatomic) NSLayoutConstraint *bottomLayoutConstraint; -@property (nonatomic) BOOL shouldAnimatedBottomLayout; +@property (nonatomic) BOOL shouldAnimateBottomLayout; @end @@ -71,14 +71,14 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) { [super viewDidAppear:animated]; - self.shouldAnimatedBottomLayout = YES; + self.shouldAnimateBottomLayout = YES; } - (void)viewDidDisappear:(BOOL)animated { [super viewDidDisappear:animated]; - self.shouldAnimatedBottomLayout = NO; + self.shouldAnimateBottomLayout = NO; } - (void)viewDidLoad @@ -216,7 +216,7 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) }; - if (self.shouldAnimatedBottomLayout) { + if (self.shouldAnimateBottomLayout) { updateLayout(); } else { [UIView performWithoutAnimation:updateLayout]; From d72c26796dba87438d72b980e3c67bb8c90f5c61 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 5 Mar 2019 10:31:20 -0800 Subject: [PATCH 3/6] Ensure onboarding views never reclaim layout space from dismissed keyboard. --- .../Registration/OnboardingBaseViewController.swift | 6 ++++++ SignalMessaging/ViewControllers/OWSViewController.h | 4 ++++ SignalMessaging/ViewControllers/OWSViewController.m | 6 +++++- 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift b/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift index a1594d3ec..6d59d0f9e 100644 --- a/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift +++ b/Signal/src/ViewControllers/Registration/OnboardingBaseViewController.swift @@ -73,6 +73,12 @@ public class OnboardingBaseViewController: OWSViewController { // MARK: - View Lifecycle + public override func viewDidLoad() { + super.viewDidLoad() + + self.shouldBottomViewReserveSpaceForKeyboard = true + } + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) diff --git a/SignalMessaging/ViewControllers/OWSViewController.h b/SignalMessaging/ViewControllers/OWSViewController.h index 6e0ed2c3c..71cb28ad2 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.h +++ b/SignalMessaging/ViewControllers/OWSViewController.h @@ -20,6 +20,10 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void); // BUT adjust its location upward if the keyboard appears. - (void)autoPinViewToBottomOfViewControllerOrKeyboard:(UIView *)view avoidNotch:(BOOL)avoidNotch; +// If YES, the bottom view never "reclaims" layout space if the keyboard is dismissed. +// Defaults to NO. +@property (nonatomic) BOOL shouldBottomViewReserveSpaceForKeyboard; + @end NS_ASSUME_NONNULL_END diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index 31350bd91..5d756ee94 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -211,7 +211,11 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) // There's no need to use: [UIView animateWithDuration:...]. // Any layout changes made during these notifications are // automatically animated. - self.bottomLayoutConstraint.constant = offset; + if (self.shouldBottomViewReserveSpaceForKeyboard) { + self.bottomLayoutConstraint.constant = MIN(self.bottomLayoutConstraint.constant, offset); + } else { + self.bottomLayoutConstraint.constant = offset; + } [self.bottomLayoutView.superview layoutIfNeeded]; }; From 6e7c135348118c67a547b085639c451531407d9c Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 5 Mar 2019 10:32:32 -0800 Subject: [PATCH 4/6] Ensure onboarding views never reclaim layout space from dismissed keyboard. --- SignalMessaging/ViewControllers/OWSViewController.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index 5d756ee94..22e5bdc15 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -208,9 +208,6 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) // We want to suppress those animations if the view isn't visible, // otherwise presentation animations don't work properly. dispatch_block_t updateLayout = ^{ - // There's no need to use: [UIView animateWithDuration:...]. - // Any layout changes made during these notifications are - // automatically animated. if (self.shouldBottomViewReserveSpaceForKeyboard) { self.bottomLayoutConstraint.constant = MIN(self.bottomLayoutConstraint.constant, offset); } else { From 6fe3ce6d875a779a7ce82976a9f12e07d5fbf365 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 8 Mar 2019 10:33:36 -0500 Subject: [PATCH 5/6] Deconflict "bottom view" layout and keyboard animations. --- .../ConversationViewController.m | 18 +++++++++++++++--- .../ViewControllers/OWSViewController.m | 13 ++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 2cdb2cf1d..23391d999 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -214,6 +214,7 @@ typedef enum : NSUInteger { @property (nonatomic) BOOL isViewCompletelyAppeared; @property (nonatomic) BOOL isViewVisible; @property (nonatomic) BOOL shouldObserveVMUpdates; +@property (nonatomic) BOOL shouldAnimateKeyboardChanges; @property (nonatomic) BOOL viewHasEverAppeared; @property (nonatomic) BOOL hasUnreadMessages; @property (nonatomic) BOOL isPickingMediaAsDocument; @@ -1230,6 +1231,7 @@ typedef enum : NSUInteger { self.isViewCompletelyAppeared = YES; self.viewHasEverAppeared = YES; + self.shouldAnimateKeyboardChanges = YES; // HACK: Because the inputToolbar is the inputAccessoryView, we make some special considertations WRT it's firstResponder status. // @@ -1291,6 +1293,7 @@ typedef enum : NSUInteger { [super viewDidDisappear:animated]; self.userHasScrolled = NO; self.isViewVisible = NO; + self.shouldAnimateKeyboardChanges = NO; [self.audioAttachmentPlayer stop]; self.audioAttachmentPlayer = nil; @@ -3720,12 +3723,21 @@ typedef enum : NSUInteger { return; } CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue]; + CGRect keyboardEndFrameConverted = [self.view convertRect:keyboardEndFrame fromView:nil]; UIEdgeInsets oldInsets = self.collectionView.contentInset; UIEdgeInsets newInsets = oldInsets; - // bottomLayoutGuide accounts for extra offset needed on iPhoneX - newInsets.bottom = keyboardEndFrame.size.height - self.bottomLayoutGuide.length; + // Use a content inset that so that the conversation content + // is not hidden behind the keyboard + input accessory. + // + // Make sure to leave space for the bottom layout guide (the notch). + // + // Always reserve room for the input accessory, which we display even + // if the keyboard is not active. + newInsets.bottom = MAX(0, + MAX(self.view.height - self.bottomLayoutGuide.length - keyboardEndFrameConverted.origin.y, + self.inputToolbar.height)); BOOL wasScrolledToBottom = [self isScrolledToBottom]; @@ -3774,7 +3786,7 @@ typedef enum : NSUInteger { } }; - if (self.isViewCompletelyAppeared) { + if (self.shouldAnimateKeyboardChanges && CurrentAppContext().isAppForegroundAndActive) { adjustInsets(); } else { // Even though we are scrolling without explicitly animating, the notification seems to occur within the context diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index 22e5bdc15..daca2d68f 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -208,16 +208,19 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) // We want to suppress those animations if the view isn't visible, // otherwise presentation animations don't work properly. dispatch_block_t updateLayout = ^{ - if (self.shouldBottomViewReserveSpaceForKeyboard) { - self.bottomLayoutConstraint.constant = MIN(self.bottomLayoutConstraint.constant, offset); - } else { - self.bottomLayoutConstraint.constant = offset; + if (self.shouldBottomViewReserveSpaceForKeyboard && offset >= 0) { + // To avoid unnecessary animations / layout jitter, + // some views never reclaim layout space when the keyboard is dismissed. + // + // They _do_ need to relayout if the user switches keyboards. + return; } + self.bottomLayoutConstraint.constant = offset; [self.bottomLayoutView.superview layoutIfNeeded]; }; - if (self.shouldAnimateBottomLayout) { + if (self.shouldAnimateBottomLayout && CurrentAppContext().isAppForegroundAndActive) { updateLayout(); } else { [UIView performWithoutAnimation:updateLayout]; From ff08919206d11d6193fdce4036df83c0a9dab3d2 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Mon, 11 Mar 2019 23:25:13 -0400 Subject: [PATCH 6/6] Respond to CR. --- SignalMessaging/ViewControllers/OWSViewController.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/SignalMessaging/ViewControllers/OWSViewController.m b/SignalMessaging/ViewControllers/OWSViewController.m index daca2d68f..53d87d88f 100644 --- a/SignalMessaging/ViewControllers/OWSViewController.m +++ b/SignalMessaging/ViewControllers/OWSViewController.m @@ -204,9 +204,6 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) // bar. CGFloat offset = -MAX(0, (self.view.height - self.bottomLayoutGuide.length - keyboardEndFrameConverted.origin.y)); - // UIKit by default animates all changes in response to keyboard events. - // We want to suppress those animations if the view isn't visible, - // otherwise presentation animations don't work properly. dispatch_block_t updateLayout = ^{ if (self.shouldBottomViewReserveSpaceForKeyboard && offset >= 0) { // To avoid unnecessary animations / layout jitter, @@ -223,6 +220,9 @@ UIInterfaceOrientationMask DefaultUIInterfaceOrientationMask(void) if (self.shouldAnimateBottomLayout && CurrentAppContext().isAppForegroundAndActive) { updateLayout(); } else { + // UIKit by default animates all changes in response to keyboard events. + // We want to suppress those animations if the view isn't visible, + // otherwise presentation animations don't work properly. [UIView performWithoutAnimation:updateLayout]; } }