From 77bd9b8852304ffbd58cb513a82cbc4a86206694 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Wed, 31 Oct 2018 18:12:56 -0600 Subject: [PATCH] Extract most Gallery functionality from the gallery NavigationController. By not overriding the initializer for an OWSNavigationController subclass, we can use the dynamic disaptch intialization chain. The root difficulty here is that super.init(navBarClass:) wants to call self.init(nibNam) --- .../ConversationViewController.m | 20 +- .../MediaGalleryViewController.swift | 187 +++++++++--------- .../MessageDetailViewController.swift | 12 +- .../OWSConversationSettingsViewController.m | 15 +- .../ViewControllers/OWSNavigationController.h | 3 - 5 files changed, 119 insertions(+), 118 deletions(-) diff --git a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m index e2a037011..d313c83a8 100644 --- a/Signal/src/ViewControllers/ConversationView/ConversationViewController.m +++ b/Signal/src/ViewControllers/ConversationView/ConversationViewController.m @@ -2051,12 +2051,12 @@ typedef enum : NSUInteger { } TSMessage *mediaMessage = (TSMessage *)viewItem.interaction; - MediaGalleryViewController *vc = [[MediaGalleryViewController alloc] - initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; + MediaGallery *mediaGallery = + [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; - [vc presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; + [mediaGallery presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; } - (void)didTapVideoViewItem:(id)viewItem @@ -2080,12 +2080,12 @@ typedef enum : NSUInteger { } TSMessage *mediaMessage = (TSMessage *)viewItem.interaction; - MediaGalleryViewController *vc = [[MediaGalleryViewController alloc] - initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; + MediaGallery *mediaGallery = + [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled | MediaGalleryOptionShowAllMediaButton]; - [vc presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; + [mediaGallery presentDetailViewFromViewController:self mediaMessage:mediaMessage replacingView:imageView]; } - (void)didTapAudioViewItem:(id)viewItem attachmentStream:(TSAttachmentStream *)attachmentStream diff --git a/Signal/src/ViewControllers/MediaGalleryViewController.swift b/Signal/src/ViewControllers/MediaGalleryViewController.swift index 6b2b75136..a222b7ec1 100644 --- a/Signal/src/ViewControllers/MediaGalleryViewController.swift +++ b/Signal/src/ViewControllers/MediaGalleryViewController.swift @@ -168,44 +168,7 @@ protocol MediaGalleryDataSourceDelegate: class { func mediaGalleryDataSource(_ mediaGalleryDataSource: MediaGalleryDataSource, deletedSections: IndexSet, deletedItems: [IndexPath]) } -@objc -class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSource, MediaTileViewControllerDelegate { - - private var pageViewController: MediaPageViewController? - - private let uiDatabaseConnection: YapDatabaseConnection - private let editingDatabaseConnection: YapDatabaseConnection - private let mediaGalleryFinder: OWSMediaGalleryFinder - - private var initialDetailItem: MediaGalleryItem? - private let thread: TSThread - private let options: MediaGalleryOption - - // we start with a small range size for quick loading. - private let fetchRangeSize: UInt = 10 - - deinit { - Logger.debug("deinit") - } - - @objc - init(thread: TSThread, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption = []) { - self.thread = thread - assert(uiDatabaseConnection.isInLongLivedReadTransaction()) - self.uiDatabaseConnection = uiDatabaseConnection - - self.editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection() - - self.options = options - self.mediaGalleryFinder = OWSMediaGalleryFinder(thread: thread) - super.init(nibName: nil, bundle: nil) - self.setValue(OWSNavigationBar(), forKey: "navigationBar") - super.setupNavbar() - } - - required init?(coder aDecoder: NSCoder) { - notImplemented() - } +class MediaGalleryNavigationController: OWSNavigationController { // MARK: View LifeCycle @@ -215,6 +178,9 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc UIViewController.attemptRotationToDeviceOrientation() } + var presentationView: UIImageView! + var retainUntilDismissed: MediaGallery? + // HACK: Though we don't have an input accessory view, the VC we are presented above (ConversationVC) does. // If the app is backgrounded and then foregrounded, when OWSWindowManager calls mainWindow.makeKeyAndVisible // the ConversationVC's inputAccessoryView will appear *above* us unless we'd previously become first responder. @@ -256,6 +222,52 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc presentationView.contentMode = .scaleAspectFit } + // MARK: Orientation + + public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return .allButUpsideDown + } +} + +@objc +class MediaGallery: NSObject, MediaGalleryDataSource, MediaTileViewControllerDelegate { + + @objc + weak public var navigationController: MediaGalleryNavigationController! + + private var pageViewController: MediaPageViewController? + + private let uiDatabaseConnection: YapDatabaseConnection + private let editingDatabaseConnection: YapDatabaseConnection + private let mediaGalleryFinder: OWSMediaGalleryFinder + + private var initialDetailItem: MediaGalleryItem? + private let thread: TSThread + private let options: MediaGalleryOption + + // we start with a small range size for quick loading. + private let fetchRangeSize: UInt = 10 + + deinit { + Logger.debug("") + } + + @objc + init(thread: TSThread, uiDatabaseConnection: YapDatabaseConnection, options: MediaGalleryOption = []) { + self.thread = thread + assert(uiDatabaseConnection.isInLongLivedReadTransaction()) + self.uiDatabaseConnection = uiDatabaseConnection + + self.editingDatabaseConnection = OWSPrimaryStorage.shared().newDatabaseConnection() + + self.options = options + self.mediaGalleryFinder = OWSMediaGalleryFinder(thread: thread) + let navController = MediaGalleryNavigationController() + self.navigationController = navController + super.init() + navController.retainUntilDismissed = self + } + // MARK: Present/Dismiss private var currentItem: MediaGalleryItem { @@ -263,7 +275,6 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } private var replacingView: UIView? - private var presentationView: UIImageView! private var presentationViewConstraints: [NSLayoutConstraint] = [] // TODO rename to replacingOriginRect @@ -294,7 +305,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.addDataSourceDelegate(pageViewController) self.pageViewController = pageViewController - self.setViewControllers([pageViewController], animated: false) + navigationController.setViewControllers([pageViewController], animated: false) self.replacingView = replacingView @@ -302,13 +313,13 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.originRect = convertedRect // loadView hasn't necessarily been called yet. - self.loadViewIfNeeded() + navigationController.loadViewIfNeeded() - self.presentationView.image = initialDetailItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in + navigationController.presentationView.image = initialDetailItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in guard let strongSelf = self else { return } - strongSelf.presentationView.image = image + strongSelf.navigationController.presentationView.image = image }, failure: { Logger.warn("Could not load presentation image.") }) @@ -316,8 +327,8 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc self.applyInitialMediaViewConstraints() // Restore presentationView.alpha in case a previous dismiss left us in a bad state. - self.setNavigationBarHidden(false, animated: false) - self.presentationView.alpha = 1 + navigationController.setNavigationBarHidden(false, animated: false) + navigationController.presentationView.alpha = 1 // We want to animate the tapped media from it's position in the previous VC // to it's resting place in the center of this view controller. @@ -330,7 +341,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // 2. For Video views, the AVPlayerLayer content does not scale with the presentation animation. So you instead get a full scale // video, wherein only the cropping is animated. // Using a simple image view allows us to address both these problems relatively easily. - self.view.alpha = 0.0 + navigationController.view.alpha = 0.0 guard let detailView = pageViewController.view else { owsFailDebug("detailView was unexpectedly nil") @@ -339,23 +350,23 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // At this point our media view should be overlayed perfectly // by our presentationView. Swapping them out should be imperceptible. - self.presentationView.isHidden = false + navigationController.presentationView.isHidden = false // We don't hide the pageViewController entirely - e.g. we want the toolbars to fade in. pageViewController.currentViewController.view.isHidden = true detailView.backgroundColor = .clear - self.view.backgroundColor = .clear + navigationController.view.backgroundColor = .clear - self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small + navigationController.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small - fromViewController.present(self, animated: false) { + fromViewController.present(navigationController, animated: false) { // 1. Fade in the entire view. UIView.animate(withDuration: 0.1) { self.replacingView?.alpha = 0.0 - self.view.alpha = 1.0 + self.navigationController.view.alpha = 1.0 } - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.superview?.layoutIfNeeded() self.applyFinalMediaViewConstraints() // 2. Animate imageView from it's initial position, which should match where it was @@ -366,20 +377,20 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc options: .curveEaseOut, animations: { - self.presentationView.layer.cornerRadius = 0 - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.layer.cornerRadius = 0 + self.navigationController.presentationView.superview?.layoutIfNeeded() // fade out content behind the pageViewController // and behind the presentation view - self.view.backgroundColor = Theme.backgroundColor + self.navigationController.view.backgroundColor = Theme.backgroundColor }, completion: { (_: Bool) in // At this point our presentation view should be overlayed perfectly // with our media view. Swapping them out should be imperceptible. pageViewController.currentViewController.view.isHidden = false - self.presentationView.isHidden = true + self.navigationController.presentationView.isHidden = true - self.view.isUserInteractionEnabled = true + self.navigationController.view.isUserInteractionEnabled = true pageViewController.wasPresented() @@ -390,7 +401,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc // // We don't need to do this when pushing VCs onto the SignalsNavigationController - only when // presenting directly from ConversationVC. - _ = self.becomeFirstResponder() + _ = self.navigationController.becomeFirstResponder() }) } } @@ -426,7 +437,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } else { // If from conversation view mediaTileViewController.focusedItem = focusedItem - self.pushViewController(mediaTileViewController, animated: true) + navigationController.pushViewController(mediaTileViewController, animated: true) } } @@ -459,7 +470,7 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc guard let pageViewController = self.pageViewController else { owsFailDebug("pageViewController was unexpectedly nil") - self.dismiss(animated: true) + self.navigationController.dismiss(animated: true) return } @@ -468,32 +479,32 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc pageViewController.willBePresentedAgain() // TODO fancy zoom animation - self.popViewController(animated: true) + self.navigationController.popViewController(animated: true) } } public func dismissMediaDetailViewController(_ mediaPageViewController: MediaPageViewController, animated isAnimated: Bool, completion: (() -> Void)?) { - self.view.isUserInteractionEnabled = false + navigationController.view.isUserInteractionEnabled = false UIApplication.shared.isStatusBarHidden = false guard let detailView = mediaPageViewController.view else { owsFailDebug("detailView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } mediaPageViewController.currentViewController.view.isHidden = true - self.presentationView.isHidden = false + navigationController.presentationView.isHidden = false // Move the presentationView back to it's initial position, i.e. where // it sits on the screen in the conversation view. let changedItems = currentItem != self.initialDetailItem if changedItems { - self.presentationView.image = currentItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in + navigationController.presentationView.image = currentItem.attachmentStream.thumbnailImageLarge(success: { [weak self] (image) in guard let strongSelf = self else { return } - strongSelf.presentationView.image = image + strongSelf.navigationController.presentationView.image = image }, failure: { Logger.warn("Could not load presentation image.") }) @@ -508,14 +519,14 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc options: .curveEaseOut, animations: { // Move back over it's original location - self.presentationView.superview?.layoutIfNeeded() + self.navigationController.presentationView.superview?.layoutIfNeeded() detailView.alpha = 0 if changedItems { - self.presentationView.alpha = 0 + self.navigationController.presentationView.alpha = 0 } else { - self.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small + self.navigationController.presentationView.layer.cornerRadius = kOWSMessageCellCornerRadius_Small } }, completion: { (_: Bool) in @@ -532,24 +543,24 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc animations: { guard let replacingView = self.replacingView else { owsFailDebug("replacingView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } // fade out content and toolbars - self.view.alpha = 0.0 + self.navigationController.view.alpha = 0.0 replacingView.alpha = 1.0 }, completion: { (_: Bool) in - self.presentingViewController?.dismiss(animated: false, completion: completion) + self.navigationController.presentingViewController?.dismiss(animated: false, completion: completion) }) } else { guard let replacingView = self.replacingView else { owsFailDebug("replacingView was unexpectedly nil") - self.presentingViewController?.dismiss(animated: false, completion: completion) + navigationController.presentingViewController?.dismiss(animated: false, completion: completion) return } replacingView.alpha = 1.0 - self.presentingViewController?.dismiss(animated: false, completion: completion) + navigationController.presentingViewController?.dismiss(animated: false, completion: completion) } } @@ -564,17 +575,17 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc return } - guard let presentationSuperview = self.presentationView.superview else { + guard let presentationSuperview = navigationController.presentationView.superview else { owsFailDebug("presentationView.superview was unexpectedly nil") return } let convertedRect: CGRect = presentationSuperview.convert(originRect, from: UIApplication.shared.keyWindow) - self.presentationViewConstraints += self.presentationView.autoSetDimensions(to: convertedRect.size) + self.presentationViewConstraints += navigationController.presentationView.autoSetDimensions(to: convertedRect.size) self.presentationViewConstraints += [ - self.presentationView.autoPinEdge(toSuperviewEdge: .top, withInset: convertedRect.origin.y), - self.presentationView.autoPinEdge(toSuperviewEdge: .left, withInset: convertedRect.origin.x) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top, withInset: convertedRect.origin.y), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .left, withInset: convertedRect.origin.x) ] } @@ -585,10 +596,10 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } self.presentationViewConstraints = [ - self.presentationView.autoPinEdge(toSuperviewEdge: .leading), - self.presentationView.autoPinEdge(toSuperviewEdge: .top), - self.presentationView.autoPinEdge(toSuperviewEdge: .trailing), - self.presentationView.autoPinEdge(toSuperviewEdge: .bottom) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .top), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .bottom) ] } @@ -599,9 +610,9 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } self.presentationViewConstraints += [ - self.presentationView.autoPinEdge(toSuperviewEdge: .leading), - self.presentationView.autoPinEdge(toSuperviewEdge: .trailing), - self.presentationView.autoPinEdge(.top, to: .bottom, of: self.view) + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .leading), + navigationController.presentationView.autoPinEdge(toSuperviewEdge: .trailing), + navigationController.presentationView.autoPinEdge(.top, to: .bottom, of: self.navigationController.view) ] } @@ -918,10 +929,4 @@ class MediaGalleryViewController: OWSNavigationController, MediaGalleryDataSourc } return Int(count) - deletedMessages.count } - - // MARK: Orientation - - public override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - return .allButUpsideDown - } } diff --git a/Signal/src/ViewControllers/MessageDetailViewController.swift b/Signal/src/ViewControllers/MessageDetailViewController.swift index 88b233142..005f9a426 100644 --- a/Signal/src/ViewControllers/MessageDetailViewController.swift +++ b/Signal/src/ViewControllers/MessageDetailViewController.swift @@ -615,17 +615,17 @@ class MessageDetailViewController: OWSViewController, MediaGalleryDataSourceDele // MARK: OWSMessageBubbleViewDelegate func didTapImageViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { - let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) - mediaGalleryViewController.addDataSourceDelegate(self) - mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + mediaGallery.addDataSourceDelegate(self) + mediaGallery.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) } func didTapVideoViewItem(_ viewItem: ConversationViewItem, attachmentStream: TSAttachmentStream, imageView: UIView) { - let mediaGalleryViewController = MediaGalleryViewController(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) + let mediaGallery = MediaGallery(thread: self.thread, uiDatabaseConnection: self.uiDatabaseConnection) - mediaGalleryViewController.addDataSourceDelegate(self) - mediaGalleryViewController.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) + mediaGallery.addDataSourceDelegate(self) + mediaGallery.presentDetailView(fromViewController: self, mediaMessage: self.message, replacingView: imageView) } func didTapContactShare(_ viewItem: ConversationViewItem) { diff --git a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m index 4135b7d4d..aa16a1fa9 100644 --- a/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/ThreadSettings/OWSConversationSettingsViewController.m @@ -54,7 +54,7 @@ const CGFloat kIconViewLength = 24; @property (nonatomic) NSArray *disappearingMessagesDurations; @property (nonatomic) OWSDisappearingMessagesConfiguration *disappearingMessagesConfiguration; -@property (nullable, nonatomic) MediaGalleryViewController *mediaGalleryViewController; +@property (nullable, nonatomic) MediaGalleryNavigationController *mediaGalleryViewController; @property (nonatomic, readonly) TSAccountManager *accountManager; @property (nonatomic, readonly) OWSContactsManager *contactsManager; @property (nonatomic, readonly) OWSMessageSender *messageSender; @@ -1273,19 +1273,18 @@ const CGFloat kIconViewLength = 24; - (void)showMediaGallery { - OWSLogDebug(@"in showMediaGallery"); + OWSLogDebug(@""); - MediaGalleryViewController *vc = - [[MediaGalleryViewController alloc] initWithThread:self.thread - uiDatabaseConnection:self.uiDatabaseConnection - options:MediaGalleryOptionSliderEnabled]; + MediaGallery *mediaGallery = [[MediaGallery alloc] initWithThread:self.thread + uiDatabaseConnection:self.uiDatabaseConnection + options:MediaGalleryOptionSliderEnabled]; // although we don't present the mediaGalleryViewController directly, we need to maintain a strong // reference to it until we're dismissed. - self.mediaGalleryViewController = vc; + self.mediaGalleryViewController = mediaGallery.navigationController; OWSAssertDebug([self.navigationController isKindOfClass:[OWSNavigationController class]]); - [vc pushTileViewFromNavController:(OWSNavigationController *)self.navigationController]; + [mediaGallery pushTileViewFromNavController:(OWSNavigationController *)self.navigationController]; } #pragma mark - Notifications diff --git a/SignalMessaging/ViewControllers/OWSNavigationController.h b/SignalMessaging/ViewControllers/OWSNavigationController.h index ebd38e06c..533a5b4d0 100644 --- a/SignalMessaging/ViewControllers/OWSNavigationController.h +++ b/SignalMessaging/ViewControllers/OWSNavigationController.h @@ -23,9 +23,6 @@ NS_ASSUME_NONNULL_BEGIN // unsaved changes. @interface OWSNavigationController : UINavigationController -- (instancetype)init; -- (void)setupNavbar; - @end NS_ASSUME_NONNULL_END