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

先將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é)果是這樣的

我們發(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é)果為:

雖然文字可以滾動(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:

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

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

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

這個(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**
