// // LargeImageViewController.swift // Tusker // // Created by Shadowfacts on 8/31/18. // Copyright © 2018 Shadowfacts. All rights reserved. // import UIKit import Photos protocol LargeImageViewControllerDelegate { func closeLargeImage() } class LargeImageViewController: UIViewController, UIScrollViewDelegate { static func create(image: UIImage, description: String?, sourceView: UIView, sourceViewController: UIViewController) -> LargeImageViewController { guard let vc = UIStoryboard(name: "LargeImage", bundle: nil).instantiateInitialViewController() as? LargeImageViewController else { fatalError() } vc.image = image vc.imageDescription = description var frame = sourceView.convert(sourceView.bounds, to: sourceViewController.view) if let scrollView = sourceViewController.view as? UIScrollView { let scale = scrollView.zoomScale let width = frame.width * scale let height = frame.height * scale let x = frame.minX * scale - scrollView.contentOffset.x + scrollView.frame.minX let y = frame.minY * scale - scrollView.contentOffset.y + scrollView.frame.minY frame = CGRect(x: x, y: y, width: width, height: height) } vc.originFrame = frame vc.originCornerRadius = sourceView.layer.cornerRadius vc.transitioningDelegate = sourceViewController return vc } var delegate: LargeImageViewControllerDelegate? var originFrame: CGRect? var originCornerRadius: CGFloat? var dismissInteractionController: LargeImageInteractionController? @IBOutlet weak var scrollView: UIScrollView! @IBOutlet weak var imageView: UIImageView! @IBOutlet weak var imageViewLeadingConstraint: NSLayoutConstraint! @IBOutlet weak var imageViewTrailingConstraint: NSLayoutConstraint! @IBOutlet weak var imageViewTopConstraint: NSLayoutConstraint! @IBOutlet weak var imageViewBottomConstraint: NSLayoutConstraint! @IBOutlet weak var topControlsView: UIView! @IBOutlet weak var topControlsHeightConstraint: NSLayoutConstraint! @IBOutlet weak var downloadButton: UIButton! @IBOutlet weak var downloadButtonTopConstraint: NSLayoutConstraint! @IBOutlet weak var downloadButtonLeadingConstraint: NSLayoutConstraint! @IBOutlet weak var closeButton: UIButton! @IBOutlet weak var closeButtonTopConstraint: NSLayoutConstraint! @IBOutlet weak var closeButtonTrailingConstraint: NSLayoutConstraint! @IBOutlet weak var bottomControlsView: UIView! @IBOutlet weak var descriptionLabel: UILabel! var initializedTopControlsConstrains = false var image: UIImage? var imageDescription: String? var controlsVisible = true { didSet { UIView.animate(withDuration: 0.2) { let topOffset = self.controlsVisible ? 0 : -self.topControlsView.bounds.height self.topControlsView.transform = CGAffineTransform(translationX: 0, y: topOffset) if self.imageDescription != nil { let bottomOffset = self.controlsVisible ? 0 : self.bottomControlsView.bounds.height + self.view.safeAreaInsets.bottom self.bottomControlsView.transform = CGAffineTransform(translationX: 0, y: bottomOffset) } } } } var prevZoomScale: CGFloat? override var prefersStatusBarHidden: Bool { return true } override func viewDidLoad() { super.viewDidLoad() imageView.image = image scrollView.delegate = self imageView.bounds = CGRect(origin: .zero, size: image!.size) if let imageDescription = imageDescription { descriptionLabel.text = imageDescription } else { bottomControlsView.isHidden = true } dismissInteractionController = LargeImageInteractionController(viewController: self) } override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() let widthScale = view.bounds.size.width / imageView.bounds.width let heightScale = view.bounds.size.height / imageView.bounds.height let minScale = min(widthScale, heightScale) scrollView.minimumZoomScale = minScale scrollView.zoomScale = minScale scrollView.maximumZoomScale = minScale >= 1 ? minScale + 2 : 2 if !initializedTopControlsConstrains { initializedTopControlsConstrains = true if view.safeAreaInsets.top == 44 { // running on iPhone X style notched device let notchWidth: CGFloat = 209 let earWidth = (view.bounds.width - notchWidth) / 2 let offset = (earWidth - downloadButton.bounds.width) / 2 downloadButtonLeadingConstraint.constant = offset closeButtonTrailingConstraint.constant = offset } } } func viewForZooming(in scrollView: UIScrollView) -> UIView? { return imageView } func scrollViewDidZoom(_ scrollView: UIScrollView) { centerImage() let prevZoomScale = self.prevZoomScale ?? scrollView.minimumZoomScale if scrollView.zoomScale <= scrollView.minimumZoomScale { controlsVisible = true } else if scrollView.zoomScale > prevZoomScale { controlsVisible = false } self.prevZoomScale = scrollView.zoomScale } func centerImage() { let yOffset = max(0, (view.bounds.size.height - imageView.frame.height) / 2) imageViewTopConstraint.constant = yOffset imageViewBottomConstraint.constant = yOffset let xOffset = max(0, (view.bounds.size.width - imageView.frame.width) / 2) imageViewLeadingConstraint.constant = xOffset imageViewTrailingConstraint.constant = xOffset } func zoomRectFor(scale: CGFloat, center: CGPoint) -> CGRect { var zoomRect = CGRect.zero zoomRect.size.width = imageView.frame.width / scale zoomRect.size.height = imageView.frame.height / scale let newCenter = scrollView.convert(center, to: imageView) zoomRect.origin.x = newCenter.x - (zoomRect.width / 2) zoomRect.origin.y = newCenter.y - (zoomRect.height / 2) return zoomRect } func animateZoomOut() { UIView.animate(withDuration: 0.3, animations: { self.scrollView.zoomScale = self.scrollView.minimumZoomScale self.view.layoutIfNeeded() }) } /* // MARK: - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepare(for segue: UIStoryboardSegue, sender: Any?) { // Get the new view controller using segue.destination. // Pass the selected object to the new view controller. } */ @IBAction func scrollViewPressed(_ sender: UITapGestureRecognizer) { if scrollView.zoomScale > scrollView.minimumZoomScale { animateZoomOut() } else { controlsVisible = !controlsVisible } } @IBAction func scrollViewDoubleTapped(_ recognizer: UITapGestureRecognizer) { if scrollView.zoomScale <= scrollView.minimumZoomScale { let point = recognizer.location(in: recognizer.view) let scale: CGFloat if scrollView.minimumZoomScale < 1 { if 1 - scrollView.zoomScale <= 0.5 { scale = scrollView.zoomScale + 1 } else { scale = 1 } } else { scale = scrollView.maximumZoomScale } let rect = zoomRectFor(scale: scale, center: point) UIView.animate(withDuration: 0.3) { self.scrollView.zoom(to: rect, animated: false) self.view.layoutIfNeeded() } } else { animateZoomOut() } } @IBAction func closeButtonPressed(_ sender: Any) { delegate?.closeLargeImage() } @IBAction func downloadPressed(_ sender: Any) { guard let image = image else { return } PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAsset(from: image) }, completionHandler: { success, error in if success { return } else if let error = error { print("Couldn't save photo: \(error)") } else { return } }) } }