diff --git a/Example/Optik.xcodeproj/project.pbxproj b/Example/Optik.xcodeproj/project.pbxproj index dfa10f1..d5987cd 100644 --- a/Example/Optik.xcodeproj/project.pbxproj +++ b/Example/Optik.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 0832454B1DA5BE40006F492E /* CustomToolbarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0832454A1DA5BE40006F492E /* CustomToolbarController.swift */; }; 486216831CE76A3A00686557 /* cats.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 4862167F1CE76A3A00686557 /* cats.jpg */; }; 486216841CE76A3A00686557 /* life.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 486216801CE76A3A00686557 /* life.jpg */; }; 486216861CE76A3A00686557 /* whiteboard.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 486216821CE76A3A00686557 /* whiteboard.jpg */; }; @@ -33,6 +34,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 0832454A1DA5BE40006F492E /* CustomToolbarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomToolbarController.swift; sourceTree = ""; }; 380B0038E4A48ADE86928EF5 /* Pods-Optik_Tests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Optik_Tests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Optik_Tests/Pods-Optik_Tests.debug.xcconfig"; sourceTree = ""; }; 4862167F1CE76A3A00686557 /* cats.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = cats.jpg; sourceTree = ""; }; 486216801CE76A3A00686557 /* life.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = life.jpg; sourceTree = ""; }; @@ -116,6 +118,7 @@ children = ( 607FACD51AFB9204008FA782 /* AppDelegate.swift */, 607FACD71AFB9204008FA782 /* ViewController.swift */, + 0832454A1DA5BE40006F492E /* CustomToolbarController.swift */, 486216891CE76C4100686557 /* AlamofireImageDownloader.swift */, 607FACD91AFB9204008FA782 /* Main.storyboard */, 607FACDC1AFB9204008FA782 /* Images.xcassets */, @@ -388,6 +391,7 @@ files = ( 4862168A1CE76C4100686557 /* AlamofireImageDownloader.swift in Sources */, 607FACD81AFB9204008FA782 /* ViewController.swift in Sources */, + 0832454B1DA5BE40006F492E /* CustomToolbarController.swift in Sources */, 607FACD61AFB9204008FA782 /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Example/Optik/CustomToolbarController.swift b/Example/Optik/CustomToolbarController.swift new file mode 100644 index 0000000..bcf402b --- /dev/null +++ b/Example/Optik/CustomToolbarController.swift @@ -0,0 +1,32 @@ +// +// CustomToolbarController.swift +// Optik +// +// Created by Daniel Vancura on 10/5/16. +// +// + +import Optik +import UIKit + +internal final class CustomToolbarController: ToolbarController { + + let navigationBarHidesOnTap = true + + let toolbarHidesOnTap = true + + let navigationItem: UINavigationItem = UINavigationItem(title: "Photos") + + func setupNavigationBar(navigationBar: UINavigationBar, forUseIn albumViewController: UIViewController) { + navigationItem.rightBarButtonItem = UIBarButtonItem(barButtonSystemItem: .Action, target: self, action: #selector(share)) + } + + func setupToolbar(toolbar: UIToolbar, forUseIn albumViewController: UIViewController) { + toolbar.items = [UIBarButtonItem(title: "Nop", style: .Done, target: nil, action: nil)] + } + + @objc private func share() { + + } + +} diff --git a/Example/Optik/ViewController.swift b/Example/Optik/ViewController.swift index e0b94c6..66f1c57 100644 --- a/Example/Optik/ViewController.swift +++ b/Example/Optik/ViewController.swift @@ -45,7 +45,8 @@ internal final class ViewController: UIViewController { @IBAction private func presentLocalImageViewer(sender: UIButton) { let viewController = Optik.imageViewer(withImages: localImages, initialImageDisplayIndex: currentLocalImageIndex, - delegate: self) + delegate: self, + toolbarController: CustomToolbarController()) presentViewController(viewController, animated: true, completion: nil) } diff --git a/Optik/Classes/AlbumViewController.swift b/Optik/Classes/AlbumViewController.swift index e3e76ec..aefd3e2 100644 --- a/Optik/Classes/AlbumViewController.swift +++ b/Optik/Classes/AlbumViewController.swift @@ -16,9 +16,14 @@ internal final class AlbumViewController: UIViewController { static let DismissButtonDimension: CGFloat = 60 static let TransitionAnimationDuration: NSTimeInterval = 0.3 + + static let toolbarHeight: CGFloat = 44.0 + static let navbarHeight: CGFloat = 44.0 } // MARK: - Properties + + var toolbarController: ToolbarController? weak var imageViewerDelegate: ImageViewerDelegate? { didSet { @@ -48,7 +53,7 @@ internal final class AlbumViewController: UIViewController { } // MARK: Private properties - + private var pageViewController: UIPageViewController private var currentImageViewController: ImageViewController? { guard let viewControllers = pageViewController.viewControllers where viewControllers.count == 1 else { @@ -57,6 +62,43 @@ internal final class AlbumViewController: UIViewController { return viewControllers[0] as? ImageViewController } + + private var navigationBar: UINavigationBar? + + private var toolbar: UIToolbar? + + private var navigationBarHidden: Bool = false { + didSet { + UIView.animateWithDuration(Constants.TransitionAnimationDuration, animations: { + if self.navigationBarHidden { + self.navigationBar?.alpha = 0.0 + self.navigationBar?.transform = CGAffineTransformMakeTranslation(0, -Constants.navbarHeight) + } else { + self.navigationBar?.alpha = 1.0 + self.navigationBar?.transform = CGAffineTransformIdentity + } + }) + } + } + + private var toolbarHidden: Bool = false { + didSet { + UIView.animateWithDuration(Constants.TransitionAnimationDuration, animations: { + if self.toolbarHidden { + self.toolbar?.alpha = 0.0 + self.toolbar?.transform = CGAffineTransformMakeTranslation(0, Constants.toolbarHeight) + } else { + self.toolbar?.alpha = 1.0 + self.toolbar?.transform = CGAffineTransformIdentity + } + + let scale = (self.pageViewController.view.bounds.height + - (self.navigationBarHidden ? CGFloat(0) : Constants.navbarHeight) + - (self.toolbarHidden ? CGFloat(0) : Constants.toolbarHeight)) / self.view.bounds.height + self.pageViewController.view.transform = CGAffineTransformMakeScale(scale, scale) + }) + } + } private var imageData: ImageData private var initialImageDisplayIndex: Int @@ -75,14 +117,17 @@ internal final class AlbumViewController: UIViewController { initialImageDisplayIndex: Int, activityIndicatorColor: UIColor?, dismissButtonImage: UIImage?, - dismissButtonPosition: DismissButtonPosition) { + dismissButtonPosition: DismissButtonPosition, + toolbarController: ToolbarController? = nil) { self.imageData = imageData self.initialImageDisplayIndex = initialImageDisplayIndex self.activityIndicatorColor = activityIndicatorColor self.dismissButtonImage = dismissButtonImage self.dismissButtonPosition = dismissButtonPosition - + + self.toolbarController = toolbarController + pageViewController = UIPageViewController(transitionStyle: .Scroll, navigationOrientation: .Horizontal, options: [UIPageViewControllerOptionInterPageSpacingKey : Constants.SpacingBetweenImages]) @@ -106,6 +151,9 @@ internal final class AlbumViewController: UIViewController { override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) + + navigationBarHidden = false + toolbarHidden = false // HACK: UIKit doesn't animate status bar transition on iOS 9. So, manually animate it. if !viewDidAppear { @@ -145,14 +193,15 @@ internal final class AlbumViewController: UIViewController { addChildViewController(pageViewController) view.addSubview(pageViewController.view) didMoveToParentViewController(pageViewController) - - setupDismissButton() + + setupToolbars() setupPanGestureRecognizer() } private func setupPageViewController() { pageViewController.dataSource = self pageViewController.delegate = self + pageViewController.view.clipsToBounds = false // Set up initial image view controller. if let imageViewController = imageViewController(forIndex: initialImageDisplayIndex) { @@ -162,54 +211,74 @@ internal final class AlbumViewController: UIViewController { completion: nil) } } - - private func setupDismissButton() { - let dismissButton = UIButton(type: .Custom) - dismissButton.translatesAutoresizingMaskIntoConstraints = false - dismissButton.setImage(dismissButtonImage, forState: .Normal) - dismissButton.addTarget(self, action: #selector(AlbumViewController.didTapDismissButton(_:)), forControlEvents: .TouchUpInside) - - let xAnchorAttribute = dismissButtonPosition.xAnchorAttribute() - let yAnchorAttribute = dismissButtonPosition.yAnchorAttribute() - - view.addSubview(dismissButton) - - view.addConstraint( - NSLayoutConstraint(item: dismissButton, - attribute: xAnchorAttribute, - relatedBy: .Equal, - toItem: view, - attribute: xAnchorAttribute, - multiplier: 1, - constant: 0) - ) - view.addConstraint( - NSLayoutConstraint(item: dismissButton, - attribute: yAnchorAttribute, - relatedBy: .Equal, - toItem: view, - attribute: yAnchorAttribute, - multiplier: 1, - constant: 0) - ) - view.addConstraint( - NSLayoutConstraint(item: dismissButton, - attribute: .Width, - relatedBy: .Equal, - toItem: nil, - attribute: .NotAnAttribute, - multiplier: 1, - constant: Constants.DismissButtonDimension) - ) - view.addConstraint( - NSLayoutConstraint(item: dismissButton, - attribute: .Height, - relatedBy: .Equal, - toItem: nil, - attribute: .NotAnAttribute, - multiplier: 1, - constant: Constants.DismissButtonDimension) - ) + + private func setupToolbars() { + let navigationBar = UINavigationBar(frame: CGRect( + x: 0, + y: 0, + width: view.bounds.width, + height: Constants.navbarHeight + )) + let toolbar = UIToolbar(frame: CGRect( + x: 0, + y: 0, + width: view.bounds.width, + height: Constants.toolbarHeight + )) + + let toolbarController = (self.toolbarController ?? DefaultToolbarController()) + + let dismissButton = UIBarButtonItem(image: dismissButtonImage, style: .Done, target: self, action: #selector(AlbumViewController.didTapDismissButton(_:))) + switch dismissButtonPosition { + case .TopLeading: + toolbarController.navigationItem.leftBarButtonItem = dismissButton + case .TopTrailing: + toolbarController.navigationItem.rightBarButtonItem = dismissButton + } + + navigationBar.setItems([toolbarController.navigationItem], animated: false) + toolbarController.setupNavigationBar(navigationBar, forUseIn: self) + toolbarController.setupToolbar(toolbar, forUseIn: self) + + let hideGestureRecognizer = UITapGestureRecognizer() + + if toolbarController.navigationBarHidesOnTap { + hideGestureRecognizer.addTarget(self, action: #selector(toggleNavigationBar)) + } + + if toolbarController.toolbarHidesOnTap { + hideGestureRecognizer.addTarget(self, action: #selector(toggleToolbar)) + } + + view.addGestureRecognizer(hideGestureRecognizer) + + view.addSubview(navigationBar) + view.addSubview(toolbar) + + navigationBar.translatesAutoresizingMaskIntoConstraints = false + toolbar.translatesAutoresizingMaskIntoConstraints = false + + setAttributeEqual(.Leading, for: view, from: view, to: navigationBar, with: 0) + setAttributeEqual(.Trailing, for: view, from: view, to: navigationBar, with: 0) + setAttributeEqual(.Top, for: view, from: topLayoutGuide, to: navigationBar, with: 0) + + setAttributeEqual(.Leading, for: view, from: view, to: toolbar, with: 0) + setAttributeEqual(.Trailing, for: view, from: view, to: toolbar, with: 0) + setAttributeEqual(.Bottom, for: view, from: bottomLayoutGuide, to: toolbar, with: 0) + + self.navigationBar = navigationBar + self.toolbar = toolbar + } + + private func setAttributeEqual(attribute: NSLayoutAttribute, for view: UIView, from source: AnyObject, to destination: AnyObject, with distance: CGFloat) { + view.addConstraint(NSLayoutConstraint( + item: source, + attribute: attribute, + relatedBy: NSLayoutRelation.Equal, + toItem: destination, + attribute: attribute, + multiplier: 1.0, + constant: distance)) } private func setupPanGestureRecognizer() { @@ -257,6 +326,14 @@ internal final class AlbumViewController: UIViewController { @objc private func didPan(sender: UIPanGestureRecognizer) { transitionController.didPan(withGestureRecognizer: sender, sourceView: view) } + + @objc private func toggleNavigationBar() { + navigationBarHidden = !navigationBarHidden + } + + @objc private func toggleToolbar() { + toolbarHidden = !toolbarHidden + } } @@ -308,5 +385,26 @@ extension AlbumViewController: UIPageViewControllerDelegate { imageViewerDelegate?.imageViewerDidDisplayImage(atIndex: 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/DefaultToolbarController.swift b/Optik/Classes/DefaultToolbarController.swift new file mode 100644 index 0000000..2bf7c26 --- /dev/null +++ b/Optik/Classes/DefaultToolbarController.swift @@ -0,0 +1,31 @@ +// +// DefaultToolbarController.swift +// Pods +// +// Created by Daniel Vancura on 10/5/16. +// +// + +import UIKit + +internal final class DefaultToolbarController: ToolbarController { + + // MARK: - Parameters + + let navigationItem: UINavigationItem = UINavigationItem(title: "") + + let navigationBarHidesOnTap: Bool = true + + let toolbarHidesOnTap: Bool = true + + // MARK: - Functions + + func setupNavigationBar(navigationBar: UINavigationBar, forUseIn albumViewController: UIViewController) { + navigationBar.barStyle = .BlackTranslucent + } + + func setupToolbar(toolbar: UIToolbar, forUseIn albumViewController: UIViewController) { + toolbar.barStyle = .BlackTranslucent + } + +} diff --git a/Optik/Classes/Optik.swift b/Optik/Classes/Optik.swift index 0066ead..4d8c51a 100644 --- a/Optik/Classes/Optik.swift +++ b/Optik/Classes/Optik.swift @@ -25,11 +25,13 @@ public func imageViewer(withImages images: [UIImage], initialImageDisplayIndex: Int = 0, delegate: ImageViewerDelegate? = nil, dismissButtonImage: UIImage? = nil, - dismissButtonPosition: DismissButtonPosition = .TopLeading) -> UIViewController { + dismissButtonPosition: DismissButtonPosition = .TopLeading, + toolbarController: ToolbarController? = nil) -> UIViewController { let albumViewController = imageViewer(withData: .Local(images: images), initialImageDisplayIndex: initialImageDisplayIndex, dismissButtonImage: dismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + toolbarController: toolbarController) albumViewController.modalPresentationStyle = .Custom albumViewController.imageViewerDelegate = delegate @@ -53,12 +55,14 @@ public func imageViewer(withURLs urls: [NSURL], imageDownloader: ImageDownloader, activityIndicatorColor: UIColor = .whiteColor(), dismissButtonImage: UIImage? = nil, - dismissButtonPosition: DismissButtonPosition = .TopLeading) -> UIViewController { + dismissButtonPosition: DismissButtonPosition = .TopLeading, + toolbarController: ToolbarController? = nil) -> UIViewController { return imageViewer(withData: .Remote(urls: urls, imageDownloader: imageDownloader), initialImageDisplayIndex: initialImageDisplayIndex, activityIndicatorColor: activityIndicatorColor, dismissButtonImage: dismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + toolbarController: toolbarController) } // MARK: - Private functions @@ -67,7 +71,8 @@ private func imageViewer(withData imageData: ImageData, initialImageDisplayIndex: Int, activityIndicatorColor: UIColor? = nil, dismissButtonImage: UIImage?, - dismissButtonPosition: DismissButtonPosition) -> AlbumViewController { + dismissButtonPosition: DismissButtonPosition, + toolbarController: ToolbarController? = nil) -> AlbumViewController { let bundle = NSBundle(forClass: AlbumViewController.self) let defaultDismissButtonImage = UIImage(named: "DismissIcon", inBundle: bundle, compatibleWithTraitCollection: nil) @@ -75,5 +80,6 @@ private func imageViewer(withData imageData: ImageData, initialImageDisplayIndex: initialImageDisplayIndex, activityIndicatorColor: activityIndicatorColor, dismissButtonImage: dismissButtonImage ?? defaultDismissButtonImage, - dismissButtonPosition: dismissButtonPosition) + dismissButtonPosition: dismissButtonPosition, + toolbarController: toolbarController) } diff --git a/Optik/Classes/ToolbarController.swift b/Optik/Classes/ToolbarController.swift new file mode 100644 index 0000000..6c02406 --- /dev/null +++ b/Optik/Classes/ToolbarController.swift @@ -0,0 +1,32 @@ +// +// ToolbarController.swift +// Pods +// +// Created by Daniel Vancura on 10/5/16. +// +// + +import UIKit + +public protocol ToolbarController: class { + + /// Indicates whether the navigation bar should hide on a tap gesture. + var navigationBarHidesOnTap: Bool { get } + + /// Indicates whether the toolbar should hide on a tap gesture. + var toolbarHidesOnTap: Bool { get } + + /// The navigation item which will be displayed in the album view's navigation bar. + var navigationItem: UINavigationItem { get } + + /// Sets up the given navigation bar before adding it to the album view controller. + /// + /// - parameter navigationBar: The album view controller's navigation bar. + func setupNavigationBar(navigationBar: UINavigationBar, forUseIn albumViewController: UIViewController) + + /// Sets up the given toolbar before adding it to the album view contorller. + /// + /// - parameter toolbar: The album view controller's toolbar. + func setupToolbar(toolbar: UIToolbar, forUseIn albumViewController: UIViewController) + +}