美文网首页iOS DeveloperObjective C开发iOS程序猿
用组件式的思想实现跑马灯的文字效果

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

作者: Swifter丶 | 来源:发表于2016-09-01 02:08 被阅读450次

     今天在项目中要做一个跑马灯文字的效果。虽然网上有第三方的,但是本宝宝觉得这个效果实现起来并不是很难,所以本宝宝决定 自己动手,风衣足食而且还要做一个可以在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)

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/xsxcettx.html