Geen omschrijving

PageViewController.swift 12KB

    // // PageViewController.swift // PaiAi // // Created by ffib on 2018/12/6. // Copyright © 2018 yb. All rights reserved. // import UIKit public struct PageOption { var font: UIFont var normalColor: UIColor var selectedColor: UIColor var spacing: CGFloat init(font: UIFont = UIFont.systemFont(ofSize: 15), normalColor: UIColor = UIColor(white: 1, alpha: 0.7), selectedColor: UIColor = UIColor.white, spacing: CGFloat = 10) { self.font = font self.normalColor = normalColor self.selectedColor = selectedColor self.spacing = spacing } } public struct PageItem { public var title: String public var viewController: UIViewController public init(title: String, viewController: UIViewController) { self.title = title self.viewController = viewController } } fileprivate let baseTag = 55161750 open class PageViewController: UIViewController { private enum ScrollDirection { case left case right } private var scrollView = UIScrollView() private var menuView = UIView() private var sliderView = UIView() private var contentRect = CGRect.zero //animation auxiliary private var currentIndex: Int = 0 private var distance: CGFloat = 0 private var currentItem: UILabel? private var startOffset: CGFloat = 0 private var isBeginScroll: Bool = false private var menuItemWidths: [CGFloat] = [] private var direction: ScrollDirection = .right private var sliderConstraint: NSLayoutConstraint? public var option = PageOption() public var pageItems = [PageItem]() { didSet { configurationPageItems() configurationMenuItems() } } override open func viewDidLoad() { super.viewDidLoad() contentRect = CGRect(x: 0, y: 0, width: view.frame.width, height: view.frame.height) configurationScrollView() configurationMenuView() configurationSliderView() } override open func viewDidLayoutSubviews() { cacheMenuItemWidth() } fileprivate func cacheMenuItemWidth() { guard menuItemWidths.isEmpty else { return } menuItemWidths = Array(repeating: 0, count: menuView.subviews.count - 1) for view in menuView.subviews { guard let label = view as? UILabel else { continue } menuItemWidths[label.tag - baseTag] = label.frame.width } } fileprivate func configurationScrollView() { scrollView.bounces = false scrollView.delegate = self scrollView.frame = contentRect scrollView.isPagingEnabled = true scrollView.showsVerticalScrollIndicator = false scrollView.showsHorizontalScrollIndicator = false view.addSubview(scrollView) if #available(iOS 11.0, *) { scrollView.contentInsetAdjustmentBehavior = .never } else { automaticallyAdjustsScrollViewInsets = false } } fileprivate func configurationMenuView() { guard let barContentView = navigationController?.navigationBar else { return } barContentView.addSubview(menuView) menuView.translatesAutoresizingMaskIntoConstraints = false let top = menuView.topAnchor .constraint(equalTo: barContentView.topAnchor) let bottom = menuView.bottomAnchor .constraint(equalTo: barContentView.bottomAnchor) let centerX = menuView.centerXAnchor .constraint(equalTo: barContentView.centerXAnchor) NSLayoutConstraint.activate([top, bottom, centerX]) let tap = UITapGestureRecognizer(target: self, action: #selector(tapMenu(tap:))) menuView.addGestureRecognizer(tap) } fileprivate func configurationSliderView() { sliderView.backgroundColor = option.selectedColor sliderView.translatesAutoresizingMaskIntoConstraints = false sliderView.layer.cornerRadius = 2.5 menuView.addSubview(sliderView) let width = sliderView.widthAnchor.constraint(equalToConstant: 5) let height = sliderView.heightAnchor.constraint(equalToConstant: 5) let bottom = sliderView.bottomAnchor .constraint(equalTo: menuView.bottomAnchor, constant: -2) NSLayoutConstraint.activate([width, height, bottom]) } fileprivate func configurationPageItems() { scrollView.contentSize = CGSize(width: contentRect.width * CGFloat(pageItems.count), height: contentRect.height) for (i, item) in pageItems.enumerated() { addChild(item.viewController) scrollView.addSubview(item.viewController.view) item.viewController.view.translatesAutoresizingMaskIntoConstraints = false let width = item.viewController.view .widthAnchor .constraint(equalToConstant: contentRect.width) let heigth = item.viewController.view .heightAnchor .constraint(equalToConstant: contentRect.height) let top = item.viewController.view .topAnchor .constraint(equalTo: scrollView.topAnchor, constant: 0) let leading = item.viewController.view .leadingAnchor .constraint(equalTo: scrollView.leadingAnchor, constant: CGFloat(i) * contentRect.width) NSLayoutConstraint.activate([width, heigth, top, leading]) } } fileprivate func configurationMenuItems() { var last: UILabel? for (i, item) in pageItems.enumerated() { let label = UILabel() label.text = item.title label.tag = baseTag + i label.font = option.font label.textAlignment = .center label.textColor = option.normalColor label.translatesAutoresizingMaskIntoConstraints = false menuView.addSubview(label) let left: NSLayoutConstraint if let lastLabel = last { left = label.leftAnchor .constraint(equalTo: lastLabel.rightAnchor, constant: option.spacing) } else { left = label.leadingAnchor .constraint(equalTo: menuView.leadingAnchor) } let centerY = label.centerYAnchor .constraint(equalTo: menuView.centerYAnchor) if i == 0 { label.textColor = option.selectedColor label.font = UIFont.systemFont(ofSize: option.font.pointSize + 1) currentItem = label } else if i == pageItems.count - 1 { NSLayoutConstraint.activate([label.trailingAnchor .constraint(equalTo: menuView.trailingAnchor)]) } NSLayoutConstraint.activate([left, centerY]) last = label } configurationSliderViewDetail() } fileprivate func configurationSliderViewDetail() { guard let label = menuView.viewWithTag(baseTag) else { return } sliderConstraint = sliderView.centerXAnchor .constraint(equalTo: label.centerXAnchor) NSLayoutConstraint.activate([sliderConstraint!]) } } //action extension PageViewController { @objc fileprivate func tapMenu(tap: UITapGestureRecognizer) { var x = tap.location(in: menuView).x for (i, width) in menuItemWidths.enumerated() { x -= (width + option.spacing / 2) guard x <= 0 else { continue } if i != currentIndex { didSelect(i) } return } } } extension PageViewController: UIScrollViewDelegate { func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { startOffset = self.scrollView.contentOffset.x isBeginScroll = true } func scrollViewDidScroll(_ scrollView: UIScrollView) { guard scrollView.isDragging || scrollView.isDecelerating || scrollView.isTracking else { return } initializeScrollParameter() moveSlider(percentage: (self.scrollView.contentOffset.x - startOffset ) / contentRect.width) } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { guard !decelerate else { return } pageViewDidEndScroll() } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { pageViewDidEndScroll() } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { pageViewDidEndScroll() } } //linkage extension PageViewController { fileprivate func didSelect(_ index: Int) { startOffset = self.scrollView.contentOffset.x sliderAnimation(index) didSelectPageItem(index) } fileprivate func initializeScrollParameter() { if isBeginScroll { direction = self.scrollView.contentOffset.x - startOffset > 0 ? .right : .left guard let label = currentItem else { return } switch direction { case .left: distance = menuItemWidths[label.tag - baseTag] / 2 + menuItemWidths[label.tag - baseTag - 1] / 2 + option.spacing case .right: distance = menuItemWidths[label.tag - baseTag] / 2 + menuItemWidths[label.tag - baseTag + 1] / 2 + option.spacing } isBeginScroll = false } } } //menu extension PageViewController { fileprivate func moveSlider(percentage: CGFloat) { sliderConstraint?.constant = percentage * distance } fileprivate func didSelectMenuItem(_ index: Int) { guard let currentLabel = currentItem, let label = menuView.viewWithTag(baseTag + index) as? UILabel else { return } currentItem = label currentLabel.font = option.font currentLabel.textColor = option.normalColor label.textColor = option.selectedColor label.font = UIFont.systemFont(ofSize: option.font.pointSize + 1) currentIndex = index updateSliderConstraint() } fileprivate func sliderAnimation(_ index: Int) { let isLeft = currentIndex - index > 0 var animationDistance: CGFloat = 0 for i in isLeft ? (index..<currentIndex) : (currentIndex..<index) { animationDistance += menuItemWidths[i] / 2 + menuItemWidths[i + 1] / 2 + option.spacing } UIView.animate(withDuration: 0.25) { self.sliderConstraint?.constant = isLeft ? -animationDistance : animationDistance self.menuView.layoutIfNeeded() } } fileprivate func updateSliderConstraint() { NSLayoutConstraint.deactivate([sliderConstraint!]) sliderConstraint = sliderView.centerXAnchor.constraint(equalTo: currentItem!.centerXAnchor) NSLayoutConstraint.activate([sliderConstraint!]) } } //page extension PageViewController { fileprivate func didSelectPageItem(_ index: Int) { scrollView.setContentOffset(CGPoint.init(x: CGFloat(index) * contentRect.width, y: 0), animated: true) } fileprivate func pageViewDidEndScroll() { guard self.scrollView.contentOffset.x != startOffset else { return } let index = Int(self.scrollView.contentOffset.x / contentRect.width) switch direction { case .left: didSelectMenuItem(index) case .right: didSelectMenuItem(index) } } }