Scroll Segmented Control(Swift)

Scroll Segmented Control(Swift),第1张

概述今天用了一个github上一个比较好用的Segmented Control但是发现不是我要效果,我需要支持scrollView。当栏目数量超过一屏幕,需要能够滑动。 由于联系作者没有回复,我就自己在其基础上增加了下scrollView的支持。 代码比较简单,直接在UIControl下写的。 其中有一个比较有意思的地方,IndicatorView下面放了一个titleMaskView作为mask。用

今天用了一个github上一个比较好用的Segmented Control但是发现不是我要效果,我需要支持scrollVIEw。当栏目数量超过一屏幕,需要能够滑动。

由于联系作者没有回复,我就自己在其基础上增加了下scrollVIEw的支持。

代码比较简单,直接在UIControl下写的。

其中有一个比较有意思的地方,IndicatorVIEw下面放了一个TitleMaskVIEw作为mask。用来遮罩选用的Titles标签。已达到过渡效果。

源代码:

//// SwiftySegmentedControl.swift// SwiftySegmentedControl//// Created by liuYanghui on 2017/1/10.// copyright © 2017年 Yanghui.liu. All rights reserved.//import UIKit// MARK: - SwiftySegmentedControl@IBDesignable open class SwiftySegmentedControl: UIControl {    // MARK: IndicatorVIEw    fileprivate class IndicatorVIEw: UIVIEw {        // MARK: PropertIEs        fileprivate let TitleMaskVIEw = UIVIEw()        fileprivate let line = UIVIEw()        fileprivate let lineHeight: CGfloat = 2.0        fileprivate var cornerRadius: CGfloat = 0 {            dIDSet {                layer.cornerRadius = cornerRadius                TitleMaskVIEw.layer.cornerRadius = cornerRadius            }        }        overrIDe open var frame: CGRect {            dIDSet {                TitleMaskVIEw.frame = frame                let lineFrame = CGRect(x: 0,y: frame.size.height - lineHeight,wIDth: frame.size.wIDth,height: lineHeight)                line.frame = lineFrame            }        }        open var linecolor = UIcolor.clear {            dIDSet {                line.backgroundcolor = linecolor            }        }        // MARK: lifecycle        init() {            super.init(frame: CGRect.zero)            finishInit()        }        required init?(coder aDecoder: NSCoder) {            super.init(coder: aDecoder)            finishInit()        }        fileprivate func finishInit() {            layer.masksToBounds = true            TitleMaskVIEw.backgroundcolor = UIcolor.black            addSubvIEw(line)        }        overrIDe open func layoutSubvIEws() {            super.layoutSubvIEws()        }    }    // MARK: Constants    fileprivate struct Animation {        fileprivate static let withBounceDuration: TimeInterval = 0.3        fileprivate static let springdamPing: CGfloat = 0.75        fileprivate static let withoutBounceDuration: TimeInterval = 0.2    }    fileprivate struct color {        fileprivate static let background: UIcolor = UIcolor.white        fileprivate static let Title: UIcolor = UIcolor.black        fileprivate static let indicatorVIEwBackground: UIcolor = UIcolor.black        fileprivate static let selectedTitle: UIcolor = UIcolor.white    }    // MARK: Error handling    public enum IndexError: Error {        case indexBeyondBounds(UInt)    }    // MARK: PropertIEs    /// The selected index    public fileprivate(set) var index: UInt    /// The Titles / options available for selection    public var Titles: [String] {        get {            let TitleLabels = TitleLabelsVIEw.subvIEws as! [UILabel]            return TitleLabels.map { $0.text! }        }        set {            guard newValue.count > 1 else {                return            }            let labels: [(UILabel,UILabel)] = newValue.map {                (string) -> (UILabel,UILabel) in                let TitleLabel = UILabel()                TitleLabel.textcolor = Titlecolor                TitleLabel.text = string                TitleLabel.lineBreakMode = .byTruncatingTail                TitleLabel.textAlignment = .center                TitleLabel.Font = TitleFont                TitleLabel.layer.borderWIDth = TitleborderWIDth                TitleLabel.layer.bordercolor = Titlebordercolor                TitleLabel.layer.cornerRadius = indicatorVIEw.cornerRadius                let selectedTitleLabel = UILabel()                selectedTitleLabel.textcolor = selectedTitlecolor                selectedTitleLabel.text = string                selectedTitleLabel.lineBreakMode = .byTruncatingTail                selectedTitleLabel.textAlignment = .center                selectedTitleLabel.Font = selectedTitleFont                return (TitleLabel,selectedTitleLabel)            }            TitleLabelsVIEw.subvIEws.forEach({ $0.removeFromSupervIEw() })            selectedTitleLabelsVIEw.subvIEws.forEach({ $0.removeFromSupervIEw() })            for (inactiveLabel,activeLabel) in labels {                TitleLabelsVIEw.addSubvIEw(inactiveLabel)                selectedTitleLabelsVIEw.addSubvIEw(activeLabel)            }            setNeedsLayout()        }    }    /// Whether the indicator should bounce when selecting a new index. Defaults to true    public var bouncesOnChange = true    /// Whether the the control should always send the .ValueChanged event,regardless of the index remaining unchanged after interaction. Defaults to false    public var alwaysAnnouncesValue = false    /// Whether to send the .ValueChanged event immediately or wait for animations to complete. Defaults to true    public var announcesValueImmediately = true    /// Whether the the control should ignore pan gestures. Defaults to false    public var panningDisabled = false    /// The control's and indicator's corner radii    @IBInspectable public var cornerRadius: CGfloat {        get {            return layer.cornerRadius        }        set {            layer.cornerRadius = newValue            indicatorVIEw.cornerRadius = newValue - indicatorVIEwInset            TitleLabels.forEach { $0.layer.cornerRadius = indicatorVIEw.cornerRadius }        }    }    /// The indicator vIEw's background color    @IBInspectable public var indicatorVIEwBackgroundcolor: UIcolor? {        get {            return indicatorVIEw.backgroundcolor        }        set {            indicatorVIEw.backgroundcolor = newValue        }    }    /// margin spacing between Titles. Default to 33.    @IBInspectable public var marginSpace: CGfloat = 33 {        dIDSet { setNeedsLayout() }    }    /// The indicator vIEw's inset. Defaults to 2.0    @IBInspectable public var indicatorVIEwInset: CGfloat = 2.0 {        dIDSet { setNeedsLayout() }    }    /// The indicator vIEw's border wIDth    public var indicatorVIEwborderWIDth: CGfloat {        get {            return indicatorVIEw.layer.borderWIDth        }        set {            indicatorVIEw.layer.borderWIDth = newValue        }    }    /// The indicator vIEw's border wIDth    public var indicatorVIEwbordercolor: CGcolor? {        get {            return indicatorVIEw.layer.bordercolor        }        set {            indicatorVIEw.layer.bordercolor = newValue        }    }    /// The indicator vIEw's line color    public var indicatorVIEwlinecolor: UIcolor {        get {            return indicatorVIEw.linecolor        }        set {            indicatorVIEw.linecolor = newValue        }    }    /// The text color of the non-selected Titles / options    @IBInspectable public var Titlecolor: UIcolor  {        dIDSet {            TitleLabels.forEach { $0.textcolor = Titlecolor }        }    }    /// The text color of the selected Title / option    @IBInspectable public var selectedTitlecolor: UIcolor {        dIDSet {            selectedTitleLabels.forEach { $0.textcolor = selectedTitlecolor }        }    }    /// The Titles' Font    public var TitleFont: UIFont = UILabel().Font {        dIDSet {            TitleLabels.forEach { $0.Font = TitleFont }        }    }    /// The selected Title's Font    public var selectedTitleFont: UIFont = UILabel().Font {        dIDSet {            selectedTitleLabels.forEach { $0.Font = selectedTitleFont }        }    }    /// The Titles' border wIDth    public var TitleborderWIDth: CGfloat = 0.0 {        dIDSet {            TitleLabels.forEach { $0.layer.borderWIDth = TitleborderWIDth }        }    }    /// The Titles' border color    public var Titlebordercolor: CGcolor = UIcolor.clear.cgcolor {        dIDSet {            TitleLabels.forEach { $0.layer.bordercolor = Titlebordercolor }        }    }    // MARK: - Private propertIEs    fileprivate let contentScrollVIEw: UIScrollVIEw = {        let scrollVIEw = UIScrollVIEw()        scrollVIEw.showsverticalScrollindicator = false        scrollVIEw.showsHorizontalScrollindicator = false        return scrollVIEw    }()    fileprivate let TitleLabelsVIEw = UIVIEw()    fileprivate let selectedTitleLabelsVIEw = UIVIEw()    fileprivate let indicatorVIEw = IndicatorVIEw()    fileprivate var initialindicatorVIEwFrame: CGRect?    fileprivate var tapGestureRecognizer: UITapGestureRecognizer!    fileprivate var panGestureRecognizer: UIPanGestureRecognizer!    fileprivate var wIDth: CGfloat { return bounds.wIDth }    fileprivate var height: CGfloat { return bounds.height }    fileprivate var TitleLabelsCount: Int { return TitleLabelsVIEw.subvIEws.count }    fileprivate var TitleLabels: [UILabel] { return TitleLabelsVIEw.subvIEws as! [UILabel] }    fileprivate var selectedTitleLabels: [UILabel] { return selectedTitleLabelsVIEw.subvIEws as! [UILabel] }    fileprivate var totalinsetSize: CGfloat { return indicatorVIEwInset * 2.0 }    fileprivate lazy var defaultTitles: [String] = { return ["First","Second"] }()    fileprivate var TitlesWIDth: [CGfloat] {        return Titles.map {            let statusLabelText: Nsstring = $0 as Nsstring            let size = CGSize(wIDth: wIDth,height: height - totalinsetSize)            let dic = NSDictionary(object: TitleFont,forKey: NSFontAttributename as NScopying)            let strSize = statusLabelText.boundingRect(with: size,options: .useslineFragmentOrigin,attributes: dic as? [String : AnyObject],context: nil).size            return strSize.wIDth        }    }    // MARK: lifecycle    required public init?(coder aDecoder: NSCoder) {        index = 0        Titlecolor = color.Title        selectedTitlecolor = color.selectedTitle        super.init(coder: aDecoder)        Titles = defaultTitles        finishInit()    }    public init(frame: CGRect,Titles: [String],index: UInt,backgroundcolor: UIcolor,Titlecolor: UIcolor,indicatorVIEwBackgroundcolor: UIcolor,selectedTitlecolor: UIcolor) {        self.index = index        self.Titlecolor = Titlecolor        self.selectedTitlecolor = selectedTitlecolor        super.init(frame: frame)        self.Titles = Titles        self.backgroundcolor = backgroundcolor        self.indicatorVIEwBackgroundcolor = indicatorVIEwBackgroundcolor        finishInit()    }    @available(*,deprecated,message: "Use init(frame:Titles:index:backgroundcolor:Titlecolor:indicatorVIEwBackgroundcolor:selectedTitlecolor:) instead.")    convenIEnce overrIDe public init(frame: CGRect) {        self.init(frame: frame,Titles: ["First","Second"],index: 0,backgroundcolor: color.background,Titlecolor: color.Title,indicatorVIEwBackgroundcolor: color.indicatorVIEwBackground,selectedTitlecolor: color.selectedTitle)    }    @available(*,unavailable,message: "Use init(frame:Titles:index:backgroundcolor:Titlecolor:indicatorVIEwBackgroundcolor:selectedTitlecolor:) instead.")    convenIEnce init() {        self.init(frame: CGRect.zero,selectedTitlecolor: color.selectedTitle)    }    fileprivate func finishInit() {        layer.masksToBounds = true        addSubvIEw(contentScrollVIEw)        contentScrollVIEw.addSubvIEw(TitleLabelsVIEw)        contentScrollVIEw.addSubvIEw(indicatorVIEw)        contentScrollVIEw.addSubvIEw(selectedTitleLabelsVIEw)        selectedTitleLabelsVIEw.layer.mask = indicatorVIEw.TitleMaskVIEw.layer        tapGestureRecognizer = UITapGestureRecognizer(target: self,action: #selector(SwiftySegmentedControl.tapped(_:)))        addGestureRecognizer(tapGestureRecognizer)        panGestureRecognizer = UIPanGestureRecognizer(target: self,action: #selector(SwiftySegmentedControl.panned(_:)))        panGestureRecognizer.delegate = self        addGestureRecognizer(panGestureRecognizer)    }    overrIDe open func layoutSubvIEws() {        super.layoutSubvIEws()        guard TitleLabelsCount > 1 else {            return        }        contentScrollVIEw.frame = bounds        let allElementsWIDth = TitlesWIDth.reduce(0,{$0 + $1}) + CGfloat(TitleLabelsCount) * marginSpace        contentScrollVIEw.contentSize = CGSize(wIDth: max(allElementsWIDth,wIDth),height: 0)        TitleLabelsVIEw.frame = bounds        selectedTitleLabelsVIEw.frame = bounds        indicatorVIEw.frame = elementFrame(forIndex: index)        for index in 0...TitleLabelsCount-1 {            let frame = elementFrame(forIndex: UInt(index))            TitleLabelsVIEw.subvIEws[index].frame = frame            selectedTitleLabelsVIEw.subvIEws[index].frame = frame        }    }    // MARK: Index Setting    /*! Sets the control's index. - parameter index: The new index - parameter animated: (Optional) Whether the change should be animated or not. Defaults to true. - throws: An error of type IndexBeyondBounds(UInt) is thrown if an index beyond the available indices is passed. */    public func setIndex(_ index: UInt,animated: Bool = true) throws {        guard TitleLabels.indices.contains(Int(index)) else {            throw IndexError.indexBeyondBounds(index)        }        let oldindex = self.index        self.index = index        moveIndicatorVIEwToIndex(animated,shouldSendEvent: (self.index != oldindex || alwaysAnnouncesValue))        fixedScrollVIEwOffset(Int(self.index))    }    // MARK: Fixed ScrollVIEw offset    fileprivate func fixedScrollVIEwOffset(_ focusIndex: Int) {        guard contentScrollVIEw.contentSize.wIDth > wIDth else {            return        }        let targetMIDX = self.TitleLabels[Int(self.index)].frame.mIDX        let offsetX = contentScrollVIEw.contentOffset.x        let addOffsetX = targetMIDX - offsetX - wIDth / 2        let newOffSetX = min(max(0,offsetX + addOffsetX),contentScrollVIEw.contentSize.wIDth - wIDth)        let point = CGPoint(x: newOffSetX,y: contentScrollVIEw.contentOffset.y)        contentScrollVIEw.setContentOffset(point,animated: true)    }    // MARK: Animations    fileprivate func moveIndicatorVIEwToIndex(_ animated: Bool,shouldSendEvent: Bool) {        if animated {            if shouldSendEvent && announcesValueImmediately {                sendActions(for: .valueChanged)            }            UIVIEw.animate(withDuration: bouncesOnChange ? Animation.withBounceDuration : Animation.withoutBounceDuration,delay: 0.0,usingSpringWithdamPing: bouncesOnChange ? Animation.springdamPing : 1.0,initialSpringVeLocity: 0.0,options: [UIVIEwAnimationoptions.beginFromCurrentState,UIVIEwAnimationoptions.curveEaSEOut],animations: {                            () -> VoID in                            self.moveIndicatorVIEw()            },completion: { (finished) -> VoID in                if finished && shouldSendEvent && !self.announcesValueImmediately {                    self.sendActions(for: .valueChanged)                }            })        } else {            moveIndicatorVIEw()            sendActions(for: .valueChanged)        }    }    // MARK: Helpers    fileprivate func elementFrame(forIndex index: UInt) -> CGRect {        // 计算出label的宽度,label宽度 = (text宽度) + marginSpace        // | <= 0.5 * marginSpace => text1 <= 0.5 * marginSpace => | <= 0.5 * marginSpace => text2 <= 0.5 * marginSpace => |        // 如果总宽度小于bunds.wIDth,则均分宽度 label宽度 = bunds.wIDth / count        let allElementsWIDth = TitlesWIDth.reduce(0,{$0 + $1}) + CGfloat(TitleLabelsCount) * marginSpace        if allElementsWIDth < wIDth {            let elementWIDth = (wIDth - totalinsetSize) / CGfloat(TitleLabelsCount)            return CGRect(x: CGfloat(index) * elementWIDth + indicatorVIEwInset,y: indicatorVIEwInset,wIDth: elementWIDth,height: height - totalinsetSize)        } else {            let TitlesWIDth = self.TitlesWIDth            let frontTitlesWIDth = TitlesWIDth.enumerated().reduce(CGfloat(0)) { (total,current) in                return current.0 < Int(index) ? total + current.1 : total            }            let x = frontTitlesWIDth + CGfloat(index) * marginSpace            return CGRect(x: x,wIDth: TitlesWIDth[Int(index)] + marginSpace,height: height - totalinsetSize)        }    }    fileprivate func nearestIndex(topoint point: CGPoint) -> UInt {        let distances = TitleLabels.map { abs(point.x - $0.center.x) }        return UInt(distances.index(of: distances.min()!)!)    }    fileprivate func moveIndicatorVIEw() {        indicatorVIEw.frame = TitleLabels[Int(self.index)].frame        layoutIfNeeded()    }    // MARK: Action handlers    @objc fileprivate func tapped(_ gestureRecognizer: UITapGestureRecognizer!) {        let location = gestureRecognizer.location(in: contentScrollVIEw)        try! setIndex(nearestIndex(topoint: location))    }    @objc fileprivate func panned(_ gestureRecognizer: UIPanGestureRecognizer!) {        guard !panningDisabled else {            return        }        switch gestureRecognizer.state {        case .began:            initialindicatorVIEwFrame = indicatorVIEw.frame        case .changed:            var frame = initialindicatorVIEwFrame!            frame.origin.x += gestureRecognizer.translation(in: self).x            frame.origin.x = max(min(frame.origin.x,bounds.wIDth - indicatorVIEwInset - frame.wIDth),indicatorVIEwInset)            indicatorVIEw.frame = frame        case .ended,.Failed,.cancelled:            try! setIndex(nearestIndex(topoint: indicatorVIEw.center))        default: break        }    }}// MARK: - UIGestureRecognizerDelegateextension SwiftySegmentedControl: UIGestureRecognizerDelegate {    overrIDe open func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {        if gestureRecognizer == panGestureRecognizer {            return indicatorVIEw.frame.contains(gestureRecognizer.location(in: contentScrollVIEw))        }        return super.gestureRecognizerShouldBegin(gestureRecognizer)    }}

使用方式

fileprivate func setupControl() {        let vIEwSegmentedControl = SwiftySegmentedControl(            frame: CGRect(x: 0.0,y: 430.0,wIDth: vIEw.bounds.wIDth,height: 50.0),Titles: ["All","New","Pictures","One","Two","Three","Four","Five","Six","Artists","Albums","Recent"],index: 1,backgroundcolor: UIcolor(red:0.11,green:0.12,blue:0.13,Alpha:1.00),Titlecolor: .white,indicatorVIEwBackgroundcolor: UIcolor(red:0.11,selectedTitlecolor: UIcolor(red:0.97,green:0.00,blue:0.24,Alpha:1.00))        vIEwSegmentedControl.autoresizingMask = [.flexibleWIDth]        vIEwSegmentedControl.indicatorVIEwInset = 0        vIEwSegmentedControl.cornerRadius = 0.0        vIEwSegmentedControl.TitleFont = UIFont(name: "HelveticaNeue",size: 16.0)!        vIEwSegmentedControl.selectedTitleFont = UIFont(name: "HelveticaNeue",size: 16.0)!        vIEwSegmentedControl.bouncesOnChange = false        // 是否禁止拖动选择,注意,这里有个问题我还没改,如果Titles长度超过1屏幕,或者说,你想使用下划线,建议禁止拖动选择。        vIEwSegmentedControl.panningDisabled = true         // 下划线颜色。默认透明        vIEwSegmentedControl.indicatorVIEwlinecolor = UIcolor.red        vIEw.addSubvIEw(vIEwSegmentedControl)    }

Github: SwiftySegmentedControl

总结

以上是内存溢出为你收集整理的Scroll Segmented Control(Swift)全部内容,希望文章能够帮你解决Scroll Segmented Control(Swift)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1067414.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-26
下一篇 2022-05-26

发表评论

登录后才能评论

评论列表(0条)

保存