Skip to content

Instantly share code, notes, and snippets.

@robbdimitrov
Last active April 3, 2018 12:25
Show Gist options
  • Save robbdimitrov/936692c54deed73c0dd2082f6ec1d46d to your computer and use it in GitHub Desktop.
Save robbdimitrov/936692c54deed73c0dd2082f6ec1d46d to your computer and use it in GitHub Desktop.
Swift 3 Carousel view
//
// 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