iOS DeveloperObjective C开发iOS程序猿

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

2016-09-01  本文已影响450人  Swifter丶

 今天在项目中要做一个跑马灯文字的效果。虽然网上有第三方的,但是本宝宝觉得这个效果实现起来并不是很难,所以本宝宝决定 自己动手,风衣足食而且还要做一个可以在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](https://img.haomeiwen.com/i1215250/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](https://img.haomeiwen.com/i1215250/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](https://img.haomeiwen.com/i1215250/467d4b98a09e652a.jpg?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
上一篇下一篇

猜你喜欢

热点阅读