Last active
April 3, 2018 12:25
-
-
Save robbdimitrov/936692c54deed73c0dd2082f6ec1d46d to your computer and use it in GitHub Desktop.
Swift 3 Carousel view
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Author: Robert Dimitrov | |
// | |
// License: MIT | |
// | |
import UIKit | |
//MARK: - CarouselViewDelegate protocol | |
// Carousel view delegate, responsible for displayed pages and their count | |
public protocol CarouselViewDelegate: class { | |
// Returns a view for the given page | |
func carouselView(carouselView: CarouselView, viewFor Page: Int) -> UIView | |
// Returns the number of pages carousel has | |
func numberOfPagesInCarouselView(carouselView: CarouselView) -> Int | |
// Notify the delegate about a tap gesture | |
func carouselView(carouselView: CarouselView, didTap page: Int) | |
} | |
//MARK: - CarouselView class | |
// Carousel scrollable view used for displaying custom page views | |
public class CarouselView: UIView { | |
weak var delegate: CarouselViewDelegate? { | |
didSet { | |
reloadData() | |
} | |
} | |
@IBOutlet weak var scrollView: UIScrollView? | |
@IBOutlet weak var pageControl: UIPageControl? | |
@IBOutlet weak var separator: UIView? | |
weak var tapGestureRecognizer: UITapGestureRecognizer? | |
var views: [UIView] = [] | |
var numberOfPages: Int = 0 | |
var currentPage: Int { | |
get { | |
return pageControl?.currentPage ?? 0 | |
} | |
set { | |
setCurrentPage(newValue, animated: false) | |
} | |
} | |
public override func awakeFromNib() { | |
super.awakeFromNib() | |
// Setup scroll view | |
scrollView?.pagingEnabled = true | |
scrollView?.alwaysBounceVertical = false | |
scrollView?.showsVerticalScrollIndicator = false | |
scrollView?.showsHorizontalScrollIndicator = false | |
// Setup page conrol | |
pageControl?.hidesForSinglePage = true | |
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(tapGesture(_:))) | |
tapGestureRecognizer.cancelsTouchesInView = false | |
scrollView?.addGestureRecognizer(tapGestureRecognizer) | |
self.tapGestureRecognizer = tapGestureRecognizer | |
} | |
func tapGesture(sender: UITapGestureRecognizer) { | |
let point = sender.locationInView(scrollView) | |
let page = Int(point.x / bounds.width) | |
delegate?.carouselView(self, didTap: page) | |
} | |
func setCurrentPage(page: Int, animated: Bool) { | |
scrollToPage(page, animated: animated) | |
pageControl?.currentPage = page | |
} | |
func scrollToPage(page: Int, animated: Bool) { | |
scrollView?.setContentOffset(CGPoint(x: CGFloat(page) * bounds.width, y: 0), animated: animated) | |
} | |
@IBAction func changePage(sender: UIPageControl) { | |
setCurrentPage(sender.currentPage, animated: true) | |
} | |
public class func createCarouselView() -> CarouselView? { | |
let items: [AnyObject] = NSBundle(forClass: CarouselView.self).loadNibNamed("CarouselView", owner: self, options: nil) ?? [] | |
for item: AnyObject in items { | |
if let view = item as? CarouselView { | |
return view | |
} | |
} | |
return nil | |
} | |
public func reloadData() { | |
// remove existing subviews | |
for view in views { | |
view.removeFromSuperview() | |
} | |
views.removeAll() | |
// Get new data and setup elements | |
numberOfPages = delegate?.numberOfPagesInCarouselView(self) ?? 0 | |
pageControl?.numberOfPages = numberOfPages | |
pageControl?.currentPage = currentPage | |
scrollView?.contentSize = CGSize(width: bounds.width * CGFloat(numberOfPages), height: 0) | |
// Get relevant pages | |
if let scrollView = scrollView { | |
scrollViewDidScroll(scrollView) | |
} | |
// Move to beginning | |
setCurrentPage(0, animated: false) | |
} | |
private func frameFor(page: Int) -> CGRect { | |
return CGRect(x: CGFloat(page) * bounds.width, | |
y: 0, | |
width: bounds.width, | |
height: bounds.height) | |
} | |
private func loadView(for page: Int) { | |
guard page >= 0 && page < numberOfPages else { | |
return | |
} | |
// Already exists, return | |
if views.count > page { | |
return | |
} | |
// New page, add to scrollView and views array | |
if let view = delegate?.carouselView(self, viewFor: page) { | |
view.frame = frameFor(page) | |
views.append(view) | |
scrollView?.addSubview(view) | |
} | |
} | |
} | |
//MARK: - UIScrollViewDelegate | |
extension CarouselView: UIScrollViewDelegate { | |
public func scrollViewDidScroll(scrollView: UIScrollView) { | |
// Calculate current page | |
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width) | |
// Load previous, current and next pages | |
loadView(for: page - 1) | |
loadView(for: page) | |
loadView(for: page + 1) | |
} | |
public func scrollViewDidEndDecelerating(scrollView: UIScrollView) { | |
// Calculate current page | |
let page = Int(scrollView.contentOffset.x / scrollView.bounds.width) | |
pageControl?.currentPage = page | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment