diff --git a/Example/Optik/Images.xcassets/AppIcon.appiconset/Contents.json b/Example/Optik/Images.xcassets/AppIcon.appiconset/Contents.json index d3942e9..b8236c6 100644 --- a/Example/Optik/Images.xcassets/AppIcon.appiconset/Contents.json +++ b/Example/Optik/Images.xcassets/AppIcon.appiconset/Contents.json @@ -1,5 +1,15 @@ { "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, { "idiom" : "iphone", "size" : "29x29", @@ -35,4 +45,4 @@ "version" : 1, "author" : "xcode" } -} +} \ No newline at end of file diff --git a/Example/Optik/Images.xcassets/Contents.json b/Example/Optik/Images.xcassets/Contents.json new file mode 100644 index 0000000..da4a164 --- /dev/null +++ b/Example/Optik/Images.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Optik/Images.xcassets/heart-empty.imageset/Contents.json b/Example/Optik/Images.xcassets/heart-empty.imageset/Contents.json new file mode 100644 index 0000000..16bd039 --- /dev/null +++ b/Example/Optik/Images.xcassets/heart-empty.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "heart.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Optik/Images.xcassets/heart-empty.imageset/heart.pdf b/Example/Optik/Images.xcassets/heart-empty.imageset/heart.pdf new file mode 100644 index 0000000..0b103aa Binary files /dev/null and b/Example/Optik/Images.xcassets/heart-empty.imageset/heart.pdf differ diff --git a/Example/Optik/Images.xcassets/heart-full.imageset/Contents.json b/Example/Optik/Images.xcassets/heart-full.imageset/Contents.json new file mode 100644 index 0000000..7e5f41f --- /dev/null +++ b/Example/Optik/Images.xcassets/heart-full.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "heart-fill.pdf" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Example/Optik/Images.xcassets/heart-full.imageset/heart-fill.pdf b/Example/Optik/Images.xcassets/heart-full.imageset/heart-fill.pdf new file mode 100644 index 0000000..d995cfd Binary files /dev/null and b/Example/Optik/Images.xcassets/heart-full.imageset/heart-fill.pdf differ diff --git a/Example/Optik/ViewController.swift b/Example/Optik/ViewController.swift index db83faf..d72d29c 100644 --- a/Example/Optik/ViewController.swift +++ b/Example/Optik/ViewController.swift @@ -45,7 +45,7 @@ internal final class ViewController: UIViewController { @IBAction private func presentLocalImageViewer(_ sender: UIButton) { let viewController = Optik.imageViewer(withImages: localImages, initialImageDisplayIndex: currentLocalImageIndex, - delegate: self) + delegate: self, enablePageControl: true) present(viewController, animated: true, completion: nil) } @@ -62,7 +62,7 @@ internal final class ViewController: UIViewController { let urls = [url1, url2, url3, url4] let imageDownloader = AlamofireImageDownloader() - let viewController = Optik.imageViewer(withURLs: urls, imageDownloader: imageDownloader) + let viewController = Optik.imageViewer(withURLs: urls, imageDownloader: imageDownloader, enablePageControl: true) present(viewController, animated: true, completion: nil) } @@ -82,4 +82,20 @@ extension ViewController: ImageViewerDelegate { currentLocalImageIndex = index } + func actionButtonTapped(button: UIButton, at index: Int) { + if button.currentImage == UIImage(named: "heart-full")! { + button.setImage(UIImage(named: "heart-empty"), for: .normal) + } else { + button.setImage(UIImage(named: "heart-full"), for: .normal) + } + } + + func imageForActionButton (at index: Int) -> UIImage { + if index % 2 == 0 { + return UIImage(named: "heart-full")! + } else { + return UIImage(named: "heart-empty")! + } + } + } diff --git a/Optik/Classes/ActionButtonPosition.swift b/Optik/Classes/ActionButtonPosition.swift new file mode 100644 index 0000000..4f37fb1 --- /dev/null +++ b/Optik/Classes/ActionButtonPosition.swift @@ -0,0 +1,34 @@ +// +// ActionButtonPosition.swift +// Pods +// +// Created by Rodrigo Leite on 28/11/16. +// +// + +import Foundation + +/** + Defines the position of the action button. + + - topLeading: action button is constrained to the top and leading anchors of its superview. + - topTrailing: action button is constrained to the top and trailing anchors of its superview. + */ +public enum ActionButtonPosition { + + case bottomLeading + + func xAnchorAttribute() -> NSLayoutAttribute { + switch self { + case .bottomLeading: + return .leading + } + } + + func yAnchorAttribute() -> NSLayoutAttribute { + switch self { + case .bottomLeading: + return .bottom + } + } +} diff --git a/Optik/Classes/AlbumViewController.swift b/Optik/Classes/AlbumViewController.swift index 9d00651..e11536d 100644 --- a/Optik/Classes/AlbumViewController.swift +++ b/Optik/Classes/AlbumViewController.swift @@ -58,34 +58,46 @@ internal final class AlbumViewController: UIViewController { return viewControllers[0] as? ImageViewController } - private var imageData: ImageData + fileprivate var imageData: ImageData private var initialImageDisplayIndex: Int private var activityIndicatorColor: UIColor? private var dismissButtonImage: UIImage? private var dismissButtonPosition: DismissButtonPosition - + private var actionButtonPosition: ActionButtonPosition private var cachedRemoteImages: [URL: UIImage] = [:] private var viewDidAppear: Bool = false private var transitionController: TransitionController = TransitionController() + fileprivate var pageControl: UIPageControl? + // MARK: - Init/Deinit init(imageData: ImageData, initialImageDisplayIndex: Int, activityIndicatorColor: UIColor?, dismissButtonImage: UIImage?, - dismissButtonPosition: DismissButtonPosition) { + dismissButtonPosition: DismissButtonPosition, + enablePageControl: Bool, + actionButtonPosition: ActionButtonPosition) { self.imageData = imageData self.initialImageDisplayIndex = initialImageDisplayIndex self.activityIndicatorColor = activityIndicatorColor self.dismissButtonImage = dismissButtonImage self.dismissButtonPosition = dismissButtonPosition + self.actionButtonPosition = actionButtonPosition - pageViewController = UIPageViewController(transitionStyle: .scroll, - navigationOrientation: .horizontal, + if enablePageControl { + pageViewController = UIPageViewController(transitionStyle: .scroll, + navigationOrientation: .vertical, options: [UIPageViewControllerOptionInterPageSpacingKey : Constants.SpacingBetweenImages]) + pageControl = UIPageControl() + } else { + pageViewController = UIPageViewController(transitionStyle: .scroll, + navigationOrientation: .horizontal, + options: [UIPageViewControllerOptionInterPageSpacingKey : Constants.SpacingBetweenImages]) + } super.init(nibName: nil, bundle: nil) @@ -115,6 +127,8 @@ internal final class AlbumViewController: UIViewController { self.setNeedsStatusBarAppearanceUpdate() }) } + updateActionButtonImage(at: initialImageDisplayIndex) + } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { @@ -147,6 +161,7 @@ internal final class AlbumViewController: UIViewController { didMove(toParentViewController: pageViewController) setupDismissButton() + setupPageControl() setupPanGestureRecognizer() } @@ -163,6 +178,56 @@ internal final class AlbumViewController: UIViewController { } } + private func setupPageControl() { + + if let page = pageControl { + page.currentPage = 0 + page.pageIndicatorTintColor = UIColor.red + page.translatesAutoresizingMaskIntoConstraints = false + page.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI/2)) + view.addSubview(page) + + view.addConstraint( + NSLayoutConstraint(item: page, + attribute: .trailing, + relatedBy: .equal, + toItem: view, + attribute: .trailing, + multiplier: 1, + constant: -10) + ) + view.addConstraint( + NSLayoutConstraint(item: page, + attribute: .centerY, + relatedBy: .equal, + toItem: view, + attribute: .centerY, + multiplier: 1, + constant: 0) + ) + view.addConstraint( + NSLayoutConstraint(item: page, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .notAnAttribute, + multiplier: 1, + constant: 30) + ) + view.addConstraint( + NSLayoutConstraint(item: page, + attribute: .height, + relatedBy: .equal, + toItem: nil, + attribute: .notAnAttribute, + multiplier: 1, + constant: 25) + ) + } + + } + + private func setupDismissButton() { let dismissButton = UIButton(type: .custom) dismissButton.translatesAutoresizingMaskIntoConstraints = false @@ -226,13 +291,18 @@ internal final class AlbumViewController: UIViewController { return nil } - return ImageViewController(image: images[index], index: index) + pageControl?.numberOfPages = images.count + let imageViewController = ImageViewController(image: images[index], index: index, actionButtonPosition: .bottomLeading) + imageViewController.actionButton?.addTarget(self, action: #selector(AlbumViewController.didSelectImage(_:)), for: .touchUpInside) + return imageViewController + case .remote(let urls, let imageDownloader): guard index >= 0 && index < urls.count else { return nil } - let imageViewController = ImageViewController(activityIndicatorColor: activityIndicatorColor, index: index) + pageControl?.numberOfPages = urls.count + let imageViewController = ImageViewController(activityIndicatorColor: activityIndicatorColor, index: index, actionButtonPosition: actionButtonPosition) let url = urls[index] if let image = cachedRemoteImages[url] { @@ -245,11 +315,22 @@ internal final class AlbumViewController: UIViewController { imageViewController.image = image }) } + + imageViewController.actionButton?.addTarget(self, action: #selector(AlbumViewController.didSelectImage(_:)), for: .touchUpInside) return imageViewController } } + fileprivate func updateActionButtonImage(at index: Int) { + if let imageViewController = pageViewController.viewControllers?.first as? ImageViewController{ + let actionButton = imageViewController.actionButton + let image = imageViewerDelegate?.imageForActionButton(at: index) + actionButton?.setImage(image, for: .normal) + } + } + + @objc private func didTapDismissButton(_ sender: UIButton) { dismiss(animated: true, completion: nil) } @@ -258,6 +339,11 @@ internal final class AlbumViewController: UIViewController { transitionController.didPan(withGestureRecognizer: sender, sourceView: view) } + @objc private func didSelectImage(_ sender: UIButton) { + if let currentImageIndex = currentImageViewController?.index { + imageViewerDelegate?.actionButtonTapped(button: sender, at: currentImageIndex) + } + } } // MARK: - Protocol conformance @@ -306,6 +392,30 @@ extension AlbumViewController: UIPageViewControllerDelegate { if let currentImageIndex = currentImageViewController?.index { imageViewerDelegate?.imageViewerDidDisplayImage(at: currentImageIndex) + pageControl?.currentPage = currentImageIndex + self.updateActionButtonImage(at: currentImageIndex) + + } + } + + func pageViewController(pageViewController: UIPageViewController, spineLocationForInterfaceOrientation orientation: UIInterfaceOrientation) -> UIPageViewControllerSpineLocation { + return .max + } + + func presentationCountForPageViewController(pageViewController: UIPageViewController) -> Int { + return numberOfImages() + } + + func presentationIndexForPageViewController(pageViewController: UIPageViewController) -> Int { + return currentImageViewController?.index ?? 0 + } + + private func numberOfImages() -> Int { + switch imageData { + case .local(images: let images): + return images.count + case .remote(urls: let urls, imageDownloader: _): + return urls.count } } diff --git a/Optik/Classes/ImageViewController.swift b/Optik/Classes/ImageViewController.swift index 681a0a4..2189cde 100644 --- a/Optik/Classes/ImageViewController.swift +++ b/Optik/Classes/ImageViewController.swift @@ -30,13 +30,15 @@ internal final class ImageViewController: UIViewController { } } private(set) var imageView: UIImageView? - + var actionButton: UIButton? + let index: Int // MARK: - Private properties private var activityIndicatorColor: UIColor? - + private var actionButtonPosition: ActionButtonPosition? + private var scrollView: UIScrollView? { didSet { guard let scrollView = scrollView else { @@ -58,15 +60,17 @@ internal final class ImageViewController: UIViewController { } private var effectiveImageSize: CGSize? - + // MARK: - Init/Deinit - init(image: UIImage? = nil, activityIndicatorColor: UIColor? = nil, index: Int) { + init(image: UIImage? = nil, activityIndicatorColor: UIColor? = nil, index: Int, actionButtonPosition: ActionButtonPosition) { self.image = image self.activityIndicatorColor = activityIndicatorColor self.index = index - + self.actionButtonPosition = actionButtonPosition super.init(nibName: nil, bundle: nil) + self.actionButton = UIButton() + } required init?(coder aDecoder: NSCoder) { @@ -159,6 +163,57 @@ internal final class ImageViewController: UIViewController { self.activityIndicatorView = activityIndicatorView } + if let button = actionButton { + button.translatesAutoresizingMaskIntoConstraints = false + button.imageView?.contentMode = .scaleAspectFill + + let xAnchorAttribute = actionButtonPosition?.xAnchorAttribute() + let yAnchorAttribute = actionButtonPosition?.yAnchorAttribute() + + view.addSubview(button) + if let xAnchor = xAnchorAttribute, + let yAnchor = yAnchorAttribute { + + view.addConstraint( + NSLayoutConstraint(item: button, + attribute: yAnchor, + relatedBy: .equal, + toItem: view, + attribute: yAnchor, + multiplier: 1, + constant: -20) + ) + view.addConstraint( + NSLayoutConstraint(item: button, + attribute: xAnchor, + relatedBy: .equal, + toItem: view, + attribute: xAnchor, + multiplier: 1, + constant: 20) + ) + view.addConstraint( + NSLayoutConstraint(item: button, + attribute: .width, + relatedBy: .equal, + toItem: nil, + attribute: .width, + multiplier: 1, + constant: 44) + ) + view.addConstraint( + NSLayoutConstraint(item: button, + attribute: .height, + relatedBy: .equal, + toItem: nil, + attribute: .height, + multiplier: 1, + constant: 44) + ) + } + + } + setupTapGestureRecognizer() } diff --git a/Optik/Classes/ImageViewerDelegate.swift b/Optik/Classes/ImageViewerDelegate.swift index 831a532..8e78482 100644 --- a/Optik/Classes/ImageViewerDelegate.swift +++ b/Optik/Classes/ImageViewerDelegate.swift @@ -30,4 +30,20 @@ public protocol ImageViewerDelegate: class { */ func imageViewerDidDisplayImage(at index: Int) + /** + Tells when the user touch in the action button + + - parameter index: Index of the image. + */ + func actionButtonTapped(button: UIButton, at index: Int) + + + /** + Ask delegate for the image that should appear in the button when the image appears. + - parameter index: index of the image + + - return: UIImage + */ + func imageForActionButton (at index: Int) -> UIImage + } diff --git a/Optik/Classes/Optik.swift b/Optik/Classes/Optik.swift index fc54d43..7d57555 100644 --- a/Optik/Classes/Optik.swift +++ b/Optik/Classes/Optik.swift @@ -25,11 +25,15 @@ public func imageViewer(withImages images: [UIImage], initialImageDisplayIndex: Int = 0, delegate: ImageViewerDelegate? = nil, dismissButtonImage: UIImage? = nil, - dismissButtonPosition: DismissButtonPosition = .topLeading) -> UIViewController { + dismissButtonPosition: DismissButtonPosition = .topLeading, + enablePageControl: Bool, + actionButtonPosition: ActionButtonPosition = .bottomLeading) -> UIViewController { let albumViewController = imageViewer(withData: .local(images: images), initialImageDisplayIndex: initialImageDisplayIndex, dismissButtonImage: dismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + enablePageControl: enablePageControl, + actionButtonPosition: actionButtonPosition) albumViewController.modalPresentationStyle = .custom albumViewController.imageViewerDelegate = delegate @@ -53,12 +57,16 @@ public func imageViewer(withURLs urls: [URL], imageDownloader: ImageDownloader, activityIndicatorColor: UIColor = .white, dismissButtonImage: UIImage? = nil, - dismissButtonPosition: DismissButtonPosition = .topLeading) -> UIViewController { + dismissButtonPosition: DismissButtonPosition = .topLeading, + enablePageControl: Bool, + actionButtonPosition: ActionButtonPosition = .bottomLeading) -> UIViewController { return imageViewer(withData: .remote(urls: urls, imageDownloader: imageDownloader), initialImageDisplayIndex: initialImageDisplayIndex, activityIndicatorColor: activityIndicatorColor, dismissButtonImage: dismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + enablePageControl: enablePageControl, + actionButtonPosition: actionButtonPosition) } // MARK: - Private functions @@ -67,7 +75,9 @@ private func imageViewer(withData imageData: ImageData, initialImageDisplayIndex: Int, activityIndicatorColor: UIColor? = nil, dismissButtonImage: UIImage?, - dismissButtonPosition: DismissButtonPosition) -> AlbumViewController { + dismissButtonPosition: DismissButtonPosition, + enablePageControl: Bool, + actionButtonPosition: ActionButtonPosition) -> AlbumViewController { let bundle = Bundle(for: AlbumViewController.self) let defaultDismissButtonImage = UIImage(named: "DismissIcon", in: bundle, compatibleWith: nil) @@ -75,5 +85,7 @@ private func imageViewer(withData imageData: ImageData, initialImageDisplayIndex: initialImageDisplayIndex, activityIndicatorColor: activityIndicatorColor, dismissButtonImage: dismissButtonImage ?? defaultDismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + enablePageControl: enablePageControl, + actionButtonPosition: actionButtonPosition) }