From 2af8c608d6757a21cf4e13050db479189ce00279 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Tue, 14 Jun 2016 22:09:53 +0100 Subject: [PATCH 1/3] First attempt at swipe down to dismiss --- Memories.xcodeproj/project.pbxproj | 4 + Memories/Grid View/GridViewCell.swift | 5 + Memories/Photo View/PhotoViewController.swift | 60 ++++++++-- .../PhotoViewDismissTransition.swift | 1 - .../PhotoViewSwipeDismissTransition.swift | 105 ++++++++++++++++++ 5 files changed, 165 insertions(+), 10 deletions(-) create mode 100644 Memories/Transitions/PhotoViewSwipeDismissTransition.swift diff --git a/Memories.xcodeproj/project.pbxproj b/Memories.xcodeproj/project.pbxproj index 30b48f4..6be0a3e 100644 --- a/Memories.xcodeproj/project.pbxproj +++ b/Memories.xcodeproj/project.pbxproj @@ -26,6 +26,7 @@ 4C8594CD1BB33D4C005CCFAC /* DACircularProgress.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8594C91BB33D02005CCFAC /* DACircularProgress.framework */; }; 4C8594D11BB33E27005CCFAC /* Cartography.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C8594D01BB33E27005CCFAC /* Cartography.framework.dSYM */; }; 4C8594D31BB33E2F005CCFAC /* DACircularProgress.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */; }; + 4C8774E21D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */; }; 4C91432C1BA958CC001B30A9 /* notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4C91432B1BA958CC001B30A9 /* notification.mp3 */; }; 4C921FD21C1A038800F0411D /* PhotoViewPresentTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */; }; 4C989BDF1BA7806E00D4C13F /* Dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */; }; @@ -129,6 +130,7 @@ 4C8594C91BB33D02005CCFAC /* DACircularProgress.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DACircularProgress.framework; path = Carthage/Build/iOS/DACircularProgress.framework; sourceTree = ""; }; 4C8594D01BB33E27005CCFAC /* Cartography.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Cartography.framework.dSYM; path = Carthage/Build/iOS/Cartography.framework.dSYM; sourceTree = ""; }; 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = DACircularProgress.framework.dSYM; path = Carthage/Build/iOS/DACircularProgress.framework.dSYM; sourceTree = ""; }; + 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoViewSwipeDismissTransition.swift; sourceTree = ""; }; 4C91432B1BA958CC001B30A9 /* notification.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notification.mp3; sourceTree = ""; }; 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoViewPresentTransition.swift; sourceTree = ""; }; 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dynamic.swift; sourceTree = ""; }; @@ -267,6 +269,7 @@ children = ( 4C00A4F41C1B7ED30078A43F /* PhotoViewDismissTransition.swift */, 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */, + 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */, ); path = Transitions; sourceTree = ""; @@ -558,6 +561,7 @@ 4C6F48EF1B69850F00CF7E67 /* DismissSegue.swift in Sources */, 4C6299AD1B4CB0AD00B8795E /* PhotoViewController.swift in Sources */, 4CB674B51BA1EBC7001F401A /* SettingsViewController.swift in Sources */, + 4C8774E21D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift in Sources */, 4C989BE11BA780BB00D4C13F /* SettingsViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Memories/Grid View/GridViewCell.swift b/Memories/Grid View/GridViewCell.swift index 9b629c1..7a2d0df 100644 --- a/Memories/Grid View/GridViewCell.swift +++ b/Memories/Grid View/GridViewCell.swift @@ -19,4 +19,9 @@ class GridViewCell: UICollectionViewCell { return imageView?.image } } + + override func prepareForReuse() { + super.prepareForReuse() + imageView?.image = nil + } } diff --git a/Memories/Photo View/PhotoViewController.swift b/Memories/Photo View/PhotoViewController.swift index 76e451a..28b515e 100644 --- a/Memories/Photo View/PhotoViewController.swift +++ b/Memories/Photo View/PhotoViewController.swift @@ -36,6 +36,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl var presentTransition: PhotoViewPresentTransition? var dismissTransition: PhotoViewDismissTransition? + var swipeDismissTransition: PhotoViewSwipeDismissTransition? required init?(coder aDecoder: NSCoder) { self.imageManager = PHCachingImageManager() @@ -64,12 +65,23 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl initialPage = model.selectedIndex imageManager.startCachingImagesForAssets(model.assets, targetSize: cacheSize, contentMode: .AspectFill, options: nil) PHPhotoLibrary.sharedPhotoLibrary().registerChangeObserver(self); + + let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(PhotoViewController.viewDidPan)) + view.addGestureRecognizer(panRecognizer) } override func viewDidAppear(animated: Bool) { hideStatusBar(true) } + override func viewDidDisappear(animated: Bool) { + NSLog("PhotoViewController.viewDidDisappear") + + PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) + self.cancelAllImageRequests() + self.purgeAllViews() +} + override func viewDidLayoutSubviews() { setupViews() } @@ -138,24 +150,46 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl }, completionHandler: nil) } - @IBAction func close(sender: UIButton) { + func doClose(interactive: Bool) { if let navController = presentingViewController as? UINavigationController, gridViewController = navController.topViewController as? GridViewController { - cancelAllImageRequests() - PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) - gridViewController.setSelectedIndex(model.selectedIndex) + let imageView = gridViewController.imageViewForIndex(model.selectedIndex)! + let pageView = pageViews[model.selectedIndex]! + if traitCollection.verticalSizeClass == .Regular { hideStatusBar(false) } - let imageView = gridViewController.imageViewForIndex(model.selectedIndex) - let pageView = pageViews[model.selectedIndex] - dismissTransition = PhotoViewDismissTransition(destImageView: imageView!, sourceImageView: pageView!.imageView) - - navController.dismissViewControllerAnimated(true) { self.purgeAllViews() } + + if interactive { + swipeDismissTransition = PhotoViewSwipeDismissTransition(destImageView: imageView, sourceImageView: pageView.imageView) + } + else { + dismissTransition = PhotoViewDismissTransition(destImageView: imageView, sourceImageView: pageView.imageView) + swipeDismissTransition = nil + } + + navController.dismissViewControllerAnimated(true) { + NSLog("dismissViewControllerAnimated completion block") + } } } + @IBAction func close(sender: UIButton) { + doClose(false) + } + + func viewDidPan(gr: UIPanGestureRecognizer) { + switch gr.state { + case .Began: + doClose(true) + default: + break + } + + swipeDismissTransition?.handlePan(panRecognizer: gr) + } + // MARK: Internal implementation func setupViews() { let pageCount = model.assets.count @@ -385,9 +419,17 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + if swipeDismissTransition != nil { + return swipeDismissTransition + } + return dismissTransition } + func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { + return swipeDismissTransition + } + // MARK: PHPhotoLibraryChangeObserver func photoLibraryDidChange(changeInstance: PHChange) { diff --git a/Memories/Transitions/PhotoViewDismissTransition.swift b/Memories/Transitions/PhotoViewDismissTransition.swift index eff7836..c010d93 100644 --- a/Memories/Transitions/PhotoViewDismissTransition.swift +++ b/Memories/Transitions/PhotoViewDismissTransition.swift @@ -7,7 +7,6 @@ // import UIKit -import AVFoundation class PhotoViewDismissTransition: NSObject, UIViewControllerAnimatedTransitioning { diff --git a/Memories/Transitions/PhotoViewSwipeDismissTransition.swift b/Memories/Transitions/PhotoViewSwipeDismissTransition.swift new file mode 100644 index 0000000..0cc4a7d --- /dev/null +++ b/Memories/Transitions/PhotoViewSwipeDismissTransition.swift @@ -0,0 +1,105 @@ + +// PhotoViewSwipeDismissTransition.swift +// Memories +// +// Created by Michael Brown on 11/06/2016. +// Copyright © 2016 Michael Brown. All rights reserved. +// + +import UIKit + +class PhotoViewSwipeDismissTransition: + UIPercentDrivenInteractiveTransition, + UIViewControllerAnimatedTransitioning { + let destImageView: UIImageView + let sourceImageView: UIImageView + let transitionDuration = NSTimeInterval(0.25) + + var panHeight = CGFloat(0) + + init(destImageView: UIImageView, sourceImageView: UIImageView) { + self.destImageView = destImageView + self.sourceImageView = sourceImageView + } + + // MARK: UIViewControllerAnimatedTransitioning + func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { + return transitionDuration + } + + func animateTransition(transitionContext: UIViewControllerContextTransitioning) { + guard let container = transitionContext.containerView(), + fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), + fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else { + transitionContext.completeTransition(false) + return + } + + let transitionView = UIView(frame: transitionContext.initialFrameForViewController(fromViewController)) + transitionView.backgroundColor = UIColor.blackColor() + container.insertSubview(transitionView, belowSubview: fromView) + + let startImageFrame = CGRectIntegral(transitionView.convertRect(self.sourceImageView.bounds, fromView: self.sourceImageView)) + let imageView = UIImageView(frame: startImageFrame) + imageView.image = destImageView.image + imageView.contentMode = container.thumbnailContentMode + imageView.clipsToBounds = true + transitionView.addSubview(imageView) + + let fromBackgroundColor = fromView.backgroundColor + fromView.backgroundColor = UIColor.clearColor() + self.destImageView.hidden = true + self.sourceImageView.hidden = true + + let newImageFrame = CGRectIntegral(transitionView.convertRect(self.destImageView.bounds, fromView: self.destImageView)) + + UIView.animateKeyframesWithDuration(transitionDuration, delay: 0, options: .CalculationModeLinear, animations: { + UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.25) { + fromView.alpha = 0.0 + } + + UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1) { + imageView.frame = newImageFrame + transitionView.backgroundColor = UIColor.clearColor() + } + }) { finished in + self.destImageView.hidden = false + + let cancelled = transitionContext.transitionWasCancelled() + + if cancelled { + fromView.backgroundColor = fromBackgroundColor + self.destImageView.hidden = false + self.sourceImageView.hidden = false + } + else { + fromView.removeFromSuperview() + } + + transitionView.removeFromSuperview() + transitionContext.completeTransition(!cancelled) + } + } + + // MARK: gesture handling + func handlePan(panRecognizer gr: UIPanGestureRecognizer) { + switch gr.state { + case .Began: + let startPoint = gr.locationInView(gr.view) + panHeight = gr.view!.bounds.height - startPoint.y + case .Changed: + let percent = gr.translationInView(gr.view).y / panHeight + updateInteractiveTransition(percent <= 0 ? 0 : percent) + case .Ended, .Cancelled: + let velocity = gr.velocityInView(gr.view) + if velocity.y < 0 || gr.state == .Cancelled { + cancelInteractiveTransition() + } + else { + finishInteractiveTransition() + } + default: + break + } + } +} From 9f4172a6fe54a7c61fcd02e859c45c3748cb3ad2 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Fri, 29 Jul 2016 23:39:43 +0100 Subject: [PATCH 2/3] Merged interactive swipe to dismiss changes from swift-3-migration branch. Had to do manual merge because, well, swift 3 :/ --- Cartfile.resolved | 4 +- Memories.xcodeproj/project.pbxproj | 10 +- Memories/Grid View/GridViewController.swift | 34 ++++- Memories/Photo View/PhotoViewController.swift | 124 ++++++++++++------ Memories/Photo View/ZoomingPhotoView.swift | 2 +- .../PhotoViewDismissTransition.swift | 10 +- .../PhotoViewPresentTransition.swift | 3 + .../PhotoViewSwipeDismissTransition.swift | 105 --------------- .../Utilities/StatusBarViewController.swift | 28 ++++ 9 files changed, 159 insertions(+), 161 deletions(-) delete mode 100644 Memories/Transitions/PhotoViewSwipeDismissTransition.swift create mode 100644 Memories/Utilities/StatusBarViewController.swift diff --git a/Cartfile.resolved b/Cartfile.resolved index 1b943ea..9e771a5 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ -github "robb/Cartography" "c73aa3bdcdf366a7648571e53075be8e8e10392b" +github "robb/Cartography" "114a314ce43ff68255c16a70e4c53824c9ecd539" github "mluisbrown/DACircularProgress" "5b06959ce05b1332a09771915a1c2ea25c5554a9" -github "mluisbrown/RMStore" "9f5a865a1fb3d1a01259fd1d14a5b7cd6698117c" +github "mluisbrown/RMStore" "2da2208c64c734258ecb0fa263e3d25e9bce68b2" diff --git a/Memories.xcodeproj/project.pbxproj b/Memories.xcodeproj/project.pbxproj index 6be0a3e..5cc0f72 100644 --- a/Memories.xcodeproj/project.pbxproj +++ b/Memories.xcodeproj/project.pbxproj @@ -26,9 +26,9 @@ 4C8594CD1BB33D4C005CCFAC /* DACircularProgress.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C8594C91BB33D02005CCFAC /* DACircularProgress.framework */; }; 4C8594D11BB33E27005CCFAC /* Cartography.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C8594D01BB33E27005CCFAC /* Cartography.framework.dSYM */; }; 4C8594D31BB33E2F005CCFAC /* DACircularProgress.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */; }; - 4C8774E21D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */; }; 4C91432C1BA958CC001B30A9 /* notification.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 4C91432B1BA958CC001B30A9 /* notification.mp3 */; }; 4C921FD21C1A038800F0411D /* PhotoViewPresentTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */; }; + 4C92A9381D4C088E00B3FAAD /* StatusBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C92A9371D4C088E00B3FAAD /* StatusBarViewController.swift */; }; 4C989BDF1BA7806E00D4C13F /* Dynamic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */; }; 4C989BE11BA780BB00D4C13F /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C989BE01BA780BB00D4C13F /* SettingsViewModel.swift */; }; 4CAAF0751B827EAD00AE47C3 /* PullView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4CAAF0741B827EAD00AE47C3 /* PullView.swift */; }; @@ -130,9 +130,9 @@ 4C8594C91BB33D02005CCFAC /* DACircularProgress.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DACircularProgress.framework; path = Carthage/Build/iOS/DACircularProgress.framework; sourceTree = ""; }; 4C8594D01BB33E27005CCFAC /* Cartography.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = Cartography.framework.dSYM; path = Carthage/Build/iOS/Cartography.framework.dSYM; sourceTree = ""; }; 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = DACircularProgress.framework.dSYM; path = Carthage/Build/iOS/DACircularProgress.framework.dSYM; sourceTree = ""; }; - 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoViewSwipeDismissTransition.swift; sourceTree = ""; }; 4C91432B1BA958CC001B30A9 /* notification.mp3 */ = {isa = PBXFileReference; lastKnownFileType = audio.mp3; path = notification.mp3; sourceTree = ""; }; 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PhotoViewPresentTransition.swift; sourceTree = ""; }; + 4C92A9371D4C088E00B3FAAD /* StatusBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarViewController.swift; sourceTree = ""; }; 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dynamic.swift; sourceTree = ""; }; 4C989BE01BA780BB00D4C13F /* SettingsViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; 4CAAF0741B827EAD00AE47C3 /* PullView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PullView.swift; sourceTree = ""; }; @@ -211,6 +211,7 @@ 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */, 4C0410E51BC8787200BBB76C /* UIDevice.swift */, 4CABEE211CAAF08500A9F23A /* UITraitEnvironment.swift */, + 4C92A9371D4C088E00B3FAAD /* StatusBarViewController.swift */, ); path = Utilities; sourceTree = ""; @@ -267,9 +268,8 @@ 4C641B901C3DB30C004B15D7 /* Transitions */ = { isa = PBXGroup; children = ( - 4C00A4F41C1B7ED30078A43F /* PhotoViewDismissTransition.swift */, 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */, - 4C8774E11D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift */, + 4C00A4F41C1B7ED30078A43F /* PhotoViewDismissTransition.swift */, ); path = Transitions; sourceTree = ""; @@ -555,13 +555,13 @@ 4CEAA9101B33462600774FE8 /* GridViewCell.swift in Sources */, 4CBA89671BBC926100913336 /* UpgradeManager.swift in Sources */, 4CF4105C1C4BB732002FD49A /* DateUtils.swift in Sources */, + 4C92A9381D4C088E00B3FAAD /* StatusBarViewController.swift in Sources */, 4CEAA90E1B3343F700774FE8 /* GridViewController.swift in Sources */, 4C00A4F51C1B7ED30078A43F /* PhotoViewDismissTransition.swift in Sources */, 4CE8BD971C03BBE500207BE1 /* DatePickerViewController.swift in Sources */, 4C6F48EF1B69850F00CF7E67 /* DismissSegue.swift in Sources */, 4C6299AD1B4CB0AD00B8795E /* PhotoViewController.swift in Sources */, 4CB674B51BA1EBC7001F401A /* SettingsViewController.swift in Sources */, - 4C8774E21D0C4D4500F523DC /* PhotoViewSwipeDismissTransition.swift in Sources */, 4C989BE11BA780BB00D4C13F /* SettingsViewModel.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Memories/Grid View/GridViewController.swift b/Memories/Grid View/GridViewController.swift index 86e9863..915aba5 100644 --- a/Memories/Grid View/GridViewController.swift +++ b/Memories/Grid View/GridViewController.swift @@ -33,7 +33,13 @@ extension UICollectionView { } } -class GridViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, PHPhotoLibraryChangeObserver, UIPopoverPresentationControllerDelegate { +class GridViewController: UICollectionViewController, + UICollectionViewDelegateFlowLayout, + PHPhotoLibraryChangeObserver, + UIPopoverPresentationControllerDelegate, + StatusBarViewController, + PhotoViewControllerDelegate +{ let reuseIdentifier = "PhotoCell" let headerIdentifier = "YearHeader" // If the size is too large then PhotoKit doesn't return an optimal image size @@ -43,6 +49,7 @@ class GridViewController: UICollectionViewController, UICollectionViewDelegateFl var model : GridViewModel! var titleView : UILabel! + var statusBarVisible = true var imageManager : PHCachingImageManager! var previousPreheatRect : CGRect = .zero @@ -179,10 +186,22 @@ class GridViewController: UICollectionViewController, UICollectionViewDelegateFl }, completion: nil) } + override func prefersStatusBarHidden() -> Bool { + return !statusBarVisible || traitCollection.verticalSizeClass == .Compact + } + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } + // MARK: StatusBarViewController + func hideStatusBar(hide: Bool) { + statusBarVisible = !hide + UIView.animateWithDuration(0.25) { + self.setNeedsStatusBarAppearanceUpdate() + } + } + // MARK: - Notification handlers func appDidBecomeActive() { if let date = NotificationManager.launchDate() where self.photosAllowed { @@ -216,6 +235,18 @@ class GridViewController: UICollectionViewController, UICollectionViewDelegateFl } } + // MARK: - PhotoViewContollerDelegate + func setSelected(index index: Int) { + collectionView?.selectItemAtIndexPath(model.indexPathForSelectedIndex(index), animated: false, scrollPosition: .CenteredVertically) + } + + func imageView(atIndex index: Int) -> UIImageView? { + guard let cell = collectionView?.cellForItemAtIndexPath(model.indexPathForSelectedIndex(index)) as? GridViewCell else { + return nil + } + + return cell.imageView + } // MARK: - UIPopoverPresentationControllerDelegate func popoverPresentationControllerShouldDismissPopover(popoverPresentationController: UIPopoverPresentationController) -> Bool { @@ -243,6 +274,7 @@ class GridViewController: UICollectionViewController, UICollectionViewDelegateFl override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { if let photoViewController = storyboard?.instantiateViewControllerWithIdentifier("photoViewController") as? PhotoViewController { photoViewController.model = model.photoViewModelForIndexPath(indexPath) + photoViewController.delegate = self if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? GridViewCell, imageView = cell.imageView { photoViewController.presentTransition = PhotoViewPresentTransition(sourceImageView: imageView) photoViewController.transitioningDelegate = photoViewController diff --git a/Memories/Photo View/PhotoViewController.swift b/Memories/Photo View/PhotoViewController.swift index 28b515e..72f13da 100644 --- a/Memories/Photo View/PhotoViewController.swift +++ b/Memories/Photo View/PhotoViewController.swift @@ -9,7 +9,18 @@ import UIKit import Photos -class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControllerTransitioningDelegate, PHPhotoLibraryChangeObserver, ZoomingPhotoViewDelegate { +protocol PhotoViewControllerDelegate { + func setSelected(index index: Int) + func imageView(atIndex index: Int) -> UIImageView? +} + + +class PhotoViewController: UIViewController, + UIScrollViewDelegate, + UIViewControllerTransitioningDelegate, + PHPhotoLibraryChangeObserver, + ZoomingPhotoViewDelegate +{ @IBOutlet var scrollView: UIScrollView! @IBOutlet weak var shareButton: UIButton! @IBOutlet weak var deleteButton: UIButton! @@ -17,7 +28,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl @IBOutlet weak var heartButton: UIButton! @IBOutlet weak var yearLabel: UILabel! - let PADDING : CGFloat = 10.0; + let PADDING = CGFloat(10) let heartFullImg = UIImage(named: "heart-full")!.imageWithRenderingMode(.AlwaysTemplate) let heartEmptyImg = UIImage(named: "heart-empty")!.imageWithRenderingMode(.AlwaysTemplate) @@ -28,15 +39,25 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl var model : PhotoViewModel! var pageViews: [ZoomingPhotoView?] = [] let imageManager : PHCachingImageManager + var delegate: PhotoViewControllerDelegate? // If the size is too large then PhotoKit doesn't return an optimal image size // see rdar://25181601 (https://openradar.appspot.com/radar?id=6158824289337344) let cacheSize = CGSize(width: 256, height: 256) - + var hideStatusBar = false + struct PanState { + let imageView: UIImageView? + let destImageView: UIImageView? + let transform: CGAffineTransform + let center: CGPoint + let panHeight: CGFloat + } + + var initialPanState = PanState(imageView: nil, destImageView: nil, transform: CGAffineTransformIdentity, center: .zero, panHeight: 0) + var presentTransition: PhotoViewPresentTransition? var dismissTransition: PhotoViewDismissTransition? - var swipeDismissTransition: PhotoViewSwipeDismissTransition? required init?(coder aDecoder: NSCoder) { self.imageManager = PHCachingImageManager() @@ -75,8 +96,6 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl } override func viewDidDisappear(animated: Bool) { - NSLog("PhotoViewController.viewDidDisappear") - PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) self.cancelAllImageRequests() self.purgeAllViews() @@ -91,7 +110,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl initialPage = model.selectedIndex initialOffsetSet = false } - + override func prefersStatusBarHidden() -> Bool { return hideStatusBar || traitCollection.verticalSizeClass == .Compact } @@ -102,7 +121,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl self.setNeedsStatusBarAppearanceUpdate() } } - + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } @@ -150,44 +169,67 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl }, completionHandler: nil) } - func doClose(interactive: Bool) { - if let navController = presentingViewController as? UINavigationController, - gridViewController = navController.topViewController as? GridViewController { - gridViewController.setSelectedIndex(model.selectedIndex) - let imageView = gridViewController.imageViewForIndex(model.selectedIndex)! - let pageView = pageViews[model.selectedIndex]! - - if traitCollection.verticalSizeClass == .Regular { - hideStatusBar(false) - } - - if interactive { - swipeDismissTransition = PhotoViewSwipeDismissTransition(destImageView: imageView, sourceImageView: pageView.imageView) - } - else { - dismissTransition = PhotoViewDismissTransition(destImageView: imageView, sourceImageView: pageView.imageView) - swipeDismissTransition = nil - } - - navController.dismissViewControllerAnimated(true) { - NSLog("dismissViewControllerAnimated completion block") - } + func doClose() { + guard let delegate = delegate else { + return } + + delegate.setSelected(index: model.selectedIndex) + let imageView = delegate.imageView(atIndex: model.selectedIndex)! + let pageView = pageViews[model.selectedIndex]! + + dismissTransition = PhotoViewDismissTransition(destImageView: imageView, sourceImageView: pageView.imageView) + presentingViewController?.dismissViewControllerAnimated(true, completion: nil) } @IBAction func close(sender: UIButton) { - doClose(false) + doClose() } func viewDidPan(gr: UIPanGestureRecognizer) { switch gr.state { case .Began: - doClose(true) + let startPoint = gr.locationInView(gr.view) + + let imageView = pageViews[model.selectedIndex]!.imageView! + initialPanState = PanState(imageView: imageView, + destImageView: delegate?.imageView(atIndex: model.selectedIndex), + transform: imageView.transform, + center: imageView.center, + panHeight: gr.view!.bounds.height - startPoint.y) + initialPanState.destImageView?.hidden = true + case .Changed: + let translation = gr.translationInView(gr.view) + let yPercent = translation.y / initialPanState.panHeight + let percent = yPercent <= 0 ? 0 : yPercent + let alpha = 1 - percent + let scale = (1 - percent / 2) + + initialPanState.imageView?.center = CGPoint(x: initialPanState.center.x + translation.x, y: initialPanState.center.y + translation.y) + initialPanState.imageView?.transform = CGAffineTransformScale(initialPanState.transform, scale, scale) + + view.backgroundColor = UIColor.blackColor().colorWithAlphaComponent(alpha) + if !controlsHidden { setControls(alpha: alpha) } + + case .Ended, .Cancelled: + let velocity = gr.velocityInView(gr.view) + if velocity.y < 0 || gr.state == .Cancelled { + UIView.animateWithDuration(0.25, animations: { + self.initialPanState.imageView?.center = self.initialPanState.center + self.initialPanState.imageView?.transform = self.initialPanState.transform + self.view.backgroundColor = UIColor.blackColor() + if !self.controlsHidden { self.setControls(alpha: 1) } + }) { finished in + self.initialPanState.destImageView?.hidden = false + } + } + else { + doClose() + } + default: break } - - swipeDismissTransition?.handlePan(panRecognizer: gr) } // MARK: Internal implementation @@ -419,17 +461,9 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl } func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { - if swipeDismissTransition != nil { - return swipeDismissTransition - } - return dismissTransition } - func interactionControllerForDismissal(animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? { - return swipeDismissTransition - } - // MARK: PHPhotoLibraryChangeObserver func photoLibraryDidChange(changeInstance: PHChange) { @@ -475,8 +509,12 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl return } + setControls(alpha: hide ? 0 : 1) + } + + func setControls(alpha alpha: CGFloat) { [shareButton, deleteButton, closeButton, heartButton, yearLabel].forEach { - $0.alpha = hide ? 0 : 1 + $0.alpha = alpha } } diff --git a/Memories/Photo View/ZoomingPhotoView.swift b/Memories/Photo View/ZoomingPhotoView.swift index 22ff6d9..23e458f 100644 --- a/Memories/Photo View/ZoomingPhotoView.swift +++ b/Memories/Photo View/ZoomingPhotoView.swift @@ -184,7 +184,7 @@ class ZoomingPhotoView: UIScrollView, UIScrollViewDelegate { // only allow scrolling if the image has been zoomed // larger than the window - scrollEnabled = zoomScale >= minZoom + scrollEnabled = zoomScale > minZoom adjustImageConstraintsForZoomScale(zoomScale) } diff --git a/Memories/Transitions/PhotoViewDismissTransition.swift b/Memories/Transitions/PhotoViewDismissTransition.swift index c010d93..49f0311 100644 --- a/Memories/Transitions/PhotoViewDismissTransition.swift +++ b/Memories/Transitions/PhotoViewDismissTransition.swift @@ -27,16 +27,17 @@ class PhotoViewDismissTransition: NSObject, UIViewControllerAnimatedTransitionin func animateTransition(transitionContext: UIViewControllerContextTransitioning) { guard let container = transitionContext.containerView(), fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), + toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey), fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else { transitionContext.completeTransition(false) return } let transitionView = UIView(frame: transitionContext.initialFrameForViewController(fromViewController)) - transitionView.backgroundColor = UIColor.blackColor() + transitionView.backgroundColor = fromView.backgroundColor container.insertSubview(transitionView, belowSubview: fromView) - let startImageFrame = CGRectIntegral(transitionView.convertRect(self.sourceImageView.bounds, fromView: self.sourceImageView)) + let startImageFrame = self.sourceImageView.frame let imageView = UIImageView(frame: startImageFrame) imageView.image = destImageView.image imageView.contentMode = container.thumbnailContentMode @@ -47,9 +48,10 @@ class PhotoViewDismissTransition: NSObject, UIViewControllerAnimatedTransitionin self.destImageView.hidden = true self.sourceImageView.hidden = true - let newImageFrame = CGRectIntegral(transitionView.convertRect(self.destImageView.bounds, fromView: self.destImageView)) - UIView.animateKeyframesWithDuration(duration, delay: 0, options: .CalculationModeLinear, animations: { + toViewController.statusBarContoller()?.hideStatusBar(false) + let newImageFrame = CGRectIntegral(transitionView.convertRect(self.destImageView.bounds, fromView: self.destImageView)) + UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.25) { fromView.alpha = 0.0 } diff --git a/Memories/Transitions/PhotoViewPresentTransition.swift b/Memories/Transitions/PhotoViewPresentTransition.swift index eac763b..5b15350 100644 --- a/Memories/Transitions/PhotoViewPresentTransition.swift +++ b/Memories/Transitions/PhotoViewPresentTransition.swift @@ -26,6 +26,7 @@ class PhotoViewPresentTransition: NSObject, UIViewControllerAnimatedTransitionin func animateTransition(transitionContext: UIViewControllerContextTransitioning) { guard let container = transitionContext.containerView(), toViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey), + fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), toView = transitionContext.viewForKey(UITransitionContextToViewKey) else { return } @@ -51,6 +52,8 @@ class PhotoViewPresentTransition: NSObject, UIViewControllerAnimatedTransitionin let newImageViewSize = adjustImageBoundsForButtons(fullImageViewSize, vcViewSize: transitionView.frame.size) UIView.animateKeyframesWithDuration(duration, delay: 0, options: .CalculationModeLinear, animations: { + fromViewController.statusBarContoller()?.hideStatusBar(true) + UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 0.75) { transitionView.backgroundColor = UIColor.blackColor() imageView.bounds = CGRect(origin: CGPointZero, size: newImageViewSize) diff --git a/Memories/Transitions/PhotoViewSwipeDismissTransition.swift b/Memories/Transitions/PhotoViewSwipeDismissTransition.swift deleted file mode 100644 index 0cc4a7d..0000000 --- a/Memories/Transitions/PhotoViewSwipeDismissTransition.swift +++ /dev/null @@ -1,105 +0,0 @@ - -// PhotoViewSwipeDismissTransition.swift -// Memories -// -// Created by Michael Brown on 11/06/2016. -// Copyright © 2016 Michael Brown. All rights reserved. -// - -import UIKit - -class PhotoViewSwipeDismissTransition: - UIPercentDrivenInteractiveTransition, - UIViewControllerAnimatedTransitioning { - let destImageView: UIImageView - let sourceImageView: UIImageView - let transitionDuration = NSTimeInterval(0.25) - - var panHeight = CGFloat(0) - - init(destImageView: UIImageView, sourceImageView: UIImageView) { - self.destImageView = destImageView - self.sourceImageView = sourceImageView - } - - // MARK: UIViewControllerAnimatedTransitioning - func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval { - return transitionDuration - } - - func animateTransition(transitionContext: UIViewControllerContextTransitioning) { - guard let container = transitionContext.containerView(), - fromViewController = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey), - fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else { - transitionContext.completeTransition(false) - return - } - - let transitionView = UIView(frame: transitionContext.initialFrameForViewController(fromViewController)) - transitionView.backgroundColor = UIColor.blackColor() - container.insertSubview(transitionView, belowSubview: fromView) - - let startImageFrame = CGRectIntegral(transitionView.convertRect(self.sourceImageView.bounds, fromView: self.sourceImageView)) - let imageView = UIImageView(frame: startImageFrame) - imageView.image = destImageView.image - imageView.contentMode = container.thumbnailContentMode - imageView.clipsToBounds = true - transitionView.addSubview(imageView) - - let fromBackgroundColor = fromView.backgroundColor - fromView.backgroundColor = UIColor.clearColor() - self.destImageView.hidden = true - self.sourceImageView.hidden = true - - let newImageFrame = CGRectIntegral(transitionView.convertRect(self.destImageView.bounds, fromView: self.destImageView)) - - UIView.animateKeyframesWithDuration(transitionDuration, delay: 0, options: .CalculationModeLinear, animations: { - UIView.addKeyframeWithRelativeStartTime(0.0, relativeDuration: 0.25) { - fromView.alpha = 0.0 - } - - UIView.addKeyframeWithRelativeStartTime(0, relativeDuration: 1) { - imageView.frame = newImageFrame - transitionView.backgroundColor = UIColor.clearColor() - } - }) { finished in - self.destImageView.hidden = false - - let cancelled = transitionContext.transitionWasCancelled() - - if cancelled { - fromView.backgroundColor = fromBackgroundColor - self.destImageView.hidden = false - self.sourceImageView.hidden = false - } - else { - fromView.removeFromSuperview() - } - - transitionView.removeFromSuperview() - transitionContext.completeTransition(!cancelled) - } - } - - // MARK: gesture handling - func handlePan(panRecognizer gr: UIPanGestureRecognizer) { - switch gr.state { - case .Began: - let startPoint = gr.locationInView(gr.view) - panHeight = gr.view!.bounds.height - startPoint.y - case .Changed: - let percent = gr.translationInView(gr.view).y / panHeight - updateInteractiveTransition(percent <= 0 ? 0 : percent) - case .Ended, .Cancelled: - let velocity = gr.velocityInView(gr.view) - if velocity.y < 0 || gr.state == .Cancelled { - cancelInteractiveTransition() - } - else { - finishInteractiveTransition() - } - default: - break - } - } -} diff --git a/Memories/Utilities/StatusBarViewController.swift b/Memories/Utilities/StatusBarViewController.swift new file mode 100644 index 0000000..6799f01 --- /dev/null +++ b/Memories/Utilities/StatusBarViewController.swift @@ -0,0 +1,28 @@ +// +// StatusBarViewController.swift +// Memories +// +// Created by Michael Brown on 25/07/2016. +// Copyright © 2016 Michael Brown. All rights reserved. +// + +import UIKit + +protocol StatusBarViewController { + func hideStatusBar(hide: Bool) +} + +extension UIViewController { + + func statusBarContoller() -> StatusBarViewController? { + let vcStatusBar : StatusBarViewController? + if let navController = self as? UINavigationController { + vcStatusBar = navController.topViewController as? StatusBarViewController + } + else { + vcStatusBar = self as? StatusBarViewController + } + + return vcStatusBar + } +} \ No newline at end of file From 0326ba367eeb987bad18adbc0aa4fd54e75cf501 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Sat, 6 Aug 2016 22:09:42 +0100 Subject: [PATCH 3/3] Bump to version 1.1.4, Build 19 --- Memories/Photo View/PhotoViewController.swift | 2 +- Memories/Supporting Files/Info.plist | 4 ++-- Photo Widget/Info.plist | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Memories/Photo View/PhotoViewController.swift b/Memories/Photo View/PhotoViewController.swift index 72f13da..abddd87 100644 --- a/Memories/Photo View/PhotoViewController.swift +++ b/Memories/Photo View/PhotoViewController.swift @@ -99,7 +99,7 @@ class PhotoViewController: UIViewController, PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) self.cancelAllImageRequests() self.purgeAllViews() -} + } override func viewDidLayoutSubviews() { setupViews() diff --git a/Memories/Supporting Files/Info.plist b/Memories/Supporting Files/Info.plist index bfdf420..d83fa80 100644 --- a/Memories/Supporting Files/Info.plist +++ b/Memories/Supporting Files/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.1.3 + 1.1.4 CFBundleSignature ???? CFBundleURLTypes @@ -32,7 +32,7 @@ CFBundleVersion - 18 + 19 LSApplicationQueriesSchemes itms-apps diff --git a/Photo Widget/Info.plist b/Photo Widget/Info.plist index 3afba45..90763f1 100644 --- a/Photo Widget/Info.plist +++ b/Photo Widget/Info.plist @@ -17,11 +17,11 @@ CFBundlePackageType XPC! CFBundleShortVersionString - 1.1.3 + 1.1.4 CFBundleSignature ???? CFBundleVersion - 18 + 19 NSExtension NSExtensionMainStoryboard