diff --git a/README.md b/README.md index 69870f2..b192b6c 100644 --- a/README.md +++ b/README.md @@ -61,8 +61,9 @@ func createPhotoGallery() -> RIGImageGalleryViewController { "https://placehold.it/150x350", ].flatMap(URL.init(string:)) - let rigItems = urls.map { _ in - RIGImageGalleryItem(placeholderImage: UIImage(named: "placeholder")) + let rigItems: [RIGImageGalleryItem] = urls.map { _ in + RIGImageGalleryItem(placeholderImage: UIImage(named: "placeholder") ?? UIImage(), + isLoading: true) } let rigController = RIGImageGalleryViewController(images: rigItems) @@ -71,6 +72,7 @@ func createPhotoGallery() -> RIGImageGalleryViewController { let request = imageSession.dataTask(with: URLRequest(url: URL)) { [weak rigController] data, _, error in if let image = data.flatMap(UIImage.init), error == nil { rigController?.images[index].image = image + rigController?.images[index].isLoading = false } } request.resume() diff --git a/RIGImageGallery/RIGAutoCenteringScrollView.swift b/RIGImageGallery/RIGAutoCenteringScrollView.swift index d7af949..9810e91 100644 --- a/RIGImageGallery/RIGAutoCenteringScrollView.swift +++ b/RIGImageGallery/RIGAutoCenteringScrollView.swift @@ -8,56 +8,48 @@ import UIKit -class RIGAutoCenteringScrollView: UIScrollView { +open class RIGAutoCenteringScrollView: UIScrollView { - var allowZoom: Bool = false + internal var allowZoom: Bool = false - var baseInsets: UIEdgeInsets = UIEdgeInsets() { + internal var baseInsets: UIEdgeInsets = UIEdgeInsets() { didSet { updateZoomScale(preserveScale: true) } } - var zoomImage: UIImage? { + open var zoomImage: UIImage? { didSet { if oldValue === zoomImage { return } if let img = zoomImage { - let imageView: UIImageView - if let img = contentView { - imageView = img - } - else { - imageView = UIImageView() - contentView = imageView - addSubview(imageView) - } - imageView.frame = CGRect(origin: CGPoint(), size: img.size) - imageView.image = img + contentView.isHidden = false + contentView.image = img } else { - contentView?.removeFromSuperview() - contentView = nil + contentView.isHidden = true } updateZoomScale(preserveScale: false) } } - fileprivate var contentView: UIImageView? + fileprivate var contentView = UIImageView() - override init(frame: CGRect) { + public override init(frame: CGRect) { super.init(frame: frame) showsVerticalScrollIndicator = false showsHorizontalScrollIndicator = false + addSubview(contentView) + configureConstraints() delegate = self } - required init?(coder aDecoder: NSCoder) { + public required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override var frame: CGRect { + open override var frame: CGRect { didSet { updateZoomScale(preserveScale: true) } @@ -68,11 +60,13 @@ class RIGAutoCenteringScrollView: UIScrollView { extension RIGAutoCenteringScrollView { func toggleZoom(animated: Bool = true) { - if zoomScale != minimumZoomScale { - setZoomScale(minimumZoomScale, animated: animated) - } - else { - setZoomScale(maximumZoomScale, animated: animated) + if self.isUserInteractionEnabled { + if zoomScale != minimumZoomScale { + setZoomScale(minimumZoomScale, animated: animated) + } + else { + setZoomScale(maximumZoomScale, animated: animated) + } } } @@ -80,6 +74,16 @@ extension RIGAutoCenteringScrollView { private extension RIGAutoCenteringScrollView { + func configureConstraints() { + contentView.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + contentView.leadingAnchor.constraint(equalTo: leadingAnchor), + contentView.trailingAnchor.constraint(equalTo: trailingAnchor), + contentView.topAnchor.constraint(equalTo: topAnchor), + contentView.bottomAnchor.constraint(equalTo: bottomAnchor), + ]) + } + func updateZoomScale(preserveScale: Bool) { guard let image = zoomImage else { contentSize = frame.size @@ -88,6 +92,8 @@ private extension RIGAutoCenteringScrollView { setZoomScale(1, animated: false) return } + updateConstraintsIfNeeded() + layoutIfNeeded() let adjustedFrame = UIEdgeInsetsInsetRect(frame, baseInsets) @@ -137,17 +143,20 @@ private extension RIGAutoCenteringScrollView { } contentInset = UIEdgeInsets(top: vertical + baseInsets.top, left: horizontal + baseInsets.left, bottom: vertical + baseInsets.bottom, right: horizontal + baseInsets.right) + + updateConstraintsIfNeeded() + layoutIfNeeded() } } extension RIGAutoCenteringScrollView: UIScrollViewDelegate { - func viewForZooming(in scrollView: UIScrollView) -> UIView? { + open func viewForZooming(in scrollView: UIScrollView) -> UIView? { return allowZoom ? contentView : nil } - func scrollViewDidZoom(_ scrollView: UIScrollView) { + open func scrollViewDidZoom(_ scrollView: UIScrollView) { centerContent() } diff --git a/RIGImageGallery/RIGImageGalleryItem.swift b/RIGImageGallery/RIGImageGalleryItem.swift index 0967f66..de49b5a 100644 --- a/RIGImageGallery/RIGImageGalleryItem.swift +++ b/RIGImageGallery/RIGImageGalleryItem.swift @@ -19,11 +19,14 @@ public struct RIGImageGalleryItem: Equatable { public var placeholderImage: UIImage? /// The title of the image public var title: String? + // The loading state + public var isLoading: Bool - public init(image: UIImage? = nil, placeholderImage: UIImage? = nil, title: String? = nil) { + public init(image: UIImage? = nil, placeholderImage: UIImage? = nil, title: String? = nil, isLoading: Bool = false) { self.image = image self.placeholderImage = placeholderImage self.title = title + self.isLoading = isLoading } } diff --git a/RIGImageGallery/RIGImageGalleryViewController.swift b/RIGImageGallery/RIGImageGalleryViewController.swift index 0a0d13a..5a34cb8 100644 --- a/RIGImageGallery/RIGImageGalleryViewController.swift +++ b/RIGImageGallery/RIGImageGalleryViewController.swift @@ -10,10 +10,10 @@ import UIKit open class RIGImageGalleryViewController: UIPageViewController { - public typealias GalleryPositionUpdateHandler = (_ gallery: RIGImageGalleryViewController, _ position: Int, _ total: Int) -> () - public typealias ActionButtonPressedHandler = (_ gallery: RIGImageGalleryViewController, _ item: RIGImageGalleryItem) -> () - public typealias GalleryEventHandler = (RIGImageGalleryViewController) -> () - public typealias IndexUpdateHandler = (Int) -> () + public typealias GalleryPositionUpdateHandler = (_ gallery: RIGImageGalleryViewController, _ position: Int, _ total: Int) -> Void + public typealias ActionButtonPressedHandler = (_ gallery: RIGImageGalleryViewController, _ item: RIGImageGalleryItem) -> Void + public typealias GalleryEventHandler = (RIGImageGalleryViewController) -> Void + public typealias IndexUpdateHandler = (Int) -> Void /// An optional closure to execute if the action button is tapped open var actionButtonHandler: ActionButtonPressedHandler? @@ -181,6 +181,14 @@ open class RIGImageGalleryViewController: UIPageViewController { traitCollectionChangeHandler?(self) } + /// Allows subclasses of RIGImageGallery to customize the gallery page + /// + /// - Parameter viewerItem: The item to be displayed + /// - Returns: The view controller that will display the item + open func createNewPage(for viewerItem: RIGImageGalleryItem) -> UIViewController { + return RIGSingleImageViewController(viewerItem: viewerItem) + } + } extension RIGImageGalleryViewController: UIGestureRecognizerDelegate { @@ -237,16 +245,14 @@ extension RIGImageGalleryViewController: UIPageViewControllerDataSource { guard let index = indexOf(viewController: viewController), index < images.count - 1 else { return nil } - let zoomView = RIGSingleImageViewController(viewerItem: images[index + 1]) - return zoomView + return createNewPage(for: images[index + 1]) } public func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { guard let index = indexOf(viewController: viewController), index > 0 else { return nil } - let zoomView = RIGSingleImageViewController(viewerItem: images[index - 1]) - return zoomView + return createNewPage(for: images[index + -1]) } } diff --git a/RIGImageGallery/RIGSingleImageViewController.swift b/RIGImageGallery/RIGSingleImageViewController.swift index 3d7cc8c..0412ee4 100644 --- a/RIGImageGallery/RIGSingleImageViewController.swift +++ b/RIGImageGallery/RIGSingleImageViewController.swift @@ -8,34 +8,52 @@ import UIKit -class RIGSingleImageViewController: UIViewController { +open class RIGSingleImageViewController: UIViewController { - var viewerItem: RIGImageGalleryItem? { + open var viewerItem: RIGImageGalleryItem? { didSet { viewerItemUpdated() } } - let scrollView = RIGAutoCenteringScrollView() + open let scrollView = RIGAutoCenteringScrollView() + open var activityIndicator: UIActivityIndicatorView? { + didSet { + oldValue?.removeFromSuperview() + if let newValue = activityIndicator { + view.addSubview(newValue) + NSLayoutConstraint.activate([ + newValue.centerXAnchor.constraint(equalTo: view.layoutMarginsGuide.centerXAnchor), + newValue.centerYAnchor.constraint(equalTo: view.layoutMarginsGuide.centerYAnchor), + ]) + } + } + } - convenience init(viewerItem: RIGImageGalleryItem) { + public convenience init(viewerItem: RIGImageGalleryItem) { self.init() self.viewerItem = viewerItem viewerItemUpdated() } - override func loadView() { + open override func viewDidLoad() { + super.viewDidLoad() automaticallyAdjustsScrollViewInsets = false - view = scrollView - view.backgroundColor = .black view.clipsToBounds = true + view.addSubview(scrollView) + let indicatorView = UIActivityIndicatorView() + indicatorView.activityIndicatorViewStyle = .gray + indicatorView.hidesWhenStopped = true + self.activityIndicator = indicatorView + configureConstraints() } - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + open override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + viewerItemUpdated() } - override func viewDidDisappear(_ animated: Bool) { + open override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) scrollView.setZoomScale(scrollView.minimumZoomScale, animated: true) } @@ -45,8 +63,28 @@ class RIGSingleImageViewController: UIViewController { private extension RIGSingleImageViewController { func viewerItemUpdated() { + if viewerItem?.isLoading == true && activityIndicator?.isAnimating == false { + activityIndicator?.startAnimating() + } + else if viewerItem?.isLoading == false && activityIndicator?.isAnimating == true { + activityIndicator?.stopAnimating() + } scrollView.allowZoom = viewerItem?.image != nil - scrollView.zoomImage = viewerItem?.image ?? viewerItem?.placeholderImage + scrollView.isUserInteractionEnabled = viewerItem?.isLoading == false + if !view.frame.isEmpty { + scrollView.zoomImage = viewerItem?.image ?? viewerItem?.placeholderImage + } + scrollView.setZoomScale(scrollView.minimumZoomScale, animated: false) } + func configureConstraints() { + scrollView.translatesAutoresizingMaskIntoConstraints = false + activityIndicator?.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + scrollView.topAnchor.constraint(equalTo: view.topAnchor), + scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) + } } diff --git a/RIGImageGalleryDemo/View Controller/ViewController.swift b/RIGImageGalleryDemo/View Controller/ViewController.swift index f73b31e..fa36717 100644 --- a/RIGImageGalleryDemo/View Controller/ViewController.swift +++ b/RIGImageGalleryDemo/View Controller/ViewController.swift @@ -107,19 +107,23 @@ private extension ViewController { let urls = type(of: self).urls - let rigItems = urls.map { _ in - RIGImageGalleryItem(placeholderImage: UIImage(named: "placeholder") ?? UIImage()) + let rigItems: [RIGImageGalleryItem] = urls.map { url in + RIGImageGalleryItem(placeholderImage: #imageLiteral(resourceName: "placeholder"), + title: url.pathComponents.last ?? "", + isLoading: true) } let rigController = RIGImageGalleryViewController(images: rigItems) - for (index, URL) in urls.enumerated() { + for (index, URL) in urls.enumerated() { let completion = rigController.handleImageLoadAtIndex(index) - let request = imageSession.dataTask(with: URLRequest(url: URL), completionHandler: completion) + let request = imageSession.dataTask(with: URLRequest(url: URL), + completionHandler: completion) request.resume() } rigController.setCurrentImage(2, animated: false) + return rigController } @@ -127,7 +131,7 @@ private extension ViewController { let items: [UIImage] = ["1", "2", "3", "4", "5", "6"].flatMap(UIImage.init(named:)) - let rigItems = items.map { item in + let rigItems: [RIGImageGalleryItem] = items.map { item in RIGImageGalleryItem(image: item) } @@ -148,7 +152,7 @@ private extension ViewController { } private extension RIGImageGalleryViewController { - func handleImageLoadAtIndex(_ index: Int) -> ((Data?, URLResponse?, Error?) -> ()) { + func handleImageLoadAtIndex(_ index: Int) -> ((Data?, URLResponse?, Error?) -> Void) { return { [weak self] (data: Data?, response: URLResponse?, error: Error?) in guard let image = data.flatMap(UIImage.init), error == nil else { if let error = error { @@ -156,9 +160,8 @@ private extension RIGImageGalleryViewController { } return } - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - self?.images[index].image = image - } + self?.images[index].isLoading = false + self?.images[index].image = image } } }