| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438 |
- import UIKit
- private let firstTabTag = 1231115
- private let firstLayoutViewTag = 131115
- protocol SeMobSlideTabViewDelegate {
- func slideTabViewDidSelect(_ index: Int)
- }
- enum SeMobSlideTabAlignType {
- case alignCenter
- case alignFixMargin(left : CGFloat?, mid : CGFloat?, right : CGFloat?)
- }
- @IBDesignable
- class SeMobSlideTabView: UIView {
-
- var delegate: SeMobSlideTabViewDelegate?
-
- var alignType: SeMobSlideTabAlignType = .alignCenter
-
-
- var scrollview: UIScrollView? {
- didSet {
- if scrollview != oldValue {
- oldValue?.removeObserver(self, forKeyPath: "contentOffset")
- scrollview?.addObserver(self, forKeyPath: "contentOffset", options: .new, context: nil)
- if scrollview != nil {
- setPortionWithScrollViewWithOffset(scrollview!.contentOffset)
- }
- }
- }
- }
-
- @IBInspectable var titles: [String] = [] {
- didSet {
- while badgeTypes.count < titles.count {
- badgeTypes.append(.number)
- }
- badgeNumbers = [Int](repeating: 0, count: titles.count)
- needsRelayout = true
- }
- }
-
- @IBInspectable var titleFont = UIFont.systemFont(ofSize: 13) {
- didSet {
- self.subviews.forEach {
- if let label = $0 as? UILabel {
- label.font = titleFont
- }
- }
- }
- }
-
- var seperaterLineView: UIView? {
- didSet {
- needsRelayout = true
- }
- }
-
- var slideLineView: UIView? {
- didSet {
- needsRelayout = true
- }
- }
-
- @IBInspectable var slideLineColor: UIColor? {
- didSet {
- needsRelayout = true
- }
- }
-
- @IBInspectable var slideLineSize = CGSize.zero {
- didSet {
- needsRelayout = true
- }
- }
-
- @IBInspectable var slideLineBottom: CGFloat = 0 {
- didSet {
- needsRelayout = true
- }
- }
-
- @IBInspectable var selectedTitleTextColor: UIColor! {
- didSet {
- needsRelayout = true
- }
- }
-
- @IBInspectable var titleTextColor: UIColor = UIColor.black {
- didSet {
- needsRelayout = true
- }
- }
- @IBInspectable var selectedIndex: Int = 0 {
- didSet {
- let value = min(titles.count, max(0, selectedIndex))
- selectedIndex = value
- if scrollview == nil {
- sliderPortion = Double(value)
- }
- for i in 0 ..< self.titles.count {
- if let label = self.viewWithTag(firstTabTag + i) as? UILabel {
- if i != selectedIndex {
- label.textColor = self.titleTextColor
- } else {
- label.textColor = self.selectedTitleTextColor
- }
- }
- }
- if selectedIndex != oldValue {
- self.delegate?.slideTabViewDidSelect(selectedIndex)
- }
- }
- }
- fileprivate var sliderPortion = 0.0 {
- didSet {
- if sliderPortion != oldValue {
- moveSlider(sliderPortion < oldValue ? true : false)
- if (sliderPortion - Double(Int(sliderPortion))) < 0.00001 {
- selectedIndex = Int(sliderPortion)
- }
- }
- }
- }
-
- enum BadgeType {
- case none
- case dot
- case number
-
- case customView((_ targetView:UIView, _ badgeNumber:Int, _ viewTag:Int)->Void)
- }
- var badgeTypes: [BadgeType] = [.none, .dot, .number]
- var badgeNumbers: [Int] = []
-
- fileprivate var needsRelayout = true {
- didSet {
- if needsRelayout {
- self.sliderView = nil
- self.setNeedsLayout()
- }
- }
- }
- fileprivate var sliderView: UIView?
- fileprivate var sliderPosConstraint: NSLayoutConstraint?
-
- override init(frame: CGRect) {
- sliderPortion = 0
- super.init(frame:frame)
- let gr = UITapGestureRecognizer(target: self, action: #selector(SeMobSlideTabView.didTap(_:)))
- self.addGestureRecognizer(gr)
- }
- required init?(coder aDecoder: NSCoder) {
- sliderPortion = 0
- super.init(coder:aDecoder)
- let gr = UITapGestureRecognizer(target: self, action: #selector(SeMobSlideTabView.didTap(_:)))
- self.addGestureRecognizer(gr)
- }
- deinit {
- scrollview?.removeObserver(self, forKeyPath: "contentOffset")
- }
-
- @objc func didTap(_ gr: UITapGestureRecognizer) {
- let pt = gr.location(in: self)
- let index = pt.x / (self.bounds.width/CGFloat(self.titles.count))
- self.selectedIndex = Int(index)
- }
- override func layoutSubviews() {
- if needsRelayout {
- self.relayout()
- }
- super.layoutSubviews()
- }
- func relayout() {
- func relayoutTitleForAlignCenter() {
- for i in 0 ..< self.titles.count {
- let label = UILabel()
- label.tag = firstTabTag + i
- label.text = self.titles[i]
- label.font = self.titleFont
- label.textAlignment = .center
- label.textColor = self.titleTextColor
- if (self.selectedTitleTextColor != nil && i == selectedIndex) {
- label.textColor = self.selectedTitleTextColor
- }
- self.addSubview(label)
- label.useAutoLayout()
- let multiplierBase = CGFloat(1.0) / CGFloat(self.titles.count)
- let multiplierCenterX = (multiplierBase * CGFloat(i) + multiplierBase / CGFloat(2)) * CGFloat(2)
- NSLayoutConstraint(item: label, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: multiplierCenterX, constant: 0).active()
- NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label]-0-|", options: [], metrics: nil, views: ["label": label]).autolayoutInstall()
- buildBadgeNumber(atIndex: i)
- }
- }
- func relayoutTitleForAlignFixMargin(_ left: CGFloat?, _ mid: CGFloat?, _ right: CGFloat?) {
- for i in 0 ..< self.titles.count {
-
- let label = UILabel()
- label.tag = firstTabTag + i
- label.text = self.titles[i]
- label.font = self.titleFont
- label.textAlignment = .center
- label.textColor = self.titleTextColor
- if (self.selectedTitleTextColor != nil && i == selectedIndex) {
- label.textColor = self.selectedTitleTextColor
- }
- self.addSubview(label)
- label.useAutoLayout()
- NSLayoutConstraint.constraints(withVisualFormat: "V:|-0-[label]-0-|", options: [], metrics: nil, views: ["label": label]).autolayoutInstall()
-
- let leftMarginView = UIView().useAutoLayout()
- leftMarginView.tag = firstLayoutViewTag + i
- self.addSubview(leftMarginView)
- leftMarginView.backgroundColor = UIColor.clear
- NSLayoutConstraint.horizontalSpace(leftMarginView, secondView: label)
- NSLayoutConstraint.centerY(leftMarginView, secondView: label)
- NSLayoutConstraint.equalHeight(firstView: leftMarginView, secondView: label)
- if i == 0 {
- NSLayoutConstraint.pinLeading(self, secondView: leftMarginView)
- if let left = left {
- leftMarginView.setLayoutWidth(left)
- }
- } else if i == 1 {
- if let mid = mid {
- leftMarginView.setLayoutWidth(mid)
- }
- if let lastLabel = self.viewWithTag(firstTabTag + i - 1) {
- NSLayoutConstraint.horizontalSpace(lastLabel, secondView: leftMarginView)
- }
- } else {
- if let lastMarginView = self.viewWithTag(firstLayoutViewTag + i - 1) {
- NSLayoutConstraint.equalWidth(firstView: lastMarginView, secondView: leftMarginView)
- }
- if let lastLabel = self.viewWithTag(firstTabTag + i - 1) {
- NSLayoutConstraint.horizontalSpace(lastLabel, secondView: leftMarginView)
- }
- }
- if i == self.titles.count - 1 {
- let rightEdgeView = UIView().useAutoLayout()
- rightEdgeView.tag = firstLayoutViewTag + i + 1
- self.addSubview(rightEdgeView)
- rightEdgeView.backgroundColor = UIColor.clear
- NSLayoutConstraint.horizontalSpace(label, secondView: rightEdgeView)
- NSLayoutConstraint.centerY(rightEdgeView, secondView: label)
- NSLayoutConstraint.equalHeight(firstView: rightEdgeView, secondView: label)
- NSLayoutConstraint.pinTrailing(rightEdgeView, secondView: self)
- let leftEdgeView = self.viewWithTag(firstLayoutViewTag)!
- if let right = right {
-
- rightEdgeView.setLayoutWidth(right)
- if let _ = left {
-
- } else {
-
- NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: leftMarginView)
- }
- } else if let _ = left {
- NSLayoutConstraint.equalWidth(firstView: leftMarginView, secondView: rightEdgeView)
- } else {
- NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: rightEdgeView)
- NSLayoutConstraint.equalWidth(firstView: leftEdgeView, secondView: leftMarginView)
- }
- }
- buildBadgeNumber(atIndex: i)
- }
- }
- self.subviews.forEach {$0.removeFromSuperview()}
-
- switch alignType {
- case .alignCenter:
- relayoutTitleForAlignCenter()
- case .alignFixMargin(let left, let mid, let right) :
- relayoutTitleForAlignFixMargin(left, mid, right)
- }
-
- self.createSlider()
-
- for i in 0 ..< self.titles.count {
- if let label = self.viewWithTag(firstTabTag + i) as? UILabel {
- if i != selectedIndex {
- label.textColor = self.titleTextColor
- } else {
- label.textColor = self.selectedTitleTextColor
- }
- }
- }
- self.needsRelayout = false
- }
- func createSlider() {
- let targetLabel = self.viewWithTag(firstTabTag+selectedIndex)
- if slideLineView != nil {
- sliderView = slideLineView
- } else if slideLineColor != nil {
-
- sliderView = UIImageView(image: UIImage.imageWithColor(slideLineColor!))
- }
- if sliderView != nil {
- self.addSubview(sliderView!)
- sliderView!.useAutoLayout()
- NSLayoutConstraint.constraints(withVisualFormat: "H:[slider(width)]", options: [], metrics: ["width": slideLineSize.width], views: ["slider": sliderView!]).autolayoutInstall()
- NSLayoutConstraint.constraints(withVisualFormat: "V:[slider(height)]-bottom-|", options: [], metrics: ["bottom": slideLineBottom, "height": slideLineSize.height], views: ["slider": sliderView!]).autolayoutInstall()
- self.sliderPosConstraint = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: targetLabel!, attribute: .centerX, multiplier: 1, constant: 0)
- self.sliderPosConstraint!.active()
- }
- }
- func moveSlider(_ left: Bool = false) {
- if sliderView != nil {
- self.sliderPosConstraint?.deActive()
- switch alignType {
- case .alignCenter:
- let multiplierBase = CGFloat(1.0) / CGFloat(self.titles.count)
- let multiplierCenterX = (multiplierBase * CGFloat(self.sliderPortion) + multiplierBase / CGFloat(2)) * CGFloat(2)
- sliderPosConstraint = NSLayoutConstraint(item: sliderView!, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: multiplierCenterX, constant: 0)
- case .alignFixMargin:
- let portion = sliderPortion - Double(Int(sliderPortion))
- if abs(portion) < 0.00001 {
- if let targetLable = self.viewWithTag(Int(sliderPortion) + firstTabTag) {
- sliderPosConstraint = NSLayoutConstraint.centerX(sliderView!, secondView: targetLable)
- }
- } else {
- if let leftLable = self.viewWithTag(Int(sliderPortion) + firstTabTag), let rightLabel = self.viewWithTag(Int(sliderPortion) + firstTabTag + 1) {
- let totalLength = abs(leftLable.center.x - rightLabel.center.x)
- sliderPosConstraint = NSLayoutConstraint.centerX(sliderView!, secondView: leftLable, constant: CGFloat(portion) * totalLength )
- }
- }
- }
- sliderPosConstraint!.active()
- UIView.animate(withDuration: 0.2, animations: { () -> Void in
- self.sliderView!.superview?.layoutIfNeeded()
- })
- }
- }
- static let badgeTagStart = 8090
- func updateBadgeNumber(index: Int, number: Int) {
- if index < badgeNumbers.count {
- badgeNumbers[index] = number
- buildBadgeNumber(atIndex: index)
- }
- }
- fileprivate func buildBadgeNumber(atIndex index: Int) {
- if index >= titles.count {
- return
- }
- let badgeType = badgeTypes[index]
- let number = badgeNumbers[index]
- if number == 0 {
- let badge = self.viewWithTag(SeMobSlideTabView.badgeTagStart+index)
- badge?.removeFromSuperview()
- } else {
- if let titleLabel = self.viewWithTag(firstTabTag+index) as? UILabel {
- let badge = self.viewWithTag(SeMobSlideTabView.badgeTagStart+index)
- badge?.removeFromSuperview()
- switch badgeType {
- case .none :
- break
- case .dot :
- let badgeDot = UIView()
- badgeDot.backgroundColor = UIColor.red
- badgeDot.cornerRadius = 2
- badgeDot.tag = SeMobSlideTabView.badgeTagStart+index
- self.addSubview(badgeDot)
- badgeDot.useAutoLayout()
- NSLayoutConstraint.constraints(withVisualFormat: "H:[title]-1-[badge(==4)]", options: [], metrics: nil, views: ["title": titleLabel, "badge": badgeDot]).autolayoutInstall()
- NSLayoutConstraint(item: badgeDot, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .top, multiplier: 1, constant: 8).active()
- NSLayoutConstraint(item: badgeDot, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 4).active()
- case .number:
- let badgeLabel = UILabel()
- badgeLabel.backgroundColor = UIColor.red
- badgeLabel.cornerRadius = 6
- badgeLabel.font = UIFont.systemFont(ofSize: 10)
- badgeLabel.textColor = UIColor.white
- badgeLabel.tag = SeMobSlideTabView.badgeTagStart+index
- self.addSubview(badgeLabel)
- badgeLabel.useAutoLayout()
- badgeLabel.textAlignment = .center
- badgeLabel.text = "\(number)"
- let metrics = ["width": 13]
- NSLayoutConstraint.constraints(withVisualFormat: "H:[title]-1-[badge(>=width)]", options: [], metrics: metrics, views: ["title": titleLabel, "badge": badgeLabel]).autolayoutInstall()
- NSLayoutConstraint(item: badgeLabel, attribute: .top, relatedBy: .equal, toItem: titleLabel, attribute: .top, multiplier: 1, constant: 7).active()
- NSLayoutConstraint(item: badgeLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 12).active()
- case .customView(let block):
- block(titleLabel, number, SeMobSlideTabView.badgeTagStart+index)
- }
- }
- }
- }
- override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
- if let _ = object as? UIScrollView {
- if let point: NSValue = change?[NSKeyValueChangeKey.newKey] as! NSValue? {
- var pt: CGPoint = CGPoint.zero
- point.getValue(&pt)
- self.setPortionWithScrollViewWithOffset(pt)
- }
- }
- }
- fileprivate func setPortionWithScrollViewWithOffset(_ point: CGPoint) {
- if let sv = scrollview {
- if sv.contentSize.width > 0 {
- let portion = (point.x / sv.contentSize.width) * CGFloat(titles.count)
- sliderPortion = Double(portion)
- }
- }
- }
- }
|