用組件式的思想實(shí)現(xiàn)跑馬燈的文字效果

?今天在項(xiàng)目中要做一個(gè)跑馬燈文字的效果。雖然網(wǎng)上有第三方的,但是本寶寶覺(jué)得這個(gè)效果實(shí)現(xiàn)起來(lái)并不是很難,所以本寶寶決定 自己動(dòng)手,風(fēng)衣足食而且還要做一個(gè)可以在IB上也能使用的控件


既然要在IB上使用,那么首先想到的是class

1.png

先將UILabel控件拖入到IB中,讓后把class改為ScrollLabel。不用寫(xiě)其他的代碼,凡是只要是ScrollLabel的都應(yīng)該有這個(gè)效果。( 這讓我想到了HTML的各種組件庫(kù),在HTML中的標(biāo)簽都是用的class,比如<button class="btn btn-default" >按鈕</button>,而不是在標(biāo)簽里寫(xiě)上style<button style="background-color : red;"></button>,顯然前一種要比后面一種要更加解藕,更加適合復(fù)用。)


整體的設(shè)計(jì)思路已搭好,下面就開(kāi)始進(jìn)入正題

第一步

先建立一個(gè)ScrollLabel的類(lèi)
swift
import UIKit

@IBDesignable
class ScrollLabel: UILabel {

private var textLayer = CATextLayer()
var labelWidth : CGFloat {
    return self.frame.size.width
}

var labelHeight : CGFloat {
    return self.frame.size.height
}

override init(frame: CGRect) {
    super.init(frame: frame)
    initUI()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func awakeFromNib() {
    super.awakeFromNib()
    initUI()
}

// ?添加一個(gè)textLayer顯示在label上
func initUI(){
textLayer.string = self.text
textLayer.anchorPoint = CGPoint(x : 0,y: 0)
textLayer.bounds = CGRect(x: 0, y: 0, width: labelWidth , height: labelHeight)
textLayer.foregroundColor = self.textColor.CGColor
textLayer.backgroundColor = self.backgroundColor?.CGColor
textLayer.fontSize = self.font.pointSize
textLayer.font = self.font
self.layer.addSublayer(textLayer)
}

}

將CATextLayer添加在label上,( CATextLayer是一個(gè)可以顯示文字的圖層,CALayer要比UIView性能要好 )
***
運(yùn)行后的結(jié)果是這樣的

![2.png](http://upload-images.jianshu.io/upload_images/1215250-332f78af65b833b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我們發(fā)現(xiàn)運(yùn)行后有一部分的被蓋住了,解決方案為一下三種:
```swift```
        self.textLayer.zPosition = 1      //第一種
        self.layer.masksToBounds = true   //第二種
        self.clipsToBounds = true         //第三種

這里我們采用的是第二種或第三種方式,因?yàn)槲覀円屗谝欢ǖ膮^(qū)域內(nèi)滾動(dòng)

第二步

添加一個(gè)動(dòng)畫(huà),讓它開(kāi)始滾動(dòng):
swift

import UIKit

class ScrollLabel: UILabel {

private var textLayer = CATextLayer()
var labelWidth : CGFloat {
    return self.frame.size.width
}

var labelHeight : CGFloat {
    return self.frame.size.height
}

override init(frame: CGRect) {
    super.init(frame: frame)
    initUI()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func awakeFromNib() {
    super.awakeFromNib()
    
    initUI()
    startScrollAnimation()
}

// ? 添加一個(gè)textLayer顯示在label上
func initUI(){

    if text == nil {
        text = ""
    }
    
    layer.masksToBounds = true
    
    textLayer.string = text
    textLayer.anchorPoint = CGPoint(x : 0,y: 0)
    textLayer.position = CGPoint(x: 0, y: 0)
    
    //計(jì)算text所需要的寬度
    let textWidth = text?.boundingRectWithSize(CGSize(width: 375,height: frame.size.height), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName : font], context: nil).size.width

    textLayer.bounds = CGRect(x: 0, y: 0, width: textWidth! , height: labelHeight)
    
    textLayer.foregroundColor = textColor.CGColor
    textLayer.backgroundColor = backgroundColor?.CGColor
    textLayer.fontSize = font.pointSize
    textLayer.font = font
    textColor = UIColor.clearColor()

    self.layer.addSublayer(self.textLayer)
    
}

// ? 添加一個(gè)動(dòng)畫(huà),讓它開(kāi)始滾動(dòng)
func startScrollAnimation(){

    let animation = CABasicAnimation(keyPath: "position.x")
    animation.duration = 6
    animation.repeatCount = MAXFLOAT
    animation.fromValue = labelWidth
    animation.toValue = -textLayer.bounds.size.width
    
    textLayer.addAnimation(animation, forKey: "animation")
}

}

運(yùn)行后的結(jié)果為:

![3.gif](http://upload-images.jianshu.io/upload_images/1215250-e64c4f5d20974974.gif?imageMogr2/auto-orient/strip)

雖然文字可以滾動(dòng),但是當(dāng)label從界面上消失的時(shí)候,再次出現(xiàn)的時(shí)候就不能動(dòng)畫(huà),我猜測(cè)的原因是可能是當(dāng)控件從界面消失的時(shí)候就會(huì)刪除動(dòng)畫(huà)。
解決這個(gè)有兩個(gè)思路:
1.  **在界面消失的時(shí)候不要?jiǎng)h掉動(dòng)畫(huà),動(dòng)畫(huà)繼續(xù)執(zhí)行**。(但是,這種方法我作不出來(lái)。我把a(bǔ)nimation設(shè)置成全局變量也不行,我估計(jì)可能animation可能有個(gè)api是可以解決這個(gè)問(wèn)題的,但是我沒(méi)有找到,如果有知道的童鞋可以告訴我)
1.  **就是在界面出現(xiàn)的時(shí)候就添加動(dòng)畫(huà)**,就是相當(dāng)于UIViewController的```viewDidAppear```。那么在UIView的子類(lèi)的控件中,有沒(méi)有類(lèi)似的方法了?答案是有的。
```didMoveToWindow()```:控件在出現(xiàn)的時(shí)候就調(diào)用這個(gè)方法,控件在消失的時(shí)候也會(huì)調(diào)用這個(gè)方法。
接下來(lái)貼上解決后的代碼:

```swift```

import UIKit


class ScrollLabel: UILabel {
    
    private var textLayer = CATextLayer()
    
    var labelWidth : CGFloat {
        return self.frame.size.width
    }
    
    var labelHeight : CGFloat {
        return self.frame.size.height
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        initUI()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        
        initUI()
        
    }
    
//  ? 將動(dòng)畫(huà)添加在這個(gè)里面 
    override func didMoveToWindow() {
        super.didMoveToWindow()
        startScrollAnimation()
    }
    
//  ? 添加一個(gè)textLayer顯示在label上
    func initUI(){
        
        if text == nil {
            text = ""
        }
        
        layer.masksToBounds = true
        
        textLayer.string = text
        textLayer.anchorPoint = CGPoint(x : 0,y: 0)
        textLayer.position = CGPoint(x: 0, y: 0)
        
        //計(jì)算text所需要的寬度
        let textWidth = text?.boundingRectWithSize(CGSize(width: 375,height: frame.size.height), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName : font], context: nil).size.width

        textLayer.bounds = CGRect(x: 0, y: 0, width: textWidth! , height: labelHeight)
        
        textLayer.foregroundColor = textColor.CGColor
        textLayer.backgroundColor = backgroundColor?.CGColor
        textLayer.fontSize = font.pointSize
        textLayer.font = font
        textColor = UIColor.clearColor()
    
        self.layer.addSublayer(self.textLayer)
        
    }
    
//  ? 添加一個(gè)動(dòng)畫(huà),讓它開(kāi)始滾動(dòng)
    func startScrollAnimation(){
        
        let anim = textLayer.animationForKey("animation")
        
        if anim != nil  {
            print("表示animation存在,return這個(gè)函數(shù)")
            return
        }else{
            print("表示animation不存在,繼續(xù)執(zhí)行下面的函數(shù)")
        }
        
        let animation = CABasicAnimation(keyPath: "position.x")
        animation.duration = 6
        animation.repeatCount = MAXFLOAT
        animation.fromValue = labelWidth
        animation.toValue = -textLayer.bounds.size.width
        
        textLayer.addAnimation(animation, forKey: "animation")
    }

}

跑馬燈的效果貌似已經(jīng)完成,But!!! 萬(wàn)萬(wàn)沒(méi)想到,當(dāng)我添加約束的時(shí)候出現(xiàn)了BUG:

4.gif

"敵人..." 那些字并不是從控件的尾部出現(xiàn)的,而是從中間出現(xiàn)的。所以我們這里的解決方就是:添加一個(gè)layoutIfNeeded()

5.png

添加后就解決了這個(gè)BUG。
關(guān)于這個(gè)BUG的原因,我們來(lái)打印下控件的frame:

6.png

打印出來(lái)的結(jié)果為:

7.png

這個(gè)BUG的原因,你們自己體會(huì)就好了


最后貼上我的完整的源代碼(直接復(fù)制粘貼就可以了):

swift
//
// ScrollLabel.swift
// ScrollLabel
//
// Created by 李修冶 on 16/8/31.
// Copyright ? 2016年 李修冶. All rights reserved.
//

import UIKit

class ScrollLabel: UILabel {

private var textLayer = CATextLayer()

var labelWidth : CGFloat {
    return self.frame.size.width
}

var labelHeight : CGFloat {
    return self.frame.size.height
}

override init(frame: CGRect) {
    super.init(frame: frame)
    initUI()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
}

override func awakeFromNib() {
    super.awakeFromNib()
    print("更新約束前",frame)

// ? 更新約束
layoutIfNeeded()
print("更新約束后",frame)
initUI()

}

// ? 將動(dòng)畫(huà)添加在這個(gè)里面
override func didMoveToWindow() {
super.didMoveToWindow()
startScrollAnimation()
}

// ? 添加一個(gè)textLayer顯示在label上
func initUI(){

    if text == nil {
        text = ""
    }
    
    layer.masksToBounds = true
    
    textLayer.string = text
    textLayer.anchorPoint = CGPoint(x : 0,y: 0)
    textLayer.position = CGPoint(x: 0, y: 0)
    
    //計(jì)算text所需要的寬度
    let textWidth = text?.boundingRectWithSize(CGSize(width: 375,height: frame.size.height), options: .UsesLineFragmentOrigin, attributes: [NSFontAttributeName : font], context: nil).size.width

    textLayer.bounds = CGRect(x: 0, y: 0, width: textWidth! , height: labelHeight)
    
    textLayer.foregroundColor = textColor.CGColor
    textLayer.backgroundColor = backgroundColor?.CGColor
    textLayer.fontSize = font.pointSize
    textLayer.font = font
    textColor = UIColor.clearColor()

    self.layer.addSublayer(self.textLayer)
    
}

// ? 添加一個(gè)動(dòng)畫(huà),讓它開(kāi)始滾動(dòng)
func startScrollAnimation(){

    let anim = textLayer.animationForKey("animation")
    
    if anim != nil  {
        print("表示animation存在,return這個(gè)函數(shù)")
        return
    }else{
        print("表示animation不存在,繼續(xù)執(zhí)行下面的函數(shù)")
    }
    
    let animation = CABasicAnimation(keyPath: "position.x")
    animation.duration = 6
    animation.repeatCount = MAXFLOAT
    animation.fromValue = labelWidth
    animation.toValue = -textLayer.bounds.size.width
    
    textLayer.addAnimation(animation, forKey: "animation")
}

}

最后希望你們?cè)诳赐暾燮恼潞?如果覺(jué)得我哪里寫(xiě)得不好,可以評(píng)論提出來(lái),文章文字功底不行,寫(xiě)得不清楚也可以提出來(lái)。
如果你覺(jué)得我寫(xiě)得還不錯(cuò)的話就請(qǐng)**雙擊666**
![8.jpg](http://upload-images.jianshu.io/upload_images/1215250-467d4b98a09e652a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫(kù)、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,657評(píng)論 4 61
  • 291976-陳國(guó)艷《2017-06-04》 【連續(xù)第113天總結(jié)】 A、目標(biāo)完成情況 1、抄寫(xiě)概念一遍完成100...
    國(guó)艷更文的365天閱讀 367評(píng)論 0 1
  • 文|陌上花 最近被兒子折磨得快要瘋掉,每晚臨睡前非得纏著我講故事。 你會(huì)不會(huì)翻我個(gè)白眼:買(mǎi)幾本童話書(shū)照著念不...
    陌上花km閱讀 508評(píng)論 11 11
  • 指尖蝶舞的花園閱讀 191評(píng)論 0 2

友情鏈接更多精彩內(nèi)容