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 30b48f4..5cc0f72 100644 --- a/Memories.xcodeproj/project.pbxproj +++ b/Memories.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ 4C8594D31BB33E2F005CCFAC /* DACircularProgress.framework.dSYM in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */; }; 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 */; }; @@ -131,6 +132,7 @@ 4C8594D21BB33E2F005CCFAC /* DACircularProgress.framework.dSYM */ = {isa = PBXFileReference; lastKnownFileType = wrapper.dsym; name = DACircularProgress.framework.dSYM; path = Carthage/Build/iOS/DACircularProgress.framework.dSYM; 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 = ""; }; @@ -209,6 +211,7 @@ 4C989BDE1BA7806E00D4C13F /* Dynamic.swift */, 4C0410E51BC8787200BBB76C /* UIDevice.swift */, 4CABEE211CAAF08500A9F23A /* UITraitEnvironment.swift */, + 4C92A9371D4C088E00B3FAAD /* StatusBarViewController.swift */, ); path = Utilities; sourceTree = ""; @@ -265,8 +268,8 @@ 4C641B901C3DB30C004B15D7 /* Transitions */ = { isa = PBXGroup; children = ( - 4C00A4F41C1B7ED30078A43F /* PhotoViewDismissTransition.swift */, 4C921FD11C1A038800F0411D /* PhotoViewPresentTransition.swift */, + 4C00A4F41C1B7ED30078A43F /* PhotoViewDismissTransition.swift */, ); path = Transitions; sourceTree = ""; @@ -552,6 +555,7 @@ 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 */, 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/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 76e451a..abddd87 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,12 +39,23 @@ 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? @@ -64,12 +86,21 @@ 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) { + PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) + self.cancelAllImageRequests() + self.purgeAllViews() + } + override func viewDidLayoutSubviews() { setupViews() } @@ -79,7 +110,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl initialPage = model.selectedIndex initialOffsetSet = false } - + override func prefersStatusBarHidden() -> Bool { return hideStatusBar || traitCollection.verticalSizeClass == .Compact } @@ -90,7 +121,7 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl self.setNeedsStatusBarAppearanceUpdate() } } - + override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } @@ -138,21 +169,66 @@ class PhotoViewController: UIViewController, UIScrollViewDelegate, UIViewControl }, completionHandler: nil) } + 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) { - if let navController = presentingViewController as? UINavigationController, - gridViewController = navController.topViewController as? GridViewController { - cancelAllImageRequests() - PHPhotoLibrary.sharedPhotoLibrary().unregisterChangeObserver(self) - - gridViewController.setSelectedIndex(model.selectedIndex) - if traitCollection.verticalSizeClass == .Regular { - hideStatusBar(false) + doClose() + } + + func viewDidPan(gr: UIPanGestureRecognizer) { + switch gr.state { + case .Began: + 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 + } } - let imageView = gridViewController.imageViewForIndex(model.selectedIndex) - let pageView = pageViews[model.selectedIndex] - dismissTransition = PhotoViewDismissTransition(destImageView: imageView!, sourceImageView: pageView!.imageView) - - navController.dismissViewControllerAnimated(true) { self.purgeAllViews() } + else { + doClose() + } + + default: + break } } @@ -433,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/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/Memories/Transitions/PhotoViewDismissTransition.swift b/Memories/Transitions/PhotoViewDismissTransition.swift index eff7836..49f0311 100644 --- a/Memories/Transitions/PhotoViewDismissTransition.swift +++ b/Memories/Transitions/PhotoViewDismissTransition.swift @@ -7,7 +7,6 @@ // import UIKit -import AVFoundation class PhotoViewDismissTransition: NSObject, UIViewControllerAnimatedTransitioning { @@ -28,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 @@ -48,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/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 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