// // SegmentedControl.swift // SegmentedControl // // Created by Xin Hong on 15/12/29. // Copyright © 2015年 Teambition. All rights reserved. // import UIKit public protocol SegmentedControlDelegate: class { func segmentedControl(_ segmentedControl: SegmentedControl, didSelectIndex selectedIndex: Int) func segmentedControl(_ segmentedControl: SegmentedControl, didLongPressIndex longPressIndex: Int) } public extension SegmentedControlDelegate { func segmentedControl(_ segmentedControl: SegmentedControl, didSelectIndex selectedIndex: Int) { } func segmentedControl(_ segmentedControl: SegmentedControl, didLongPressIndex longPressIndex: Int) { } } open class SegmentedControl: UIControl { open weak var delegate: SegmentedControlDelegate? open fileprivate(set) var selectedIndex = 0 { didSet { setNeedsDisplay() } } open var segmentWidth: CGFloat? open var minimumSegmentWidth: CGFloat? open var maximumSegmentWidth: CGFloat? open var isAnimationEnabled = true open var isUserDragEnabled = true open fileprivate(set) var style: SegmentedControlStyle = .text open var selectionBoxStyle: SegmentedControlSelectionBoxStyle = .none open var selectionBoxColor = UIColor.blue open var selectionBoxCornerRadius: CGFloat = 0 open var selectionBoxEdgeInsets = UIEdgeInsets.zero open var selectionIndicatorStyle: SegmentedControlSelectionIndicatorStyle = .none open var selectionIndicatorColor = UIColor.black open var selectionIndicatorHeight = SelectionIndicator.defaultHeight open var selectionIndicatorEdgeInsets = UIEdgeInsets.zero open var titleAttachedIconPositionOffset: (x: CGFloat, y: CGFloat ) = (0, 0) open fileprivate(set) var titles = [NSAttributedString]() open fileprivate(set) var selectedTitles: [NSAttributedString]? open fileprivate(set) var images = [UIImage]() open fileprivate(set) var selectedImages: [UIImage]? open fileprivate(set) var titleAttachedIcons: [UIImage]? open fileprivate(set) var selectedTitleAttachedIcons: [UIImage]? open var isLongPressEnabled = false { didSet { if isLongPressEnabled { longPressGesture = UILongPressGestureRecognizer() longPressGesture!.addTarget(self, action: #selector(segmentedControlLongPressed(_:))) longPressGesture!.minimumPressDuration = longPressMinimumPressDuration scrollView.addGestureRecognizer(longPressGesture!) longPressGesture!.delegate = self } else if let _ = longPressGesture { scrollView.removeGestureRecognizer(longPressGesture!) longPressGesture!.delegate = nil longPressGesture = nil } } } open var isUnselectedSegmentsLongPressEnabled = false open var longPressMinimumPressDuration: CFTimeInterval = 0.5 { didSet { assert(longPressMinimumPressDuration >= 0.5, "MinimumPressDuration of LongPressGestureRecognizer must be no less than 0.5") if let longPressGesture = longPressGesture { longPressGesture.minimumPressDuration = longPressMinimumPressDuration } } } open fileprivate(set) var isLongPressActivated = false fileprivate lazy var scrollView: SCScrollView = { let scrollView = SCScrollView() scrollView.scrollsToTop = false scrollView.isScrollEnabled = true scrollView.showsHorizontalScrollIndicator = false scrollView.showsVerticalScrollIndicator = false return scrollView }() fileprivate lazy var selectionBoxLayer = CALayer() fileprivate lazy var selectionIndicatorLayer = CALayer() fileprivate var longPressGesture: UILongPressGestureRecognizer? // MARK: - Public functions open class func initWithTitles(_ titles: [NSAttributedString], selectedTitles: [NSAttributedString]?) -> SegmentedControl { let segmentedControl = SegmentedControl(frame: CGRect.zero) segmentedControl.style = .text segmentedControl.titles = titles segmentedControl.selectedTitles = selectedTitles return segmentedControl } open class func initWithImages(_ images: [UIImage], selectedImages: [UIImage]?) -> SegmentedControl { let segmentedControl = SegmentedControl(frame: CGRect.zero) segmentedControl.style = .image segmentedControl.images = images segmentedControl.selectedImages = selectedImages return segmentedControl } open func setTitles(_ titles: [NSAttributedString], selectedTitles: [NSAttributedString]?) { style = .text self.titles = titles self.selectedTitles = selectedTitles } open func setImages(_ images: [UIImage], selectedImages: [UIImage]?) { style = .image self.images = images self.selectedImages = selectedImages } open func setTitleAttachedIcons(_ titleAttachedIcons: [UIImage]?, selectedTitleAttachedIcons: [UIImage]?) { self.titleAttachedIcons = titleAttachedIcons self.selectedTitleAttachedIcons = selectedTitleAttachedIcons } open func setSelected(at index: Int, animated: Bool) { if !(0.., with event: UIEvent?) { if isLongPressActivated { return } if let touch = touches.first { let touchLocation = touch.location(in: self) if !bounds.contains(touchLocation) { return } if singleSegmentWidth() == 0 { return } let touchIndex = Int((touchLocation.x + scrollView.contentOffset.x) / singleSegmentWidth()) if 0.. Bool { if gestureRecognizer == longPressGesture { if let longPressIndex = locationIndex(for: gestureRecognizer) { return isUnselectedSegmentsLongPressEnabled ? true : longPressIndex == selectedIndex } } return false } @objc func segmentedControlLongPressed(_ gesture: UIGestureRecognizer) { switch gesture.state { case .possible: print("LongPressGesture Possible!") break case .began: print("LongPressGesture Began!") isLongPressActivated = true longPressDidBegin(gesture) break case .changed: print("LongPressGesture Changed!") break case .ended: print("LongPressGesture Ended!") isLongPressActivated = false break case .cancelled: print("LongPressGesture Cancelled!") isLongPressActivated = false break case .failed: print("LongPressGesture Failed!") isLongPressActivated = false break } } fileprivate func locationIndex(for gesture: UIGestureRecognizer) -> Int? { let longPressLocation = gesture.location(in: self) if !bounds.contains(longPressLocation) { return nil } if singleSegmentWidth() == 0 { return nil } let longPressIndex = Int((longPressLocation.x + scrollView.contentOffset.x) / singleSegmentWidth()) return longPressIndex } fileprivate func longPressDidBegin(_ gesture: UIGestureRecognizer) { if let longPressIndex = locationIndex(for: gesture) { if longPressIndex != selectedIndex && !isUnselectedSegmentsLongPressEnabled { return } if 0.. CGSize { let size = attributedString.size() return CGRect(origin: CGPoint.zero, size: size).integral.size } fileprivate func selectedImage(at index: Int) -> UIImage? { if let selectedImages = selectedImages { if 0.. NSAttributedString? { if let selectedTitles = selectedTitles { if 0.. UIImage? { if let titleAttachedIcons = titleAttachedIcons { if 0.. UIImage? { if let selectedTitleAttachedIcons = selectedTitleAttachedIcons { if 0.. Int { switch style { case .text: return titles.count case .image: return images.count } } fileprivate func frameForSelectionBox() -> CGRect { if selectionBoxStyle == .none { return CGRect.zero } let xPosition: CGFloat = { return singleSegmentWidth() * CGFloat(selectedIndex) }() let fullRect = CGRect(x: xPosition, y: 0, width: singleSegmentWidth(), height: frame.height) let boxRect = CGRect(x: fullRect.origin.x + selectionBoxEdgeInsets.left, y: fullRect.origin.y + selectionBoxEdgeInsets.top, width: fullRect.width - (selectionBoxEdgeInsets.left + selectionBoxEdgeInsets.right), height: fullRect.height - (selectionBoxEdgeInsets.top + selectionBoxEdgeInsets.bottom)) return boxRect } fileprivate func frameForSelectionIndicator() -> CGRect { if selectionIndicatorStyle == .none { return CGRect.zero } let xPosition: CGFloat = { return singleSegmentWidth() * CGFloat(selectedIndex) }() let yPosition: CGFloat = { switch selectionIndicatorStyle { case .bottom: return frame.height - selectionIndicatorHeight case .top: return 0 default: return 0 } }() let fullRect = CGRect(x: xPosition, y: yPosition, width: singleSegmentWidth(), height: selectionIndicatorHeight) let indicatorRect = CGRect(x: fullRect.origin.x + selectionIndicatorEdgeInsets.left, y: fullRect.origin.y + selectionIndicatorEdgeInsets.top, width: fullRect.width - (selectionIndicatorEdgeInsets.left + selectionIndicatorEdgeInsets.right), height: fullRect.height - (selectionIndicatorEdgeInsets.top + selectionIndicatorEdgeInsets.bottom)) return indicatorRect } fileprivate func rectForSelectedIndex() -> CGRect { return CGRect(x: singleSegmentWidth() * CGFloat(selectedIndex), y: 0, width: singleSegmentWidth(), height: frame.height) } fileprivate func singleSegmentWidth() -> CGFloat { func defaultSegmentWidth() -> CGFloat { if segmentsCount() == 0 { return 0 } var segmentWidth = frame.width / CGFloat(segmentsCount()) if let minimumSegmentWidth = minimumSegmentWidth { if segmentWidth < minimumSegmentWidth { segmentWidth = minimumSegmentWidth } } if let maximumSegmentWidth = maximumSegmentWidth { if segmentWidth > maximumSegmentWidth { segmentWidth = maximumSegmentWidth } } return segmentWidth } if let segmentWidth = segmentWidth { return segmentWidth } return defaultSegmentWidth() } fileprivate func totalSegmentsWidth() -> CGFloat { return CGFloat(segmentsCount()) * singleSegmentWidth() } }