Add fullscreen image previewing and zooming
This commit is contained in:
parent
5ed508b709
commit
3ee0506b4a
|
@ -56,6 +56,8 @@
|
|||
513C5D0D232574DA003D4054 /* RSTree.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84C37F9520DD8CFE00CA8CF5 /* RSTree.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
513C5D0E232574E4003D4054 /* SyncDatabase.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; };
|
||||
513C5D0F232574E4003D4054 /* SyncDatabase.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 51554C01228B6EB50055115A /* SyncDatabase.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5142192923522B5500E07E2C /* ImageViewController.swift */; };
|
||||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 514219362352510100E07E2C /* ImageScrollView.swift */; };
|
||||
5144EA2F2279FAB600D19003 /* AccountsDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */; };
|
||||
5144EA362279FC3D00D19003 /* AccountsAddLocal.xib in Resources */ = {isa = PBXBuildFile; fileRef = 5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */; };
|
||||
5144EA382279FC6200D19003 /* AccountsAddLocalWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */; };
|
||||
|
@ -750,6 +752,8 @@
|
|||
513C5CE8232571C2003D4054 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
|
||||
513C5CEB232571C2003D4054 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
|
||||
513C5CED232571C2003D4054 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageViewController.swift; sourceTree = "<group>"; };
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageScrollView.swift; sourceTree = "<group>"; };
|
||||
5144EA2E2279FAB600D19003 /* AccountsDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountsDetailViewController.swift; sourceTree = "<group>"; };
|
||||
5144EA352279FC3D00D19003 /* AccountsAddLocal.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = AccountsAddLocal.xib; sourceTree = "<group>"; };
|
||||
5144EA372279FC6200D19003 /* AccountsAddLocalWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsAddLocalWindowController.swift; sourceTree = "<group>"; };
|
||||
|
@ -1359,6 +1363,8 @@
|
|||
51C4527E2265092C00C03939 /* ArticleViewController.swift */,
|
||||
517630222336657E00E15FFF /* ArticleViewControllerWebViewProvider.swift */,
|
||||
51102164233A7D6C0007A5F7 /* ArticleExtractorButton.swift */,
|
||||
5142192923522B5500E07E2C /* ImageViewController.swift */,
|
||||
514219362352510100E07E2C /* ImageScrollView.swift */,
|
||||
);
|
||||
path = Article;
|
||||
sourceTree = "<group>";
|
||||
|
@ -2842,6 +2848,7 @@
|
|||
51E3EB3D229AB08300645299 /* ErrorHandler.swift in Sources */,
|
||||
5183CCE5226F4DFA0010922C /* RefreshInterval.swift in Sources */,
|
||||
51C4529D22650A1000C03939 /* FaviconURLFinder.swift in Sources */,
|
||||
5142192A23522B5500E07E2C /* ImageViewController.swift in Sources */,
|
||||
51C45258226508CF00C03939 /* AppAssets.swift in Sources */,
|
||||
51FA73A82332BE880090D516 /* ExtractedArticle.swift in Sources */,
|
||||
51C4527C2265091600C03939 /* MasterTimelineDefaultCellLayout.swift in Sources */,
|
||||
|
@ -2874,6 +2881,7 @@
|
|||
5183CCE9226F68D90010922C /* AccountRefreshTimer.swift in Sources */,
|
||||
51C452882265093600C03939 /* AddFeedViewController.swift in Sources */,
|
||||
51934CCE2310792F006127BE /* ActivityManager.swift in Sources */,
|
||||
514219372352510100E07E2C /* ImageScrollView.swift in Sources */,
|
||||
DF999FF722B5AEFA0064B687 /* SafariView.swift in Sources */,
|
||||
51C4529B22650A1000C03939 /* FaviconDownloader.swift in Sources */,
|
||||
84DEE56622C32CA4005FC42C /* SmartFeedDelegate.swift in Sources */,
|
||||
|
|
|
@ -14,6 +14,18 @@ function linkHover() {
|
|||
});
|
||||
}
|
||||
|
||||
// Used to pop a resizable image view
|
||||
function imageWasClicked(img) {
|
||||
window.webkit.messageHandlers.imageWasClicked.postMessage(img.src);
|
||||
}
|
||||
|
||||
// Add the click listeners for images
|
||||
function imageClicks() {
|
||||
document.querySelectorAll("img").forEach(element => {
|
||||
element.addEventListener("click", function() { imageWasClicked(this) });
|
||||
});
|
||||
}
|
||||
|
||||
// Here we are making iframes responsive. Particularly useful for inline Youtube videos.
|
||||
function wrapFrames() {
|
||||
document.querySelectorAll("iframe").forEach(element => {
|
||||
|
@ -52,5 +64,6 @@ function render(data) {
|
|||
wrapFrames()
|
||||
stripStyles()
|
||||
linkHover()
|
||||
imageClicks()
|
||||
inlineVideos()
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ enum ArticleViewState: Equatable {
|
|||
}
|
||||
|
||||
class ArticleViewController: UIViewController {
|
||||
|
||||
private struct MessageName {
|
||||
static let imageWasClicked = "imageWasClicked"
|
||||
}
|
||||
|
||||
@IBOutlet private weak var nextUnreadBarButtonItem: UIBarButtonItem!
|
||||
@IBOutlet private weak var prevArticleBarButtonItem: UIBarButtonItem!
|
||||
|
@ -102,7 +106,10 @@ class ArticleViewController: UIViewController {
|
|||
self.webViewContainer.addChildAndPin(webView)
|
||||
webView.navigationDelegate = self
|
||||
webView.uiDelegate = self
|
||||
|
||||
|
||||
webView.configuration.userContentController.removeScriptMessageHandler(forName: MessageName.imageWasClicked)
|
||||
webView.configuration.userContentController.add(self, name: MessageName.imageWasClicked)
|
||||
|
||||
// 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
|
||||
webView.loadHTMLString(ArticleRenderer.page.html, baseURL: ArticleRenderer.page.baseURL)
|
||||
|
@ -337,6 +344,20 @@ extension ArticleViewController: WKUIDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: WKScriptMessageHandler
|
||||
|
||||
extension ArticleViewController: WKScriptMessageHandler {
|
||||
|
||||
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
|
||||
if message.name == MessageName.imageWasClicked, let link = message.body as? String, let url = URL(string: link) {
|
||||
let imageVC = UIStoryboard.main.instantiateController(ofType: ImageViewController.self)
|
||||
imageVC.url = url
|
||||
imageVC.modalPresentationStyle = .fullScreen
|
||||
present(imageVC, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private extension ArticleViewController {
|
||||
|
|
|
@ -0,0 +1,372 @@
|
|||
//
|
||||
// ImageScrollView.swift
|
||||
// Beauty
|
||||
//
|
||||
// Created by Nguyen Cong Huy on 1/19/16.
|
||||
// Copyright © 2016 Nguyen Cong Huy. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@objc public protocol ImageScrollViewDelegate: UIScrollViewDelegate {
|
||||
func imageScrollViewDidChangeOrientation(imageScrollView: ImageScrollView)
|
||||
func imageScrollViewDidGestureSwipeUp(imageScrollView: ImageScrollView)
|
||||
func imageScrollViewDidGestureSwipeDown(imageScrollView: ImageScrollView)
|
||||
}
|
||||
|
||||
open class ImageScrollView: UIScrollView {
|
||||
|
||||
@objc public enum ScaleMode: Int {
|
||||
case aspectFill
|
||||
case aspectFit
|
||||
case widthFill
|
||||
case heightFill
|
||||
}
|
||||
|
||||
@objc public enum Offset: Int {
|
||||
case begining
|
||||
case center
|
||||
}
|
||||
|
||||
static let kZoomInFactorFromMinWhenDoubleTap: CGFloat = 2
|
||||
|
||||
@objc open var imageContentMode: ScaleMode = .widthFill
|
||||
@objc open var initialOffset: Offset = .begining
|
||||
|
||||
@objc public private(set) var zoomView: UIImageView? = nil
|
||||
|
||||
@objc open weak var imageScrollViewDelegate: ImageScrollViewDelegate?
|
||||
|
||||
var imageSize: CGSize = CGSize.zero
|
||||
private var pointToCenterAfterResize: CGPoint = CGPoint.zero
|
||||
private var scaleToRestoreAfterResize: CGFloat = 1.0
|
||||
var maxScaleFromMinScale: CGFloat = 3.0
|
||||
|
||||
override open var frame: CGRect {
|
||||
willSet {
|
||||
if frame.equalTo(newValue) == false && newValue.equalTo(CGRect.zero) == false && imageSize.equalTo(CGSize.zero) == false {
|
||||
prepareToResize()
|
||||
}
|
||||
}
|
||||
|
||||
didSet {
|
||||
if frame.equalTo(oldValue) == false && frame.equalTo(CGRect.zero) == false && imageSize.equalTo(CGSize.zero) == false {
|
||||
recoverFromResizing()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
super.init(coder: aDecoder)
|
||||
|
||||
initialize()
|
||||
}
|
||||
|
||||
deinit {
|
||||
NotificationCenter.default.removeObserver(self)
|
||||
}
|
||||
|
||||
private func initialize() {
|
||||
showsVerticalScrollIndicator = false
|
||||
showsHorizontalScrollIndicator = false
|
||||
bouncesZoom = true
|
||||
decelerationRate = UIScrollView.DecelerationRate.fast
|
||||
delegate = self
|
||||
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(ImageScrollView.changeOrientationNotification), name: UIDevice.orientationDidChangeNotification, object: nil)
|
||||
}
|
||||
|
||||
@objc public func adjustFrameToCenter() {
|
||||
|
||||
guard let unwrappedZoomView = zoomView else {
|
||||
return
|
||||
}
|
||||
|
||||
var frameToCenter = unwrappedZoomView.frame
|
||||
|
||||
// center horizontally
|
||||
if frameToCenter.size.width < bounds.width {
|
||||
frameToCenter.origin.x = (bounds.width - frameToCenter.size.width) / 2
|
||||
}
|
||||
else {
|
||||
frameToCenter.origin.x = 0
|
||||
}
|
||||
|
||||
// center vertically
|
||||
if frameToCenter.size.height < bounds.height {
|
||||
frameToCenter.origin.y = (bounds.height - frameToCenter.size.height) / 2
|
||||
}
|
||||
else {
|
||||
frameToCenter.origin.y = 0
|
||||
}
|
||||
|
||||
unwrappedZoomView.frame = frameToCenter
|
||||
}
|
||||
|
||||
private func prepareToResize() {
|
||||
let boundsCenter = CGPoint(x: bounds.midX, y: bounds.midY)
|
||||
pointToCenterAfterResize = convert(boundsCenter, to: zoomView)
|
||||
|
||||
scaleToRestoreAfterResize = zoomScale
|
||||
|
||||
// If we're at the minimum zoom scale, preserve that by returning 0, which will be converted to the minimum
|
||||
// allowable scale when the scale is restored.
|
||||
if scaleToRestoreAfterResize <= minimumZoomScale + CGFloat(Float.ulpOfOne) {
|
||||
scaleToRestoreAfterResize = 0
|
||||
}
|
||||
}
|
||||
|
||||
private func recoverFromResizing() {
|
||||
setMaxMinZoomScalesForCurrentBounds()
|
||||
|
||||
// restore zoom scale, first making sure it is within the allowable range.
|
||||
let maxZoomScale = max(minimumZoomScale, scaleToRestoreAfterResize)
|
||||
zoomScale = min(maximumZoomScale, maxZoomScale)
|
||||
|
||||
// restore center point, first making sure it is within the allowable range.
|
||||
|
||||
// convert our desired center point back to our own coordinate space
|
||||
let boundsCenter = convert(pointToCenterAfterResize, to: zoomView)
|
||||
|
||||
// calculate the content offset that would yield that center point
|
||||
var offset = CGPoint(x: boundsCenter.x - bounds.size.width/2.0, y: boundsCenter.y - bounds.size.height/2.0)
|
||||
|
||||
// restore offset, adjusted to be within the allowable range
|
||||
let maxOffset = maximumContentOffset()
|
||||
let minOffset = minimumContentOffset()
|
||||
|
||||
var realMaxOffset = min(maxOffset.x, offset.x)
|
||||
offset.x = max(minOffset.x, realMaxOffset)
|
||||
|
||||
realMaxOffset = min(maxOffset.y, offset.y)
|
||||
offset.y = max(minOffset.y, realMaxOffset)
|
||||
|
||||
contentOffset = offset
|
||||
}
|
||||
|
||||
private func maximumContentOffset() -> CGPoint {
|
||||
return CGPoint(x: contentSize.width - bounds.width,y:contentSize.height - bounds.height)
|
||||
}
|
||||
|
||||
private func minimumContentOffset() -> CGPoint {
|
||||
return CGPoint.zero
|
||||
}
|
||||
|
||||
// MARK: - Set up
|
||||
|
||||
open func setup() {
|
||||
var topSupperView = superview
|
||||
|
||||
while topSupperView?.superview != nil {
|
||||
topSupperView = topSupperView?.superview
|
||||
}
|
||||
|
||||
// Make sure views have already layout with precise frame
|
||||
topSupperView?.layoutIfNeeded()
|
||||
}
|
||||
|
||||
// MARK: - Display image
|
||||
|
||||
@objc open func display(image: UIImage) {
|
||||
|
||||
if let zoomView = zoomView {
|
||||
zoomView.removeFromSuperview()
|
||||
}
|
||||
|
||||
zoomView = UIImageView(image: image)
|
||||
zoomView!.isUserInteractionEnabled = true
|
||||
addSubview(zoomView!)
|
||||
|
||||
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(doubleTapGestureRecognizer(_:)))
|
||||
tapGesture.numberOfTapsRequired = 2
|
||||
zoomView!.addGestureRecognizer(tapGesture)
|
||||
|
||||
let downSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeUpGestureRecognizer(_:)))
|
||||
downSwipeGesture.direction = .down
|
||||
zoomView!.addGestureRecognizer(downSwipeGesture)
|
||||
|
||||
let upSwipeGesture = UISwipeGestureRecognizer(target: self, action: #selector(swipeDownGestureRecognizer(_:)))
|
||||
upSwipeGesture.direction = .up
|
||||
zoomView!.addGestureRecognizer(upSwipeGesture)
|
||||
|
||||
configureImageForSize(image.size)
|
||||
}
|
||||
|
||||
private func configureImageForSize(_ size: CGSize) {
|
||||
imageSize = size
|
||||
contentSize = imageSize
|
||||
setMaxMinZoomScalesForCurrentBounds()
|
||||
zoomScale = minimumZoomScale
|
||||
|
||||
switch initialOffset {
|
||||
case .begining:
|
||||
contentOffset = CGPoint.zero
|
||||
case .center:
|
||||
let xOffset = contentSize.width < bounds.width ? 0 : (contentSize.width - bounds.width)/2
|
||||
let yOffset = contentSize.height < bounds.height ? 0 : (contentSize.height - bounds.height)/2
|
||||
|
||||
switch imageContentMode {
|
||||
case .aspectFit:
|
||||
contentOffset = CGPoint.zero
|
||||
case .aspectFill:
|
||||
contentOffset = CGPoint(x: xOffset, y: yOffset)
|
||||
case .heightFill:
|
||||
contentOffset = CGPoint(x: xOffset, y: 0)
|
||||
case .widthFill:
|
||||
contentOffset = CGPoint(x: 0, y: yOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setMaxMinZoomScalesForCurrentBounds() {
|
||||
// calculate min/max zoomscale
|
||||
let xScale = bounds.width / imageSize.width // the scale needed to perfectly fit the image width-wise
|
||||
let yScale = bounds.height / imageSize.height // the scale needed to perfectly fit the image height-wise
|
||||
|
||||
var minScale: CGFloat = 1
|
||||
|
||||
switch imageContentMode {
|
||||
case .aspectFill:
|
||||
minScale = max(xScale, yScale)
|
||||
case .aspectFit:
|
||||
minScale = min(xScale, yScale)
|
||||
case .widthFill:
|
||||
minScale = xScale
|
||||
case .heightFill:
|
||||
minScale = yScale
|
||||
}
|
||||
|
||||
|
||||
let maxScale = maxScaleFromMinScale*minScale
|
||||
|
||||
// don't let minScale exceed maxScale. (If the image is smaller than the screen, we don't want to force it to be zoomed.)
|
||||
if minScale > maxScale {
|
||||
minScale = maxScale
|
||||
}
|
||||
|
||||
maximumZoomScale = maxScale
|
||||
minimumZoomScale = minScale * 0.999 // the multiply factor to prevent user cannot scroll page while they use this control in UIPageViewController
|
||||
}
|
||||
|
||||
// MARK: - Gesture
|
||||
|
||||
@objc func doubleTapGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
|
||||
// zoom out if it bigger than middle scale point. Else, zoom in
|
||||
if zoomScale >= maximumZoomScale / 2.0 {
|
||||
setZoomScale(minimumZoomScale, animated: true)
|
||||
}
|
||||
else {
|
||||
let center = gestureRecognizer.location(in: gestureRecognizer.view)
|
||||
let zoomRect = zoomRectForScale(ImageScrollView.kZoomInFactorFromMinWhenDoubleTap * minimumZoomScale, center: center)
|
||||
zoom(to: zoomRect, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func swipeUpGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
|
||||
if gestureRecognizer.state == .ended {
|
||||
imageScrollViewDelegate?.imageScrollViewDidGestureSwipeUp(imageScrollView: self)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func swipeDownGestureRecognizer(_ gestureRecognizer: UIGestureRecognizer) {
|
||||
if gestureRecognizer.state == .ended {
|
||||
imageScrollViewDelegate?.imageScrollViewDidGestureSwipeDown(imageScrollView: self)
|
||||
}
|
||||
}
|
||||
|
||||
private func zoomRectForScale(_ scale: CGFloat, center: CGPoint) -> CGRect {
|
||||
var zoomRect = CGRect.zero
|
||||
|
||||
// the zoom rect is in the content view's coordinates.
|
||||
// at a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
|
||||
// as the zoom scale decreases, so more content is visible, the size of the rect grows.
|
||||
zoomRect.size.height = frame.size.height / scale
|
||||
zoomRect.size.width = frame.size.width / scale
|
||||
|
||||
// choose an origin so as to get the right center.
|
||||
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0)
|
||||
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0)
|
||||
|
||||
return zoomRect
|
||||
}
|
||||
|
||||
open func refresh() {
|
||||
if let image = zoomView?.image {
|
||||
display(image: image)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc func changeOrientationNotification() {
|
||||
// A weird bug that frames are not update right after orientation changed. Need delay a little bit with async.
|
||||
DispatchQueue.main.async {
|
||||
self.configureImageForSize(self.imageSize)
|
||||
self.imageScrollViewDelegate?.imageScrollViewDidChangeOrientation(imageScrollView: self)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension ImageScrollView: UIScrollViewDelegate {
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewDidScroll?(scrollView)
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewWillBeginDragging?(scrollView)
|
||||
}
|
||||
|
||||
public func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
|
||||
imageScrollViewDelegate?.scrollViewWillEndDragging?(scrollView, withVelocity: velocity, targetContentOffset: targetContentOffset)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
imageScrollViewDelegate?.scrollViewDidEndDragging?(scrollView, willDecelerate: decelerate)
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDecelerating(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewWillBeginDecelerating?(scrollView)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewDidEndDecelerating?(scrollView)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewDidEndScrollingAnimation?(scrollView)
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
|
||||
imageScrollViewDelegate?.scrollViewWillBeginZooming?(scrollView, with: view)
|
||||
}
|
||||
|
||||
public func scrollViewDidEndZooming(_ scrollView: UIScrollView, with view: UIView?, atScale scale: CGFloat) {
|
||||
imageScrollViewDelegate?.scrollViewDidEndZooming?(scrollView, with: view, atScale: scale)
|
||||
}
|
||||
|
||||
public func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
@available(iOS 11.0, *)
|
||||
public func scrollViewDidChangeAdjustedContentInset(_ scrollView: UIScrollView) {
|
||||
imageScrollViewDelegate?.scrollViewDidChangeAdjustedContentInset?(scrollView)
|
||||
}
|
||||
|
||||
public func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||
return zoomView
|
||||
}
|
||||
|
||||
public func scrollViewDidZoom(_ scrollView: UIScrollView) {
|
||||
adjustFrameToCenter()
|
||||
imageScrollViewDelegate?.scrollViewDidZoom?(scrollView)
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// ImageViewController.swift
|
||||
// NetNewsWire-iOS
|
||||
//
|
||||
// Created by Maurice Parker on 10/12/19.
|
||||
// Copyright © 2019 Ranchero Software. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class ImageViewController: UIViewController {
|
||||
|
||||
@IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
|
||||
@IBOutlet weak var imageScrollView: ImageScrollView!
|
||||
|
||||
private var dataTask: URLSessionDataTask? = nil
|
||||
var url: URL!
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
activityIndicatorView.isHidden = false
|
||||
activityIndicatorView.startAnimating()
|
||||
|
||||
imageScrollView.setup()
|
||||
imageScrollView.imageScrollViewDelegate = self
|
||||
imageScrollView.imageContentMode = .aspectFit
|
||||
imageScrollView.initialOffset = .center
|
||||
|
||||
dataTask = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
|
||||
guard let self = self else { return }
|
||||
if let data = data, let image = UIImage(data: data) {
|
||||
|
||||
DispatchQueue.main.async {
|
||||
self.activityIndicatorView.isHidden = true
|
||||
self.activityIndicatorView.stopAnimating()
|
||||
self.imageScrollView.display(image: image)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dataTask!.resume()
|
||||
}
|
||||
|
||||
@IBAction func done(_ sender: Any) {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: ImageScrollViewDelegate
|
||||
|
||||
extension ImageViewController: ImageScrollViewDelegate {
|
||||
|
||||
func imageScrollViewDidChangeOrientation(imageScrollView: ImageScrollView) {
|
||||
}
|
||||
|
||||
func imageScrollViewDidGestureSwipeUp(imageScrollView: ImageScrollView) {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
func imageScrollViewDidGestureSwipeDown(imageScrollView: ImageScrollView) {
|
||||
dismiss(animated: true)
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15504" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
|
||||
<device id="retina6_1" orientation="portrait" appearance="light"/>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15508"/>
|
||||
<capability name="Named colors" minToolsVersion="9.0"/>
|
||||
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
|
@ -226,6 +227,61 @@
|
|||
</objects>
|
||||
<point key="canvasLocation" x="900" y="-759"/>
|
||||
</scene>
|
||||
<!--Image View Controller-->
|
||||
<scene sceneID="TT4-oA-DBw">
|
||||
<objects>
|
||||
<viewController storyboardIdentifier="ImageViewController" id="vO9-a3-Dnu" customClass="ImageViewController" customModule="NetNewsWire" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<view key="view" contentMode="scaleToFill" id="w6Q-vH-063">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<scrollView verifyAmbiguity="off" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" translatesAutoresizingMaskIntoConstraints="NO" id="msG-pz-EKk" customClass="ImageScrollView" customModule="NetNewsWire" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
|
||||
<subviews>
|
||||
<activityIndicatorView opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" style="large" translatesAutoresizingMaskIntoConstraints="NO" id="iEh-n3-Vkg">
|
||||
<rect key="frame" x="188.5" y="390.5" width="37" height="37"/>
|
||||
</activityIndicatorView>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="iEh-n3-Vkg" firstAttribute="centerX" secondItem="msG-pz-EKk" secondAttribute="centerX" id="FSP-DY-Vax"/>
|
||||
<constraint firstItem="iEh-n3-Vkg" firstAttribute="centerY" secondItem="msG-pz-EKk" secondAttribute="centerY" id="rev-zC-wMY"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="contentLayoutGuide" id="phv-DN-krZ"/>
|
||||
<viewLayoutGuide key="frameLayoutGuide" id="NNU-C8-Fsz"/>
|
||||
</scrollView>
|
||||
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cXR-ll-xBx">
|
||||
<rect key="frame" x="0.0" y="44" width="44" height="44"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="44" id="6kc-Gw-KbZ"/>
|
||||
<constraint firstAttribute="width" constant="44" id="cBq-gs-WzN"/>
|
||||
</constraints>
|
||||
<color key="tintColor" name="primaryAccentColor"/>
|
||||
<state key="normal" image="multiply.circle.fill" catalog="system"/>
|
||||
<connections>
|
||||
<action selector="done:" destination="vO9-a3-Dnu" eventType="touchUpInside" id="tgd-ov-4Ft"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
|
||||
<constraints>
|
||||
<constraint firstItem="mbY-02-GFL" firstAttribute="bottom" secondItem="msG-pz-EKk" secondAttribute="bottom" id="AtA-bA-jDr"/>
|
||||
<constraint firstItem="mbY-02-GFL" firstAttribute="trailing" secondItem="msG-pz-EKk" secondAttribute="trailing" id="R49-qV-8nm"/>
|
||||
<constraint firstItem="msG-pz-EKk" firstAttribute="leading" secondItem="mbY-02-GFL" secondAttribute="leading" id="XN1-xN-hYS"/>
|
||||
<constraint firstItem="msG-pz-EKk" firstAttribute="top" secondItem="mbY-02-GFL" secondAttribute="top" id="p1a-s0-wdK"/>
|
||||
<constraint firstItem="cXR-ll-xBx" firstAttribute="leading" secondItem="mbY-02-GFL" secondAttribute="leading" id="vJs-LN-Ydd"/>
|
||||
<constraint firstItem="cXR-ll-xBx" firstAttribute="top" secondItem="mbY-02-GFL" secondAttribute="top" id="xVN-Qt-WYA"/>
|
||||
</constraints>
|
||||
<viewLayoutGuide key="safeArea" id="mbY-02-GFL"/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="activityIndicatorView" destination="iEh-n3-Vkg" id="xue-X1-awS"/>
|
||||
<outlet property="imageScrollView" destination="msG-pz-EKk" id="dGi-M6-dcO"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="ZPN-tH-JAG" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3056.521739130435" y="-759.375"/>
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="chevron.down" catalog="system" width="64" height="36"/>
|
||||
|
@ -233,8 +289,12 @@
|
|||
<image name="chevron.up" catalog="system" width="64" height="36"/>
|
||||
<image name="circle" catalog="system" width="64" height="60"/>
|
||||
<image name="gear" catalog="system" width="64" height="58"/>
|
||||
<image name="multiply.circle.fill" catalog="system" width="64" height="60"/>
|
||||
<image name="safari" catalog="system" width="64" height="60"/>
|
||||
<image name="square.and.arrow.up" catalog="system" width="56" height="64"/>
|
||||
<image name="star" catalog="system" width="64" height="58"/>
|
||||
<namedColor name="primaryAccentColor">
|
||||
<color red="0.031372549019607843" green="0.41568627450980394" blue="0.93333333333333335" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</namedColor>
|
||||
</resources>
|
||||
</document>
|
||||
|
|
Loading…
Reference in New Issue