From ebf576a4c04b027f1a7d195b4bd3c145483ec738 Mon Sep 17 00:00:00 2001 From: Nora Trapp Date: Fri, 9 Aug 2019 23:06:36 -0700 Subject: [PATCH] Fix some attachment keyboard layout issues --- .../AttachmentFormatPickerView.swift | 24 +++++---------- .../AttachmentKeyboard.swift | 22 ++++++++------ .../RecentPhotoCollectionView.swift | 29 +++++-------------- .../ConversationInputToolbar.m | 18 +++++++----- .../ConversationViewController.m | 8 +---- SignalMessaging/Views/CustomKeyboard.swift | 16 +++++++--- SignalMessaging/categories/UIView+OWS.swift | 4 +++ 7 files changed, 57 insertions(+), 64 deletions(-) diff --git a/Signal/src/ViewControllers/Attachment Keyboard/AttachmentFormatPickerView.swift b/Signal/src/ViewControllers/Attachment Keyboard/AttachmentFormatPickerView.swift index 5d43258d03..f18d9283b6 100644 --- a/Signal/src/ViewControllers/Attachment Keyboard/AttachmentFormatPickerView.swift +++ b/Signal/src/ViewControllers/Attachment Keyboard/AttachmentFormatPickerView.swift @@ -15,20 +15,16 @@ protocol AttachmentFormatPickerDelegate: class { class AttachmentFormatPickerView: UICollectionView { weak var attachmentFormatPickerDelegate: AttachmentFormatPickerDelegate? - private var itemSize: CGSize { - return CGSize(width: frame.height, height: frame.height) + var itemSize: CGSize = .zero { + didSet { + guard oldValue != itemSize else { return } + updateLayout() + } } private let collectionViewFlowLayout = UICollectionViewFlowLayout() private var photoCapture: PhotoCapture? - override var bounds: CGRect { - didSet { - guard oldValue != bounds else { return } - updateLayout() - } - } - init() { super.init(frame: .zero, collectionViewLayout: collectionViewFlowLayout) @@ -68,19 +64,15 @@ class AttachmentFormatPickerView: UICollectionView { photoCapture = nil } - func orientationDidChange() { - updateLayout() - } - private func updateLayout() { AssertIsOnMainThread() - guard frame.height > 0 else { return } + guard itemSize.height > 0, itemSize.width > 0 else { return } - // The items should always expand to fit the height of the collection view. - // We'll always just have one row of items. collectionViewFlowLayout.itemSize = itemSize collectionViewFlowLayout.invalidateLayout() + + reloadData() } required init?(coder aDecoder: NSCoder) { diff --git a/Signal/src/ViewControllers/Attachment Keyboard/AttachmentKeyboard.swift b/Signal/src/ViewControllers/Attachment Keyboard/AttachmentKeyboard.swift index 96c16f4308..5e93e46c46 100644 --- a/Signal/src/ViewControllers/Attachment Keyboard/AttachmentKeyboard.swift +++ b/Signal/src/ViewControllers/Attachment Keyboard/AttachmentKeyboard.swift @@ -144,7 +144,8 @@ class AttachmentKeyboard: CustomKeyboard { override func wasPresented() { super.wasPresented() - checkPermissionsAndLayout() + checkPermissions() + updateItemSizes() } override func wasDismissed() { @@ -156,22 +157,25 @@ class AttachmentKeyboard: CustomKeyboard { override func orientationDidChange() { super.orientationDidChange() - recentPhotosCollectionView.orientationDidChange() - attachmentFormatPickerView.orientationDidChange() + updateItemSizes() } - func checkPermissionsAndLayout() { + func updateItemSizes() { + // The items should always expand to fit the height of their collection view. + // We'll always just have one row of items. + recentPhotosCollectionView.itemSize = CGSize(square: recentPhotosCollectionView.height()) + attachmentFormatPickerView.itemSize = CGSize(square: attachmentFormatPickerView.height()) + } + + func checkPermissions() { switch mediaLibraryAuthorizationStatus { case .authorized: showRecentPhotos() case .denied, .restricted: showRecentPhotosError() case .notDetermined: - PHPhotoLibrary.requestAuthorization { status in - DispatchQueue.main.async { - if status == .authorized { self.recentPhotosCollectionView.permissionChanged() } - self.checkPermissionsAndLayout() - } + PHPhotoLibrary.requestAuthorization { _ in + DispatchQueue.main.async { self.checkPermissions() } } @unknown default: showRecentPhotosError() diff --git a/Signal/src/ViewControllers/Attachment Keyboard/RecentPhotoCollectionView.swift b/Signal/src/ViewControllers/Attachment Keyboard/RecentPhotoCollectionView.swift index 76f363a649..4f969184da 100644 --- a/Signal/src/ViewControllers/Attachment Keyboard/RecentPhotoCollectionView.swift +++ b/Signal/src/ViewControllers/Attachment Keyboard/RecentPhotoCollectionView.swift @@ -45,8 +45,11 @@ class RecentPhotosCollectionView: UICollectionView { private lazy var collection = photoLibrary.defaultPhotoCollection() private lazy var collectionContents = collection.contents(ascending: false, limit: maxRecentPhotos) - private var itemSize: CGSize { - return CGSize(width: frame.height, height: frame.height) + var itemSize: CGSize = .zero { + didSet { + guard oldValue != itemSize else { return } + updateLayout() + } } private var photoMediaSize: PhotoMediaSize { @@ -57,13 +60,6 @@ class RecentPhotosCollectionView: UICollectionView { private let collectionViewFlowLayout = UICollectionViewFlowLayout() - override var bounds: CGRect { - didSet { - guard oldValue != bounds else { return } - updateLayout() - } - } - init() { super.init(frame: .zero, collectionViewLayout: collectionViewFlowLayout) @@ -83,26 +79,17 @@ class RecentPhotosCollectionView: UICollectionView { updateLayout() } - func permissionChanged() { - guard isReadyForPhotoLibraryAccess else { return } - updateLayout() - } - - func orientationDidChange() { - updateLayout() - } - private func updateLayout() { AssertIsOnMainThread() // We don't want to do anything until media library permission is granted. guard isReadyForPhotoLibraryAccess else { return } - guard frame.height > 0 else { return } + guard itemSize.height > 0, itemSize.width > 0 else { return } - // The items should always expand to fit the height of the collection view. - // We'll always just have one row of items. collectionViewFlowLayout.itemSize = itemSize collectionViewFlowLayout.invalidateLayout() + + reloadData() } required init?(coder aDecoder: NSCoder) { diff --git a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m index 3676472fc8..02c50c2f45 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationInputToolbar.m @@ -102,6 +102,7 @@ const CGFloat kMaxTextViewHeight = 98; @property (nonatomic) KeyboardType desiredKeyboardType; @property (nonatomic, readonly) StickerKeyboard *stickerKeyboard; @property (nonatomic, readonly) AttachmentKeyboard *attachmentKeyboard; +@property (nonatomic) BOOL isMeasuringKeyboardHeight; @end @@ -1469,14 +1470,16 @@ const CGFloat kMaxTextViewHeight = 98; // We disable animations so this preload is invisible to the // user. if (!self.inputTextView.isFirstResponder) { + + // Flag that we're measuring the system keyboard's height, so + // even if though it won't be the first responder by the time + // the notifications fire, we'll still read its measurement + self.isMeasuringKeyboardHeight = YES; + [UIView setAnimationsEnabled:NO]; [self.inputTextView becomeFirstResponder]; - - // Next run loop - dispatch_async(dispatch_get_main_queue(), ^{ - [self.inputTextView resignFirstResponder]; - [UIView setAnimationsEnabled:YES]; - }); + [self.inputTextView resignFirstResponder]; + [UIView setAnimationsEnabled:YES]; } } @@ -1517,12 +1520,13 @@ const CGFloat kMaxTextViewHeight = 98; } CGRect keyboardEndFrame = [keyboardEndFrameValue CGRectValue]; - if (self.inputTextView.isFirstResponder) { + if (self.inputTextView.isFirstResponder || self.isMeasuringKeyboardHeight) { // The returned keyboard height includes the input view, so subtract our height. CGFloat newHeight = keyboardEndFrame.size.height - self.frame.size.height; if (newHeight > 0) { [self.stickerKeyboard updateSystemKeyboardHeight:newHeight]; [self.attachmentKeyboard updateSystemKeyboardHeight:newHeight]; + self.isMeasuringKeyboardHeight = NO; } } } diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index 17c77f019e..d4ed71fcea 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -1338,13 +1338,7 @@ typedef enum : NSUInteger { - (void)updateHeaderViewFrame { - // TODO: iOS 13 – The titleView no longer respects the headerView's intrinsicContentSize - // and always tries to center the headerView. See if this changes in later betas. - BOOL iOS11and12 = NO; - if (@available(iOS 13, *)) iOS11and12 = NO; - else if (@available(iOS 11, *)) iOS11and12 = YES; - - if (iOS11and12) { + if (@available(iOS 11, *)) { // Do nothing, we use autolayout/intrinsic content size to grow } else { // Request "full width" title; the navigation bar will truncate this diff --git a/SignalMessaging/Views/CustomKeyboard.swift b/SignalMessaging/Views/CustomKeyboard.swift index 319ac156a5..e8bfe9e008 100644 --- a/SignalMessaging/Views/CustomKeyboard.swift +++ b/SignalMessaging/Views/CustomKeyboard.swift @@ -50,10 +50,15 @@ open class CustomKeyboard: UIInputView { } open override func didMoveToSuperview() { - if superview == nil { - wasDismissed() - } else { - wasPresented() + // Call wasPresented/wasDismissed on the next run loop, + // once this view hierarchy change has finished. + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + if self.superview == nil { + self.wasDismissed() + } else { + self.wasPresented() + } } } @@ -81,6 +86,9 @@ open class CustomKeyboard: UIInputView { @objc public func updateSystemKeyboardHeight(_ height: CGFloat) { + // Only respect this height if it's reasonable, we don't want + // to have a tiny keyboard. + guard height > 100 else { return } cachedSystemKeyboardHeight.current = height resizeToSystemKeyboard() } diff --git a/SignalMessaging/categories/UIView+OWS.swift b/SignalMessaging/categories/UIView+OWS.swift index e8f0378699..63c016c9cb 100644 --- a/SignalMessaging/categories/UIView+OWS.swift +++ b/SignalMessaging/categories/UIView+OWS.swift @@ -301,6 +301,10 @@ public extension CGSize { var ceil: CGSize { return CGSizeCeil(self) } + + init(square: CGFloat) { + self.init(width: square, height: square) + } } // MARK: -