Swift 星星評(píng)分控件 改寫

前言:

突然需要使用星星評(píng)分控件了。我們使用Swift開發(fā),閑的就改寫了一個(gè)庫狞玛。

Objective-C
原作者地址: https://github.com/akiroom/AXRatingView

Swift
代碼地址:https://github.com/gityuency/Autolayout
示例代碼類名 【RatingStarsViewController】 【SlidingRatingView】

所有支持的效果都在這個(gè)gif圖片里面了。
這次的改寫學(xué)到了一個(gè)騷東西,就是自己寫的View可以在XIB里面設(shè)置可編輯的屬性。

效果圖:


星星評(píng)分控件.gif

這個(gè)控件的部分屬性值設(shè)置支持XIB設(shè)置


XIB編輯.gif

源代碼,復(fù)制粘貼就能用

//
//  SlidingRatingView.swift
//  姬友大人
//
//  Created by 姬友大人 on 2019/5/20.
//  Copyright ? 2019 FunPlus. All rights reserved.
//

import Foundation
import UIKit

@IBDesignable
class SlidingRatingView: UIControl {
    
    private var starMaskLayer: CALayer?
    ///正常狀態(tài)
    private var basementLayer: CALayer?
    ///高亮狀態(tài)
    private var highlightLayer: CALayer?
    
    ///計(jì)算得到文字大小
    private var cachedMarkCharacterSize = CGSize.zero;
    
    /// 星星個(gè)數(shù)的浮點(diǎn)型數(shù)據(jù),用于計(jì)算,省的每次都要轉(zhuǎn)換
    private var numberOfStar_float: CGFloat = 5
    
    // 設(shè)置星星的個(gè)數(shù)
    @IBInspectable var numberOfStar: Int = 5 {
        didSet {
            if oldValue != numberOfStar {
                numberOfStar_float = CGFloat(numberOfStar)
                self.invalidateIntrinsicContentSize()
                self.setNeedsDisplay()
            }
        }
    }
    
    //設(shè)置默認(rèn)的字符
    @IBInspectable var markCharacter: String = "\u{2605}" {
        didSet {
            if oldValue != markCharacter {
                markImage = nil
                cachedMarkCharacterSize = CGSize.zero
                self.invalidateIntrinsicContentSize()
                self.setNeedsDisplay()
            }
        }
    }
    
    ///設(shè)置默認(rèn)的字體
    @IBInspectable var markFont: UIFont = UIFont.systemFont(ofSize: 22) {
        didSet {
            if oldValue != markFont {
                markImage = nil;
                setNeedsDisplay()
            }
        }
    }
    
    
    ///如果有圖片,優(yōu)先使用圖片, 如果沒有圖片就使用文字
    @IBInspectable var markImage: UIImage?
    
    ///正常顏色
    @IBInspectable var baseColor: UIColor = UIColor.lightGray {
        didSet {
            if oldValue != baseColor {
                basementLayer?.removeFromSuperlayer();
                basementLayer = nil
                setNeedsDisplay();
            }
        }
    }
    
    ///高亮顏色
    @IBInspectable var highlightColor: UIColor = UIColor.red {
        didSet {
            if oldValue != highlightColor {
                highlightLayer?.removeFromSuperlayer()
                starMaskLayer?.removeFromSuperlayer()
                highlightLayer = nil
                starMaskLayer = nil
                setNeedsDisplay()
            }
        }
    }
    
    ///當(dāng)前的值
    @IBInspectable var value: Float = 0.0 {
        didSet {
            if oldValue != value {
                value = min(max(value, 0.0), Float(numberOfStar_float))
                setNeedsDisplay()
            }
        }
    }
    
    ///步長(zhǎng)
    @IBInspectable var stepInterval: CGFloat = 0.0 {
        didSet {
            stepInterval = max(stepInterval, 0.0)
        }
    }
    
    ///最小值
    @IBInspectable var minimumValue: CGFloat = 0.0
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func sizeToFit() {
        super.sizeToFit()
        self.frame = CGRect(origin: self.frame.origin, size: self.intrinsicContentSize)
    }
    
    /// 這個(gè)是什么,沒有見過
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return self.intrinsicContentSize;
    }
    
    override var intrinsicContentSize: CGSize {
        var size = CGSize.zero
        if let markImage = markImage {
            size = markImage.size
        } else {
            size = needMarkCharacterSize()
        }
        return CGSize(width: size.width * numberOfStar_float, height: size.height);
    }
    
    override func draw(_ rect: CGRect) {
        
        if starMaskLayer == nil {
            
            starMaskLayer = needMaskLayer();
            layer.mask = starMaskLayer;
            
            basementLayer = needBasementLayer();
            layer.addSublayer(basementLayer!);
            
            highlightLayer = needHighlightLayer()
            layer.addSublayer(highlightLayer!);
        }
        
        let selfWidth =  markImage!.size.width * numberOfStar_float
        let selfHalfWidth = selfWidth / 2
        let selfHalfHeight = frame.size.height / 2
        let frameOffsetX = (frame.size.width - selfWidth) / 2;
        let offsetX = selfWidth / numberOfStar_float * (numberOfStar_float - CGFloat(value))
        CATransaction.begin()
        CATransaction.setValue(true, forKey: kCATransactionDisableActions)
        highlightLayer?.position = CGPoint(x: selfHalfWidth - (offsetX - frameOffsetX), y: selfHalfHeight)
        CATransaction.commit()
    }
    
    /// 這個(gè)方法返回字符的尺寸
    func needMarkCharacterSize() -> CGSize {
        if cachedMarkCharacterSize.equalTo(CGSize.zero) {  //如果這個(gè)緩存之前沒有存下數(shù)據(jù), 是空的, 就重新計(jì)算
            let size: CGSize = markCharacter.size(withAttributes: [NSAttributedString.Key.font: markFont])
            cachedMarkCharacterSize = size;
        }
        return cachedMarkCharacterSize
    }
    
    /// 返回圖片
    func needMarkImage() -> UIImage {
        
        if let markImage = markImage {  //如果有圖片,就直接返回圖片
            
            return markImage
            
        } else { //如果沒有圖片,就返回文字生成的圖片
            
            let size = self.needMarkCharacterSize()
            UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
            UIColor.black.set()
            let text: NSString =  NSString(cString: markCharacter.cString(using: .utf8)!, encoding: String.Encoding.utf8.rawValue)!
            text.draw(at: CGPoint.zero, withAttributes: [NSAttributedString.Key.font: markFont, NSAttributedString.Key.foregroundColor: UIColor.black]);
            let image = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            markImage = image
            return markImage!
        }
    }
    
    
    /// 生成 mask layer
    func needMaskLayer() -> CALayer {
        
        // 這里是按照p圖片生成的 image, 一定是有東西的
        markImage = self.needMarkImage();
        
        let starMaskLayer = CALayer();
        starMaskLayer.isOpaque = false;
        
        if let markimage = markImage {
            
            let markWidth = markimage.size.width;
            let markHalfWidth = markWidth / 2
            let markHeight = markimage.size.height
            let markHalfHeight = markHeight / 2;
            
            for i in 0..<numberOfStar {
                let starLayer = CALayer()
                starLayer.contents = markimage.cgImage
                starLayer.bounds = CGRect(origin: CGPoint.zero, size: markimage.size)
                starLayer.position = CGPoint(x: markHalfWidth + markWidth * CGFloat(i), y: markHalfHeight)
                starMaskLayer.addSublayer(starLayer)
            }
            let totalStartsWidth = (markWidth * numberOfStar_float);
            let frameOffsetX = (self.frame.size.width - totalStartsWidth) / 2
            let frameOffsetY = (self.frame.size.height - markimage.size.height) / 2
            starMaskLayer.frame = CGRect(x: frameOffsetX, y: frameOffsetY, width: markimage.size.width * numberOfStar_float, height: markimage.size.height)
        }
        return starMaskLayer;
    }
    
    /// 設(shè)置  Basement Layer
    func needBasementLayer() -> CALayer {
        let layer = CALayer()
        layer.backgroundColor = baseColor.cgColor
        if let markimage = markImage {
            layer.bounds = CGRect(origin: CGPoint.zero, size: CGSize(width: markimage.size.width * numberOfStar_float, height: markimage.size.height))
            layer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        }
        return layer;
    }
    
    /// 生成高亮laier
    func needHighlightLayer() -> CALayer {
        let layer = CALayer()
        layer.backgroundColor = highlightColor.cgColor
        if let markimage = markImage {
            layer.bounds = CGRect(origin: CGPoint.zero, size: CGSize(width: markimage.size.width * numberOfStar_float, height: markimage.size.height))
            layer.position = CGPoint(x: bounds.midX, y: bounds.midY)
        }
        return layer;
    }
    
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        touchesMoved(touches, with: event)
    }
    
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let location = touches.first?.location(in: self)
        if let markimage = markImage {
            let totalStartsWidth = markimage.size.width * numberOfStar_float
            let frameOffsetX = (frame.size.width - totalStartsWidth ) / 2
            let frameOffsetY = (frame.size.height - markimage.size.height) / 2
            let rect = CGRect(x: frameOffsetX - markimage.size.width, y: frameOffsetY, width: totalStartsWidth + markimage.size.width, height: markimage.size.height)
            if rect.contains(location!) {
                var value = ((location!.x - frameOffsetX) / (markimage.size.width * numberOfStar_float) * numberOfStar_float);
                if stepInterval != 0 {
                    value = max(minimumValue, ceil(value / stepInterval) * stepInterval)
                } else {
                    value = max(minimumValue, value)
                }
                self.value = Float(value)
                self .sendActions(for: UIControl.Event.valueChanged)
            }
        }
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末棵里,一起剝皮案震驚了整個(gè)濱河市润文,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殿怜,老刑警劉巖典蝌,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異头谜,居然都是意外死亡骏掀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門柱告,熙熙樓的掌柜王于貴愁眉苦臉地迎上來截驮,“玉大人,你說我怎么就攤上這事际度】” “怎么了?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵乖菱,是天一觀的道長(zhǎng)坡锡。 經(jīng)常有香客問我,道長(zhǎng)窒所,這世上最難降的妖魔是什么鹉勒? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮吵取,結(jié)果婚禮上禽额,老公的妹妹穿的比我還像新娘。我一直安慰自己皮官,他們只是感情好绵疲,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布哲鸳。 她就那樣靜靜地躺著,像睡著了一般盔憨。 火紅的嫁衣襯著肌膚如雪徙菠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天郁岩,我揣著相機(jī)與錄音婿奔,去河邊找鬼。 笑死问慎,一個(gè)胖子當(dāng)著我的面吹牛萍摊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播如叼,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼冰木,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了笼恰?” 一聲冷哼從身側(cè)響起踊沸,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎社证,沒想到半個(gè)月后逼龟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡追葡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年腺律,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宜肉。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡匀钧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谬返,到底是詐尸還是另有隱情榴捡,我是刑警寧澤,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布朱浴,位于F島的核電站吊圾,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏翰蠢。R本人自食惡果不足惜项乒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望梁沧。 院中可真熱鬧檀何,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至垛孔,卻和暖如春藕甩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背周荐。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來泰國打工狭莱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人概作。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓腋妙,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國和親讯榕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子骤素,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 今天中午睡過午覺后便一直頭疼济竹,晚上時(shí)疼的更厲害了,不愿用腦子想東西集绰,所以今天不寫日更了规辱。還是需要加強(qiáng)身體鍛煉谆棺,保持...
    夢(mèng)可在閱讀 128評(píng)論 0 0
  • 有多久沒做運(yùn)動(dòng)了栽燕? 想不起了,自從跟腱止點(diǎn)炎后看了幾年的醫(yī)生改淑,“運(yùn)動(dòng)”這詞開始在生活中消失碍岔。隨著年齡的增長(zhǎng)開始明顯...
    A君閱讀 177評(píng)論 2 1
  • 公司業(yè)務(wù)使然,很久沒使用scrapy了朵夏,最近復(fù)習(xí)一下框架知識(shí)蔼啦,順帶體驗(yàn)一下強(qiáng)大的scrapy調(diào)度能力 簡(jiǎn)書的it類...
    hellodyp閱讀 1,374評(píng)論 3 3
  • 梅子吉祥如意懷德閱讀 296評(píng)論 0 2