Change image zoom animation to be a solid picture zooming in and out

This commit is contained in:
Maurice Parker 2019-10-16 16:40:49 -05:00
parent 3ddd14d856
commit cb6490222f
3 changed files with 120 additions and 73 deletions

View File

@ -24,6 +24,7 @@ class ArticleViewController: UIViewController {
private struct MessageName { private struct MessageName {
static let imageWasClicked = "imageWasClicked" static let imageWasClicked = "imageWasClicked"
static let imageWasShown = "imageWasShown"
} }
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem! @IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
@ -43,7 +44,8 @@ class ArticleViewController: UIViewController {
}() }()
private var webView: WKWebView! private var webView: WKWebView!
private var transition = ImageTransition() private lazy var transition = ImageTransition(controller: self)
private var clickedImageCompletion: (() -> Void)?
weak var coordinator: SceneCoordinator! weak var coordinator: SceneCoordinator!
@ -109,7 +111,9 @@ class ArticleViewController: UIViewController {
webView.uiDelegate = self webView.uiDelegate = self
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked) webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasShown)
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked) webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasClicked)
webView.configuration.userContentController.add(WrapperScriptMessageHandler(self), name: MessageName.imageWasShown)
// Even though page.html should be loaded into this webview, we have to do it again // Even though page.html should be loaded into this webview, we have to do it again
// to work around this bug: http://www.openradar.me/22855188 // to work around this bug: http://www.openradar.me/22855188
@ -296,6 +300,15 @@ class ArticleViewController: UIViewController {
webView.scrollView.setContentOffset(scrollToPoint, animated: true) webView.scrollView.setContentOffset(scrollToPoint, animated: true)
} }
func hideClickedImage() {
webView?.evaluateJavaScript("hideClickedImage();")
}
func showClickedImage(completion: @escaping () -> Void) {
clickedImageCompletion = completion
webView?.evaluateJavaScript("showClickedImage();")
}
} }
// MARK: WKNavigationDelegate // MARK: WKNavigationDelegate
@ -350,26 +363,13 @@ extension ArticleViewController: WKUIDelegate {
extension ArticleViewController: WKScriptMessageHandler { extension ArticleViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) { func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == MessageName.imageWasClicked, switch message.name {
let body = message.body as? String, case MessageName.imageWasShown:
let data = body.data(using: .utf8), clickedImageCompletion?()
let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data), case MessageName.imageWasClicked:
let range = clickMessage.imageURL.range(of: ";base64,") { imageWasClicked(body: message.body as? String)
default:
let base64Image = String(clickMessage.imageURL.suffix(from: range.upperBound)) return
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))
transition.originFrame = webView.convert(rect, to: nil)
transition.originImage = image
let imageVC = UIStoryboard.main.instantiateController(ofType: ImageViewController.self)
imageVC.image = image
imageVC.modalPresentationStyle = .fullScreen
imageVC.transitioningDelegate = self
present(imageVC, animated: true)
}
} }
} }
@ -430,4 +430,25 @@ private extension ArticleViewController {
} }
} }
func imageWasClicked(body: String?) {
guard let body = body,
let data = body.data(using: .utf8),
let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data),
let range = clickMessage.imageURL.range(of: ";base64,")
else { return }
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))
transition.originFrame = webView.convert(rect, to: nil)
transition.originImage = image
let imageVC = UIStoryboard.main.instantiateController(ofType: ImageViewController.self)
imageVC.image = image
imageVC.modalPresentationStyle = .fullScreen
imageVC.transitioningDelegate = self
present(imageVC, animated: true)
}
}
} }

View File

@ -10,74 +10,84 @@ import UIKit
class ImageTransition: NSObject, UIViewControllerAnimatedTransitioning { class ImageTransition: NSObject, UIViewControllerAnimatedTransitioning {
let duration = 0.4 private weak var articleController: ArticleViewController?
private let duration = 0.4
var presenting = true var presenting = true
var originFrame: CGRect! var originFrame: CGRect!
var originImage: UIImage! var originImage: UIImage!
init(controller: ArticleViewController) {
self.articleController = controller
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration return duration
} }
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if presenting {
animateTransitionPresenting(using: transitionContext)
} else {
animateTransitionReturning(using: transitionContext)
}
}
let destFrame: CGRect = { private func animateTransitionPresenting(using transitionContext: UIViewControllerContextTransitioning) {
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) let imageView = UIImageView(image: originImage)
imageView.frame = initialFrame imageView.frame = originFrame
let fromView = transitionContext.view(forKey: .from)! let fromView = transitionContext.view(forKey: .from)!
fromView.removeFromSuperview() fromView.removeFromSuperview()
if presenting { transitionContext.containerView.backgroundColor = UIColor.systemBackground
transitionContext.containerView.addSubview(imageView)
transitionContext.containerView.backgroundColor = UIColor.systemBackground articleController?.hideClickedImage()
transitionContext.containerView.addSubview(imageView)
UIView.animate( UIView.animate(
withDuration: duration, withDuration: duration,
delay:0.0, delay:0.0,
usingSpringWithDamping: 0.8, usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.2, initialSpringVelocity: 0.2,
animations: { animations: {
imageView.frame = targetFrame let imageController = transitionContext.viewController(forKey: .to) as! ImageViewController
}, completion: { _ in imageView.frame = imageController.zoomedFrame
imageView.removeFromSuperview() }, completion: { _ in
let toView = transitionContext.view(forKey: .to)! imageView.removeFromSuperview()
transitionContext.containerView.addSubview(toView) let toView = transitionContext.view(forKey: .to)!
transitionContext.completeTransition(true) transitionContext.containerView.addSubview(toView)
}) transitionContext.completeTransition(true)
})
}
} else { private func animateTransitionReturning(using transitionContext: UIViewControllerContextTransitioning) {
let imageController = transitionContext.viewController(forKey: .from) as! ImageViewController
let imageView = UIImageView(image: originImage)
imageView.frame = imageController.zoomedFrame
let toView = transitionContext.view(forKey: .to)! let fromView = transitionContext.view(forKey: .from)!
transitionContext.containerView.addSubview(toView) fromView.removeFromSuperview()
transitionContext.containerView.addSubview(imageView)
UIView.animate( let toView = transitionContext.view(forKey: .to)!
withDuration: duration, transitionContext.containerView.addSubview(toView)
delay:0.0, transitionContext.containerView.addSubview(imageView)
animations: {
imageView.frame = targetFrame
imageView.alpha = 0
}, completion: { _ in
imageView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
UIView.animate(
withDuration: duration,
delay:0.0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.2,
animations: {
imageView.frame = self.originFrame
}, completion: { _ in
self.articleController?.showClickedImage() {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
imageView.removeFromSuperview()
transitionContext.completeTransition(true)
}
}
})
} }
} }

View File

@ -1,5 +1,7 @@
// Used to pop a resizable image view // Used to pop a resizable image view
async function imageWasClicked(img) { async function imageWasClicked(img) {
img.classList.add("nnwClicked");
const rect = img.getBoundingClientRect(); const rect = img.getBoundingClientRect();
var message = { var message = {
@ -31,7 +33,21 @@ async function imageWasClicked(img) {
} }
// Add the click listeners for images // Used to animate the transition to a fullscreen image
function hideClickedImage() {
var img = document.querySelector('.nnwClicked')
img.style.opacity = 0
}
// Used to animate the transition from a fullscreen image
function showClickedImage() {
var img = document.querySelector('.nnwClicked')
img.classList.remove("nnwClicked");
img.style.opacity = 1
window.webkit.messageHandlers.imageWasShown.postMessage("");
}
// Add the click listener for images
function imageClicks() { function imageClicks() {
window.onclick = function(event) { window.onclick = function(event) {
if (event.target.matches('img')) { if (event.target.matches('img')) {