| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- import UIKit
- public final class LNCyclePageControl: UIControl {
- public var numberOfPages: Int = 0 {
- didSet {
- guard numberOfPages != oldValue else { return }
- if currentPage >= numberOfPages {
- currentPage = 0
- }
- updateIndicatorViews()
- if !indicatorViews.isEmpty { setNeedsLayout() }
- }
- }
- public var currentPage: Int = 0 {
- didSet {
- guard oldValue != currentPage, currentPage < indicatorViews.count else { return }
- if currentPage < 0 { currentPage = 0; return }
- if !currentPageIndicatorSize.equalTo(pageIndicatorSize) {
- setNeedsLayout()
- }
- updateIndicatorViewsBehavior()
- if isUserInteractionEnabled {
- sendActions(for: .valueChanged)
- }
- }
- }
- public var hidesForSinglePage = false
- public var pageIndicatorSpacing: CGFloat = 10 {
- didSet {
- guard oldValue != pageIndicatorSpacing else { return }
- if !indicatorViews.isEmpty { setNeedsLayout() }
- }
- }
- public var contentInset: UIEdgeInsets = .zero {
- didSet { setNeedsLayout() }
- }
- public var contentSize: CGSize {
- let count = indicatorViews.count
- let width = CGFloat(max(count - 1, 0)) * (pageIndicatorSize.width + pageIndicatorSpacing) + pageIndicatorSize.width + contentInset.left + contentInset.right
- let height = currentPageIndicatorSize.height + contentInset.top + contentInset.bottom
- return CGSize(width: width, height: height)
- }
- public var pageIndicatorTintColor: UIColor? = UIColor(white: 0.5, alpha: 1) {
- didSet { updateIndicatorViewsBehavior() }
- }
- public var currentPageIndicatorTintColor: UIColor? = .white {
- didSet { updateIndicatorViewsBehavior() }
- }
- public var pageIndicatorImage: UIImage? {
- didSet { updateIndicatorViewsBehavior() }
- }
- public var currentPageIndicatorImage: UIImage? {
- didSet { updateIndicatorViewsBehavior() }
- }
- public var indicatorImageContentMode: UIView.ContentMode = .center {
- didSet {
- indicatorViews.forEach { $0.contentMode = indicatorImageContentMode }
- }
- }
- public var pageIndicatorSize: CGSize = CGSize(width: 6, height: 6) {
- didSet {
- guard !pageIndicatorSize.equalTo(oldValue) else { return }
- if currentPageIndicatorSize == .zero || (currentPageIndicatorSize.width < pageIndicatorSize.width && currentPageIndicatorSize.height < pageIndicatorSize.height) {
- currentPageIndicatorSize = pageIndicatorSize
- }
- if !indicatorViews.isEmpty { setNeedsLayout() }
- }
- }
- public var currentPageIndicatorSize: CGSize = CGSize(width: 6, height: 6) {
- didSet {
- guard !currentPageIndicatorSize.equalTo(oldValue) else { return }
- if !indicatorViews.isEmpty { setNeedsLayout() }
- }
- }
- public var animateDuration: TimeInterval = 0.3
- private var indicatorViews: [UIImageView] = []
- private var forceUpdate = false
- public override init(frame: CGRect) {
- super.init(frame: frame)
- configure()
- }
- public required init?(coder: NSCoder) {
- super.init(coder: coder)
- configure()
- }
- private func configure() {
- isUserInteractionEnabled = false
- currentPageIndicatorSize = pageIndicatorSize
- }
- public override func willMove(toSuperview newSuperview: UIView?) {
- super.willMove(toSuperview: newSuperview)
- if newSuperview != nil {
- forceUpdate = true
- updateIndicatorViews()
- forceUpdate = false
- }
- }
- public func setCurrentPage(_ page: Int, animate: Bool) {
- if animate {
- UIView.animate(withDuration: animateDuration) {
- self.currentPage = page
- self.layoutIfNeeded()
- }
- } else {
- currentPage = page
- }
- }
- public override func layoutSubviews() {
- super.layoutSubviews()
- layoutIndicatorViews()
- }
- private func updateIndicatorViews() {
- if superview == nil, !forceUpdate { return }
- if indicatorViews.count < numberOfPages {
- for _ in indicatorViews.count..<numberOfPages {
- let iv = UIImageView()
- iv.contentMode = indicatorImageContentMode
- addSubview(iv)
- indicatorViews.append(iv)
- }
- } else if indicatorViews.count > numberOfPages {
- for idx in stride(from: indicatorViews.count - 1, through: numberOfPages, by: -1) {
- indicatorViews[idx].removeFromSuperview()
- indicatorViews.remove(at: idx)
- }
- }
- updateIndicatorViewsBehavior()
- }
- private func updateIndicatorViewsBehavior() {
- guard !indicatorViews.isEmpty else { return }
- if superview == nil, !forceUpdate { return }
- if hidesForSinglePage, indicatorViews.count == 1 {
- indicatorViews[0].isHidden = true
- return
- }
- for (idx, view) in indicatorViews.enumerated() {
- if let pageIndicatorImage {
- view.contentMode = indicatorImageContentMode
- view.image = (idx == currentPage) ? currentPageIndicatorImage : pageIndicatorImage
- view.backgroundColor = .clear
- } else {
- view.image = nil
- view.backgroundColor = (idx == currentPage) ? currentPageIndicatorTintColor : pageIndicatorTintColor
- }
- view.isHidden = false
- }
- }
- private func layoutIndicatorViews() {
- guard !indicatorViews.isEmpty else { return }
- var originX: CGFloat = 0
- var centerY: CGFloat = 0
- var spacing = pageIndicatorSpacing
- switch contentHorizontalAlignment {
- case .center:
- originX = (bounds.width - CGFloat(indicatorViews.count - 1) * (pageIndicatorSize.width + pageIndicatorSpacing) - currentPageIndicatorSize.width) * 0.5
- case .left:
- originX = contentInset.left
- case .right:
- originX = bounds.width - (CGFloat(indicatorViews.count - 1) * (pageIndicatorSize.width + pageIndicatorSpacing) + currentPageIndicatorSize.width) - contentInset.right
- case .fill:
- originX = contentInset.left
- if indicatorViews.count > 1 {
- spacing = (bounds.width - contentInset.left - contentInset.right - pageIndicatorSize.width - CGFloat(indicatorViews.count - 1) * pageIndicatorSize.width) / CGFloat(indicatorViews.count - 1)
- }
- default:
- break
- }
- switch contentVerticalAlignment {
- case .center:
- centerY = bounds.height * 0.5
- case .top:
- centerY = contentInset.top + currentPageIndicatorSize.height * 0.5
- case .bottom:
- centerY = bounds.height - currentPageIndicatorSize.height * 0.5 - contentInset.bottom
- case .fill:
- centerY = (bounds.height - contentInset.top - contentInset.bottom) * 0.5 + contentInset.top
- default:
- break
- }
- for (idx, view) in indicatorViews.enumerated() {
- let size = (idx == currentPage) ? currentPageIndicatorSize : pageIndicatorSize
- view.layer.cornerRadius = (pageIndicatorImage == nil) ? (size.height * 0.5) : 0
- view.clipsToBounds = true
- view.frame = CGRect(x: originX, y: centerY - size.height * 0.5, width: size.width, height: size.height)
- originX += size.width + spacing
- }
- }
- }
|