用组件式的思想实现跑马灯的文字效果

 今天在项目中要做一个跑马灯文字的效果。虽然网上有第三方的,但是本宝宝觉得这个效果实现起来并不是很难,所以本宝宝决定 自己动手,风衣足食而且还要做一个可以在IB上也能使用的控件


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

1.png

先将UILabel控件拖入到IB中,让后把class改为ScrollLabel。不用写其他的代码,凡是只要是ScrollLabel的都应该有这个效果。( 这让我想到了HTML的各种组件库,在HTML中的标签都是用的class,比如<button class="btn btn-default" >按钮</button>,而不是在标签里写上style<button style="background-color : red;"></button>,显然前一种要比后面一种要更加解藕,更加适合复用。)


整体的设计思路已搭好,下面就开始进入正题

第一步

先建立一个ScrollLabel的类
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()
}

// ️添加一个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是一个可以显示文字的图层,CALayer要比UIView性能要好 )
***
运行后的结果是这样的

![2.png](http://upload-images.jianshu.io/upload_images/1215250-332f78af65b833b3.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
我们发现运行后有一部分的被盖住了,解决方案为一下三种:
```swift```
        self.textLayer.zPosition = 1      //第一种
        self.layer.masksToBounds = true   //第二种
        self.clipsToBounds = true         //第三种

这里我们采用的是第二种或第三种方式,因为我们要让它在一定的区域内滚动

第二步

添加一个动画,让它开始滚动:
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()
}

// ️ 添加一个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)
    
    //计算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)
    
}

// ️ 添加一个动画,让它开始滚动
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")
}

}

运行后的结果为:

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

虽然文字可以滚动,但是当label从界面上消失的时候,再次出现的时候就不能动画,我猜测的原因是可能是当控件从界面消失的时候就会删除动画。
解决这个有两个思路:
1.  **在界面消失的时候不要删掉动画,动画继续执行**。(但是,这种方法我作不出来。我把animation设置成全局变量也不行,我估计可能animation可能有个api是可以解决这个问题的,但是我没有找到,如果有知道的童鞋可以告诉我)
1.  **就是在界面出现的时候就添加动画**,就是相当于UIViewController的```viewDidAppear```。那么在UIView的子类的控件中,有没有类似的方法了?答案是有的。
```didMoveToWindow()```:控件在出现的时候就调用这个方法,控件在消失的时候也会调用这个方法。
接下来贴上解决后的代码:

```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()
        
    }
    
//  ️ 将动画添加在这个里面 
    override func didMoveToWindow() {
        super.didMoveToWindow()
        startScrollAnimation()
    }
    
//  ️ 添加一个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)
        
        //计算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)
        
    }
    
//  ️ 添加一个动画,让它开始滚动
    func startScrollAnimation(){
        
        let anim = textLayer.animationForKey("animation")
        
        if anim != nil  {
            print("表示animation存在,return这个函数")
            return
        }else{
            print("表示animation不存在,继续执行下面的函数")
        }
        
        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")
    }

}

跑马灯的效果貌似已经完成,But!!! 万万没想到,当我添加约束的时候出现了BUG:

4.gif

"敌人..." 那些字并不是从控件的尾部出现的,而是从中间出现的。所以我们这里的解决方就是:添加一个layoutIfNeeded()

5.png

添加后就解决了这个BUG。
关于这个BUG的原因,我们来打印下控件的frame:

6.png

打印出来的结果为:

7.png

这个BUG的原因,你们自己体会就好了


最后贴上我的完整的源代码(直接复制粘贴就可以了):

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()

}

// ️ 将动画添加在这个里面
override func didMoveToWindow() {
super.didMoveToWindow()
startScrollAnimation()
}

// ️ 添加一个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)
    
    //计算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)
    
}

// ️ 添加一个动画,让它开始滚动
func startScrollAnimation(){

    let anim = textLayer.animationForKey("animation")
    
    if anim != nil  {
        print("表示animation存在,return这个函数")
        return
    }else{
        print("表示animation不存在,继续执行下面的函数")
    }
    
    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")
}

}

最后希望你们在看完折篇文章后,如果觉得我哪里写得不好,可以评论提出来,文章文字功底不行,写得不清楚也可以提出来。
如果你觉得我写得还不错的话就请**双击666**
![8.jpg](http://upload-images.jianshu.io/upload_images/1215250-467d4b98a09e652a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容