From 425b3b09a1f2bfde972c566e24e800856ff14b83 Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Tue, 15 Oct 2019 18:08:13 -0500 Subject: [PATCH] Animate the transition to full screen image view --- NetNewsWire.xcodeproj/project.pbxproj | 4 ++ iOS/Article/ArticleViewController.swift | 30 +++++++++++ iOS/Article/ImageScrollView.swift | 13 ++--- iOS/Article/ImageTransition.swift | 66 +++++++++++++++++++++++++ iOS/Article/ImageViewController.swift | 3 ++ 5 files changed, 110 insertions(+), 6 deletions(-) create mode 100644 iOS/Article/ImageTransition.swift diff --git a/NetNewsWire.xcodeproj/project.pbxproj b/NetNewsWire.xcodeproj/project.pbxproj index 130c4ca38..c391ab627 100644 --- a/NetNewsWire.xcodeproj/project.pbxproj +++ b/NetNewsWire.xcodeproj/project.pbxproj @@ -103,6 +103,7 @@ 518651B023555EB20078E021 /* NNW3FeedsImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651A923555EB20078E021 /* NNW3FeedsImporter.swift */; }; 518651B123555EB20078E021 /* NNW3Feed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AA23555EB20078E021 /* NNW3Feed.swift */; }; 518651B223555EB20078E021 /* NNW3Document.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651AB23555EB20078E021 /* NNW3Document.swift */; }; + 518651DA235621840078E021 /* ImageTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 518651D9235621840078E021 /* ImageTransition.swift */; }; 518B2EE82351B45600400001 /* NetNewsWire_iOSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 840D61952029031D009BC708 /* NetNewsWire_iOSTests.swift */; }; 51934CCB230F599B006127BE /* ThemedNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CC1230F5963006127BE /* ThemedNavigationController.swift */; }; 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 51934CCD2310792F006127BE /* ActivityManager.swift */; }; @@ -798,6 +799,7 @@ 518651A923555EB20078E021 /* NNW3FeedsImporter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3FeedsImporter.swift; sourceTree = ""; }; 518651AA23555EB20078E021 /* NNW3Feed.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3Feed.swift; sourceTree = ""; }; 518651AB23555EB20078E021 /* NNW3Document.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NNW3Document.swift; sourceTree = ""; }; + 518651D9235621840078E021 /* ImageTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageTransition.swift; sourceTree = ""; }; 518B2ED22351B3DD00400001 /* NetNewsWire-iOSTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "NetNewsWire-iOSTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 518B2EE92351B4C200400001 /* NetNewsWire_iOSTests_target.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = NetNewsWire_iOSTests_target.xcconfig; sourceTree = ""; }; 51934CC1230F5963006127BE /* ThemedNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemedNavigationController.swift; sourceTree = ""; }; @@ -1397,6 +1399,7 @@ 51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */, 5142192923522B5500E07E2C /* ImageViewController.swift */, 514219362352510100E07E2C /* ImageScrollView.swift */, + 518651D9235621840078E021 /* ImageTransition.swift */, ); path = Article; sourceTree = ""; @@ -2919,6 +2922,7 @@ 5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */, 51C452882265093600C03939 /* AddFeedViewController.swift in Sources */, 51934CCE2310792F006127BE /* ActivityManager.swift in Sources */, + 518651DA235621840078E021 /* ImageTransition.swift in Sources */, 514219372352510100E07E2C /* ImageScrollView.swift in Sources */, DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */, 51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */, diff --git a/iOS/Article/ArticleViewController.swift b/iOS/Article/ArticleViewController.swift index 7d95975d4..8dd1fcc0e 100644 --- a/iOS/Article/ArticleViewController.swift +++ b/iOS/Article/ArticleViewController.swift @@ -43,6 +43,7 @@ class ArticleViewController: UIViewController { }() private var webView: WKWebView! + private var transition = ImageTransition() weak var coordinator: SceneCoordinator! @@ -65,6 +66,9 @@ class ArticleViewController: UIViewController { return nil } } + + var clickedImage: UIImage? + var clickedImageFrame: CGRect? var articleExtractorButtonState: ArticleExtractorButtonState { get { @@ -357,17 +361,43 @@ extension ArticleViewController: WKScriptMessageHandler { let base64Image = String(clickMessage.imageURL.suffix(from: range.upperBound)) if let imageData = Data(base64Encoded: base64Image), let image = UIImage(data: imageData) { + + let rect = CGRect(x: CGFloat(clickMessage.x), y: CGFloat(clickMessage.y), width: CGFloat(clickMessage.width), height: CGFloat(clickMessage.height)) + clickedImageFrame = webView.convert(rect, to: nil) + clickedImage = image + let imageVC = UIStoryboard.main.instantiateController(ofType: ImageViewController.self) imageVC.image = image imageVC.modalPresentationStyle = .fullScreen + imageVC.transitioningDelegate = self present(imageVC, animated: true) + } } } } +// MARK: UIViewControllerTransitioningDelegate + +extension ArticleViewController: UIViewControllerTransitioningDelegate { + + func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { + guard let frame = clickedImageFrame, let image = clickedImage else { return nil } + transition.originFrame = frame + transition.originImage = image + transition.presenting = true + return transition + } + + func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { + transition.presenting = false + return transition + } +} + // MARK: JSON + private struct TemplateData: Codable { let style: String let body: String diff --git a/iOS/Article/ImageScrollView.swift b/iOS/Article/ImageScrollView.swift index df44c8e68..b807c929c 100644 --- a/iOS/Article/ImageScrollView.swift +++ b/iOS/Article/ImageScrollView.swift @@ -42,6 +42,10 @@ open class ImageScrollView: UIScrollView { private var scaleToRestoreAfterResize: CGFloat = 1.0 var maxScaleFromMinScale: CGFloat = 3.0 + var zoomedFrame: CGRect { + return convert(zoomView?.frame ?? CGRect.zero, to: nil) + } + override open var frame: CGRect { willSet { if frame.equalTo(newValue) == false && newValue.equalTo(CGRect.zero) == false && imageSize.equalTo(CGSize.zero) == false { @@ -93,16 +97,14 @@ open class ImageScrollView: UIScrollView { // center horizontally if frameToCenter.size.width < bounds.width { frameToCenter.origin.x = (bounds.width - frameToCenter.size.width) / 2 - } - else { + } else { frameToCenter.origin.x = 0 } // center vertically if frameToCenter.size.height < bounds.height { frameToCenter.origin.y = (bounds.height - frameToCenter.size.height) / 2 - } - else { + } else { frameToCenter.origin.y = 0 } @@ -260,8 +262,7 @@ open class ImageScrollView: UIScrollView { // zoom out if it bigger than middle scale point. Else, zoom in if zoomScale >= maximumZoomScale / 2.0 { setZoomScale(minimumZoomScale, animated: true) - } - else { + } else { let center = gestureRecognizer.location(in: gestureRecognizer.view) let zoomRect = zoomRectForScale(ImageScrollView.kZoomInFactorFromMinWhenDoubleTap * minimumZoomScale, center: center) zoom(to: zoomRect, animated: true) diff --git a/iOS/Article/ImageTransition.swift b/iOS/Article/ImageTransition.swift new file mode 100644 index 000000000..1e66fffcc --- /dev/null +++ b/iOS/Article/ImageTransition.swift @@ -0,0 +1,66 @@ +// +// ImageAnimator.swift +// NetNewsWire-iOS +// +// Created by Maurice Parker on 10/15/19. +// Copyright © 2019 Ranchero Software. All rights reserved. +// + +import UIKit + +class ImageTransition: NSObject, UIViewControllerAnimatedTransitioning { + + let duration = 0.5 + var presenting = true + var originFrame: CGRect! + var originImage: UIImage! + + func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { + return duration + } + + func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { + + let destFrame: CGRect = { + if presenting { + let imageController = transitionContext.viewController(forKey: .to) as! ImageViewController + return imageController.zoomedFrame + } else { + let imageController = transitionContext.viewController(forKey: .from) as! ImageViewController + return imageController.zoomedFrame + } + }() + + let initialFrame = presenting ? originFrame! : destFrame + let targetFrame = presenting ? destFrame : originFrame! + + let imageView = UIImageView(image: originImage) + imageView.frame = initialFrame + +// let xScaleFactor = presenting ? initialFrame.width / targetFrame.width : targetFrame.width / initialFrame.width +// let yScaleFactor = presenting ? initialFrame.height / targetFrame.height : targetFrame.height / initialFrame.height +// let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, y: yScaleFactor) + + let fromView = transitionContext.view(forKey: .from)! + fromView.removeFromSuperview() + + transitionContext.containerView.backgroundColor = UIColor.systemBackground + transitionContext.containerView.addSubview(imageView) + + UIView.animate( + withDuration: duration, + animations: { + imageView.frame = targetFrame +// imageView.transform = scaleTransform +// imageView.center = CGPoint(x: targetFrame.midX, y: targetFrame.midY) + }, completion: { _ in + imageView.removeFromSuperview() + let toView = transitionContext.view(forKey: .to)! + transitionContext.containerView.addSubview(toView) + transitionContext.containerView.bringSubviewToFront(toView) + transitionContext.completeTransition(true) + }) + + } + +} diff --git a/iOS/Article/ImageViewController.swift b/iOS/Article/ImageViewController.swift index 50047ed20..b1c40a153 100644 --- a/iOS/Article/ImageViewController.swift +++ b/iOS/Article/ImageViewController.swift @@ -14,6 +14,9 @@ class ImageViewController: UIViewController { @IBOutlet weak var imageScrollView: ImageScrollView! var image: UIImage! + var zoomedFrame: CGRect { + return imageScrollView.zoomedFrame + } override func viewDidLoad() { super.viewDidLoad()