diff --git a/MediaSlideshow/Source/MediaSlideshow.swift b/MediaSlideshow/Source/MediaSlideshow.swift index a8c89a0e..2fbd2872 100644 --- a/MediaSlideshow/Source/MediaSlideshow.swift +++ b/MediaSlideshow/Source/MediaSlideshow.swift @@ -28,7 +28,7 @@ public protocol MediaSlideshowDelegate: class { @objc optional func mediaSlideshowDidEndDecelerating(_ mediaSlideshow: MediaSlideshow) } -/** +/** Used to represent position of the Page Control - hidden: Page Control is hidden - insideScrollView: Page Control is inside image slideshow @@ -73,6 +73,14 @@ open class MediaSlideshow: UIView { reloadScrollView() } } + + open var circular = true { + didSet { + if sources.count > 0 { + setMediaSources(sources) + } + } + } open var pageIndicator: PageIndicatorView? { didSet { @@ -177,6 +185,14 @@ open class MediaSlideshow: UIView { } } + /// Image change interval, zero stops the auto-scrolling + open var slideshowInterval = 0.0 { + didSet { + slideshowTimer?.invalidate() + slideshowTimer = nil + setTimerIfNeeded() + } + } /// Image preload configuration, can be sed to .fixed to enable lazy load or .all open var preload = ImagePreload.all @@ -189,6 +205,8 @@ open class MediaSlideshow: UIView { } } + fileprivate var slideshowTimer: Timer? + fileprivate var scrollViewImages = [MediaSource]() fileprivate var isAnimating: Bool = false /// Transitioning delegate to manage the transition to full screen controller @@ -242,9 +260,15 @@ open class MediaSlideshow: UIView { pageIndicator = UIPageControl() } + setTimerIfNeeded() layoutScrollView() } + open override func removeFromSuperview() { + super.removeFromSuperview() + pauseTimer() + } + open override func layoutSubviews() { super.layoutSubviews() @@ -331,10 +355,32 @@ open class MediaSlideshow: UIView { */ public func setMediaSources(_ sources: [MediaSource]) { self.sources = sources + pageIndicator?.numberOfPages = sources.count + + // in circular mode we add dummy first and last image to enable smooth scrolling + if circular && sources.count > 1 { + var scImages = [MediaSource]() + + if let last = sources.last { + scImages.append(last) + } + scImages += sources + if let first = sources.first { + scImages.append(first) + } + + scrollViewImages = scImages + } else { + scrollViewImages = sources + } + + reloadScrollView() layoutScrollView() layoutPageControl() + setTimerIfNeeded() + } // MARK: paging methods @@ -345,7 +391,13 @@ open class MediaSlideshow: UIView { - parameter animated: true if animate the change */ open func setCurrentPage(_ newPage: Int, animated: Bool) { - setScrollViewPage(newPage, animated: animated) + var pageOffset = newPage + if circular && (scrollViewImages.count > 1) { + pageOffset += 1 + } + + setScrollViewPage(pageOffset, animated: animated) + } /** @@ -362,6 +414,24 @@ open class MediaSlideshow: UIView { } } } + + fileprivate func setTimerIfNeeded() { + if slideshowInterval > 0 && scrollViewImages.count > 1 && slideshowTimer == nil { + slideshowTimer = Timer.scheduledTimer(timeInterval: slideshowInterval, target: self, selector: #selector(MediaSlideshow.slideshowTick(_:)), userInfo: nil, repeats: true) + } + } + + func slideshowTick(_ timer: Timer) { + let page = scrollView.frame.size.width > 0 ? Int(scrollView.contentOffset.x / scrollView.frame.size.width) : 0 + var nextPage = page + 1 + + if !circular && page == scrollViewImages.count - 1 { + nextPage = 0 + } + + setScrollViewPage(nextPage, animated: true) + } + fileprivate func setCurrentPageForScrollViewPage(_ page: Int) { if scrollViewPage != page { @@ -381,7 +451,50 @@ open class MediaSlideshow: UIView { } fileprivate func currentPageForScrollViewPage(_ page: Int) -> Int { - page + if circular { + if page == 0 { + // first page contains the last image + return Int(sources.count) - 1 + } else if page == scrollViewImages.count - 1 { + // last page contains the first image + return 0 + } else { + return page - 1 + } + } else { + return page + } + } + + fileprivate func restartTimer() { + if slideshowTimer?.isValid != nil { + slideshowTimer?.invalidate() + slideshowTimer = nil + } + + setTimerIfNeeded() + } + + + /// Stops slideshow timer + open func pauseTimer() { + slideshowTimer?.invalidate() + slideshowTimer = nil + } + + /// Restarts slideshow timer + open func unpauseTimer() { + setTimerIfNeeded() + } + + @available(*, deprecated, message: "use pauseTimer instead") + open func pauseTimerIfNeeded() { + pauseTimer() + } + + @available(*, deprecated, message: "use unpauseTimer instead") + open func unpauseTimerIfNeeded() { + unpauseTimer() } /** @@ -389,10 +502,15 @@ open class MediaSlideshow: UIView { - Parameter animated: true if animate the change */ open func nextPage(animated: Bool) { + if !circular && currentPage == sources.count - 1 { + return + } if isAnimating { return } + setCurrentPage(currentPage + 1, animated: animated) + restartTimer() } /** @@ -400,13 +518,19 @@ open class MediaSlideshow: UIView { - Parameter animated: true if animate the change */ open func previousPage(animated: Bool) { + if !circular && currentPage == 0 { + return + } if isAnimating { return } - let newPage = scrollViewPage > 0 ? scrollViewPage - 1 : sources.count - 3 + + let newPage = scrollViewPage > 0 ? scrollViewPage - 1 : scrollViewImages.count - 3 setScrollViewPage(newPage, animated: animated) + restartTimer() } + @objc private func pageControlValueChanged() { if let currentPage = pageIndicator?.page { setCurrentPage(currentPage, animated: true) @@ -417,6 +541,7 @@ open class MediaSlideshow: UIView { extension MediaSlideshow: UIScrollViewDelegate { open func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + restartTimer() willBeginDragging?() delegate?.mediaSlideshowWillBeginDragging?(self) } @@ -426,15 +551,32 @@ extension MediaSlideshow: UIScrollViewDelegate { didEndDecelerating?() delegate?.mediaSlideshowDidEndDecelerating?(self) } - +// +// open func scrollViewDidScroll(_ scrollView: UIScrollView) { +// // Updates the page indicator as the user scrolls (#204). Not called when not dragging to prevent flickers +// // when interacting with PageControl directly (#376). +// if scrollView.isDragging { +// pageIndicator?.page = currentPageForScrollViewPage(primaryVisiblePage) +// } +// } open func scrollViewDidScroll(_ scrollView: UIScrollView) { + if circular && (scrollViewImages.count > 1) { + let regularContentOffset = scrollView.frame.size.width * CGFloat(sources.count) + + if scrollView.contentOffset.x >= scrollView.frame.size.width * CGFloat(sources.count + 1) { + scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x - regularContentOffset, y: 0) + } else if scrollView.contentOffset.x <= 0 { + scrollView.contentOffset = CGPoint(x: scrollView.contentOffset.x + regularContentOffset, y: 0) + } + } + // Updates the page indicator as the user scrolls (#204). Not called when not dragging to prevent flickers // when interacting with PageControl directly (#376). if scrollView.isDragging { pageIndicator?.page = currentPageForScrollViewPage(primaryVisiblePage) } } - + public func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { isAnimating = false }