No Description

FFPageMenuView.swift 12KB

    // // FFPageMenuView.swift // FFPage // // Created by FFIB on 2017/10/17. // Copyright © 2017年 FFIB. All rights reserved. // import UIKit public protocol FFPageMenuViewDelegate { func pageMenuView(pageMenuView: FFPageMenuView, didSelectItemAt index: Int) } public enum FFPageMenuContentMode { case scaleAspectFill case center(space: CGFloat) } @IBDesignable open class FFPageMenuView: UIView { private var needlayout = true private var space: CGFloat = 0 private let baseTag = 55161750 private var selectedLablel = UILabel() private var sliderConstriant: NSLayoutConstraint? private var isTap = false private var ffConstraints = [NSLayoutConstraint]() //configuratable parameter public var sliderView: UIView? public var selectedColor = UIColor.blue public var normalColor = UIColor.black public var font = UIFont.systemFont(ofSize: 17) public var pageDelegate: FFPageMenuViewDelegate? public var selectIndex: Int = 0 public var pageMenuContentMode = FFPageMenuContentMode.scaleAspectFill public var isBigger = true public var titles = [String]() { didSet { needlayout = true } } public override init(frame: CGRect) { super.init(frame: frame) let tap = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) addGestureRecognizer(tap) } required public init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) let tap = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:))) addGestureRecognizer(tap) } @objc func didTap(gesture: UITapGestureRecognizer) { let point = gesture.location(in: self) let targetView = subviews.filter { $0.frame.contains(point) }.first guard let target = targetView else { return } scroll(at: target.tag - baseTag) pageDelegate?.pageMenuView(pageMenuView: self, didSelectItemAt: target.tag - baseTag) selectIndex = target.tag - baseTag } open override func layoutSubviews() { super.layoutSubviews() if needlayout { reloadSubviews() } } private func reloadSubviews() { func clear() { subviews.forEach { $0.removeFromSuperview() } NSLayoutConstraint.deactivate(ffConstraints) ffConstraints.removeAll() needlayout = false } clear() layoutIfNeeded() configurationOfMenu() configurationOfSlider() NSLayoutConstraint.activate(ffConstraints) } } // MARK: configuration extension FFPageMenuView { private func configurationOfSlider() { guard let label = viewWithTag(baseTag) else { return } if sliderView == nil { sliderView = UIView(frame: CGRect(x: 0, y: 0, width: 15, height: 2)) sliderView?.backgroundColor = selectedColor } sliderView!.translatesAutoresizingMaskIntoConstraints = false addSubview(sliderView!) //init slide constraint sliderConstriant = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: label, attribute: .centerX, multiplier: 1, constant: 0) let sliderConstriants = [sliderConstriant!, NSLayoutConstraint(item: sliderView!, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .height, multiplier: 1, constant: sliderView!.bounds.height), NSLayoutConstraint(item: sliderView!, attribute: .top, relatedBy: .equal, toItem: label, attribute: .bottom, multiplier: 1, constant: 5), NSLayoutConstraint(item: sliderView!, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: sliderView!.bounds.width)] ffConstraints += sliderConstriants } private func configurationOfMenu() { var last: UILabel? let labelWidth: CGFloat switch pageMenuContentMode { case .scaleAspectFill: space = 0 labelWidth = bounds.width / CGFloat(titles.count) case .center(let spacing): space = spacing labelWidth = 0 } var contentWidth: CGFloat = 0 for (i, title) in titles.enumerated() { //init label let label = UILabel() label.tag = baseTag + i label.text = title label.font = font label.textAlignment = .center label.translatesAutoresizingMaskIntoConstraints = false label.sizeToFit() label.textColor = normalColor addSubview(label) if i == 0 { selectedLablel = label label.textColor = selectedColor if isBigger { label.font = UIFont.systemFont(ofSize: font.pointSize + 1) } } //init label constraint let leftConstraint: NSLayoutConstraint if last == nil { leftConstraint = NSLayoutConstraint(item: label, attribute: .leading, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: space) } else { leftConstraint = NSLayoutConstraint(item: label, attribute: .left, relatedBy: .equal, toItem: last, attribute: .right, multiplier: 1, constant: space) } var labelConstraints = [NSLayoutConstraint(item: label, attribute: .centerY, relatedBy: .equal, toItem: self, attribute: .centerY, multiplier: 1, constant: 0), leftConstraint] if labelWidth != 0 { labelConstraints.append(NSLayoutConstraint(item: label, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: labelWidth)) contentWidth += labelWidth } else { contentWidth += label.bounds.width + space } ffConstraints += labelConstraints last = label } ffConstraints.append(NSLayoutConstraint(item: self, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .width, multiplier: 1, constant: contentWidth)) } } //scroll function extension FFPageMenuView { private func translationSlider(percent: CGFloat) { sliderConstriant?.isActive = false sliderConstriant = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: selectedLablel, attribute: .centerX, multiplier: 1, constant: percent) sliderConstriant?.isActive = true } func scroll(at index: Int, animated: Bool = true) { guard let targetLabel = viewWithTag(index + baseTag) as? UILabel, targetLabel.tag != selectedLablel.tag else { return } selectedLablel.textColor = normalColor selectedLablel.font = font targetLabel.textColor = selectedColor if isBigger { targetLabel.font = UIFont.systemFont(ofSize: font.pointSize + 1) } if animated { let animation = CAKeyframeAnimation(keyPath: "position.x") animation.values = [sliderView!.frame.origin.x, sliderView!.frame.origin.x + targetLabel.frame.origin.x - selectedLablel.frame.origin.x] animation.delegate = self animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards sliderView?.layer.add(animation, forKey: "FFPage.FFPageMenuView.sliderView.animation") } else { sliderConstriant?.isActive = false sliderConstriant = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: targetLabel, attribute: .centerX, multiplier: 1, constant: 0) sliderConstriant?.isActive = true } selectedLablel = targetLabel } func scroll(offset: CGFloat) { let percent = offset - CGFloat(selectedLablel.tag - baseTag) if percent > 0 && percent < 1 { guard let nextLabel = viewWithTag(selectedLablel.tag + 1) as? UILabel else { return } translationSlider(percent: percent * (space + nextLabel.frame.width / 2 + selectedLablel.frame.width / 2)) } else if percent < 0 && percent > -1 { guard let nextLabel = viewWithTag(selectedLablel.tag - 1) as? UILabel else { return } translationSlider(percent: percent * (space + nextLabel.frame.width / 2 + selectedLablel.frame.width / 2)) } else { if percent != 0 { scroll(at: Int(offset), animated: false) } } } } //CAAnimationDelegate extension FFPageMenuView: CAAnimationDelegate { public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { //move slide translationSlider(percent: 0) sliderView!.layer.removeAnimation(forKey: "FFPage.FFPageMenuView.sliderView.animation") } }