iOS LED跑馬燈效果實現


iOS中實現LED跑馬燈效果

 

 

實現原理是使用scrollView, 將需要滾動的label添加兩次到 scrollView的subView下面, 然后通過滾動scrollView來實現跑馬燈效果。

 

具體實現代碼如下:

//
//  KMScrollLabel.swift
//  StopSmokingPrograms
//
//  Created by Fran on 15/11/2.
//  Copyright © 2015年 kimree. All rights reserved.
//

import UIKit

// 文字滾動方向
enum KMScrollDirection: Int{
    case Left = 0
    case Right
    case Up
    case Down
}

class KMScrollLabel: UIScrollView {
    
    // label 是只讀的
    // 在外部只修改label的text即可
    private var label = UILabel()
    var textLabel: UILabel{
        get{
            return label
        }
    }
    
    // label是否在滑動
    private var keepScroll = false
    
    // 動畫時間
    var animationDuringTime:Double = 5
    
    deinit{
        KMLog("KMScrollLabel deinit")
    }
    
    convenience init(){
        self.init(frame: CGRectZero)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        KMLog("init frame")
        
        self.scrollEnabled = false
        self.showsHorizontalScrollIndicator = false
        self.showsVerticalScrollIndicator = false
        self.addSubview(label)
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        KMLog("init aDecoder")
        
        self.scrollEnabled = false
        self.showsHorizontalScrollIndicator = false
        self.showsVerticalScrollIndicator = false
        self.addSubview(label)
    }
    
    // MARK: - 開始滑動
    func startScroll(direction: KMScrollDirection = .Left){
        fitFrame(direction)
        
        // 當label不處於滑動狀態時, 才可以開始滑動, 避免動畫效果疊加
        if !keepScroll{
            keepScroll = true
            self.contentOffset = CGPointZero
            beginScroll(direction)
        }
    }
    
    // removeAllAnimations 之后會立刻執行 animation block 的 completion 部分, 
    // 目前無法確定 completion 部分的參數總會是 false, 為了避免巧合, 需要配合 keepScroll 參數一起使用
    func stopScroll(){
        keepScroll = false
        self.layer.removeAllAnimations()
    }
    
    // MARK: - 具體怎樣滑動
    func beginScroll(direction: KMScrollDirection = .Left){
        let w = label.frame.size.width
        let h = label.frame.size.height
        
        switch direction{
        case .Left:
            self.contentOffset.x = 0
            UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                self?.contentOffset.x = w
                }, completion: { [weak self](finished: Bool) -> Void in
                    if finished && (self != nil && self!.keepScroll){
                        self!.contentOffset.x = 0
                        self!.beginScroll(direction)
                    }
                })
            
        case .Right:
            self.contentOffset.x = w
            UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                self?.contentOffset.x = 0
                }, completion: { [weak self](finished: Bool) -> Void in
                    if finished && (self != nil && self!.keepScroll){
                        self!.contentOffset.x = w
                        self!.beginScroll(direction)
                    }
                })
            
        case .Up:
            self.contentOffset.y = 0
            UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                self?.contentOffset.y = h
                }, completion: { [weak self](finished: Bool) -> Void in
                    if finished && (self != nil && self!.keepScroll){
                        self!.contentOffset.y = 0
                        self!.beginScroll(direction)
                    }
                })
            
        case .Down:
            self.contentOffset.y = h
            UIView.animateWithDuration(animationDuringTime, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { [weak self]() -> Void in
                self?.contentOffset.y = 0
                }, completion: { [weak self](finished: Bool) -> Void in
                    if finished && (self != nil && self!.keepScroll){
                        self!.contentOffset.y = h
                        self!.beginScroll(direction)
                    }
                })
        }
        
    }
    
    // MARK: - 適配frame
    private func fitFrame(direction: KMScrollDirection){
        // 按照水平方向移動的要求修改frame
        func fitFrameAtHorizontalDirection(){
            
            let width = self.frame.size.width
            var height = self.frame.size.height
            
            label.numberOfLines = 1
            label.lineBreakMode = .ByWordWrapping
            var size = label.sizeThatFits(CGSizeMake(CGFloat.max, height))
            
            // scroll 的 height 不能小於 label 的 height
            if height < size.height{
                self.frame.size.height = size.height
                height = size.height
            }
            
            // label 的 width 要加上 scroll width 的一半作為留白, 並且 label 的 width 不能小於 scroll 的 width
            size.width += width / 2.0
            if size.width < width{
                size.width = width
            }
            
            label.frame = CGRectMake(0, (height - size.height) / 2.0, size.width, size.height)
            
            let tempLabel = NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(label)) as! UILabel
            tempLabel.frame.origin.x += size.width
            self.addSubview(tempLabel)
            
            self.contentSize = CGSizeMake(2 * size.width, height)
        }
        
        // 按照豎直方向移動的要求修改frame
        func fitFrameAtVerticalDirection(){
            
            var width = self.frame.size.width
            let height = self.frame.size.height
            
            label.numberOfLines = 0
            label.lineBreakMode = .ByWordWrapping
            let originalText = label.text
            
            // 獲取單個文字的寬度
            label.text = "田"
            let singleSize = label.sizeThatFits(CGSizeZero)
            
            label.text = originalText
            var size = label.sizeThatFits(CGSizeMake(singleSize.width, CGFloat.max))
            
            if width < size.width{
                self.frame.size.width = size.width
                width = size.width
            }
            
            size.height += height / 2.0
            if size.height < height{
                size.height = height
            }
            
            label.frame = CGRectMake((width - size.width) / 2.0, 0, size.width, size.height)
            
            let tempLabel = NSKeyedUnarchiver.unarchiveObjectWithData(NSKeyedArchiver.archivedDataWithRootObject(label)) as! UILabel
            tempLabel.frame.origin.y += size.height
            self.addSubview(tempLabel)
            
            self.contentSize = CGSizeMake(width, 2 * size.height)
        }
        
        // 首先遍歷 scroll 的所有 subView, 將占位的 label 移除
        for view in self.subviews{
            if view != label{
                view.removeFromSuperview()
            }
        }
        
        switch direction{
        case .Left, .Right:
            fitFrameAtHorizontalDirection()
        case .Up, .Down:
            fitFrameAtVerticalDirection()
        }
        
    }
    
    /*
    // Only override drawRect: if you perform custom drawing.
    // An empty implementation adversely affects performance during animation.
    override func drawRect(rect: CGRect) {
    // Drawing code
    }
    */
    
}

  注意: 如果在有navigationController的界面使用時,由於跑馬燈中使用到了UIScrollView,此時跑馬燈的Label顯示不正常,解決方法:

        self.automaticallyAdjustsScrollViewInsets = false
        
        // 不想采用上訴方式時, 可以調整scrollView的contentInset來達到目的
        // 需要在 kmScrollView.startScroll() 之后調用
//        kmScrollView.contentInset.top = -64

  

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM