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 {
static let imageWasClicked = "imageWasClicked"
static let imageWasShown = "imageWasShown"
}
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
@ -43,7 +44,8 @@ class ArticleViewController: UIViewController {
}()
private var webView: WKWebView!
private var transition = ImageTransition()
private lazy var transition = ImageTransition(controller: self)
private var clickedImageCompletion: (() -> Void)?
weak var coordinator: SceneCoordinator!
@ -109,7 +111,9 @@ class ArticleViewController: UIViewController {
webView.uiDelegate = self
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.imageWasShown)
// 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
@ -296,6 +300,15 @@ class ArticleViewController: UIViewController {
webView.scrollView.setContentOffset(scrollToPoint, animated: true)
}
func hideClickedImage() {
webView?.evaluateJavaScript("hideClickedImage();")
}
func showClickedImage(completion: @escaping () -> Void) {
clickedImageCompletion = completion
webView?.evaluateJavaScript("showClickedImage();")
}
}
// MARK: WKNavigationDelegate
@ -350,26 +363,13 @@ extension ArticleViewController: WKUIDelegate {
extension ArticleViewController: WKScriptMessageHandler {
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if message.name == MessageName.imageWasClicked,
let body = message.body as? String,
let data = body.data(using: .utf8),
let clickMessage = try? JSONDecoder().decode(ImageClickMessage.self, from: data),
let range = clickMessage.imageURL.range(of: ";base64,") {
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)
}
switch message.name {
case MessageName.imageWasShown:
clickedImageCompletion?()
case MessageName.imageWasClicked:
imageWasClicked(body: message.body as? String)
default:
return
}
}
@ -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 {
let duration = 0.4
private weak var articleController: ArticleViewController?
private let duration = 0.4
var presenting = true
var originFrame: CGRect!
var originImage: UIImage!
init(controller: ArticleViewController) {
self.articleController = controller
}
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return duration
}
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if presenting {
animateTransitionPresenting(using: transitionContext)
} else {
animateTransitionReturning(using: transitionContext)
}
}
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!
private func animateTransitionPresenting(using transitionContext: UIViewControllerContextTransitioning) {
let imageView = UIImageView(image: originImage)
imageView.frame = initialFrame
imageView.frame = originFrame
let fromView = transitionContext.view(forKey: .from)!
fromView.removeFromSuperview()
if presenting {
transitionContext.containerView.backgroundColor = UIColor.systemBackground
transitionContext.containerView.addSubview(imageView)
transitionContext.containerView.backgroundColor = UIColor.systemBackground
transitionContext.containerView.addSubview(imageView)
articleController?.hideClickedImage()
UIView.animate(
withDuration: duration,
delay:0.0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.2,
animations: {
imageView.frame = targetFrame
}, completion: { _ in
imageView.removeFromSuperview()
let toView = transitionContext.view(forKey: .to)!
transitionContext.containerView.addSubview(toView)
transitionContext.completeTransition(true)
})
UIView.animate(
withDuration: duration,
delay:0.0,
usingSpringWithDamping: 0.8,
initialSpringVelocity: 0.2,
animations: {
let imageController = transitionContext.viewController(forKey: .to) as! ImageViewController
imageView.frame = imageController.zoomedFrame
}, completion: { _ in
imageView.removeFromSuperview()
let toView = transitionContext.view(forKey: .to)!
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)!
transitionContext.containerView.addSubview(toView)
transitionContext.containerView.addSubview(imageView)
let fromView = transitionContext.view(forKey: .from)!
fromView.removeFromSuperview()
UIView.animate(
withDuration: duration,
delay:0.0,
animations: {
imageView.frame = targetFrame
imageView.alpha = 0
}, completion: { _ in
imageView.removeFromSuperview()
transitionContext.completeTransition(true)
})
}
let toView = transitionContext.view(forKey: .to)!
transitionContext.containerView.addSubview(toView)
transitionContext.containerView.addSubview(imageView)
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
async function imageWasClicked(img) {
img.classList.add("nnwClicked");
const rect = img.getBoundingClientRect();
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() {
window.onclick = function(event) {
if (event.target.matches('img')) {