iOS文字滚动:使用CATextLayer实现的跑马灯(附源码)

引言

在 iOS 开发中,跑马灯效果(Marquee Effect)是一种常见的文本滚动效果,广泛应用于广告展示、动态消息栏、通知推送等场景。通过跑马灯效果,我们能够以流畅的方式展示超出屏幕范围的文本,提升用户体验。

通常,在 iOS 中实现跑马灯效果,我们可能会想到UILabel。然而,虽然UILabel提供了丰富的文本样式支持,它在动画和性能方面却有一定局限性。特别是在需要自定义动画效果和处理高性能的场景中,UILabel并不是最理想的选择。

此时,CATextLayer就成为了一个更灵活的替代方案。CATextLayer是一个低级的图层类,继承自CALayer,它专注于文本渲染,并且可以与 Core Animation 配合,实现高效且平滑的动画效果。相比于UILabel,CATextLayer更加轻量,且能更精确地控制动画和渲染性能,因此非常适合用于跑马灯这类需要高效动画渲染的场景。

本文将通过一个实际的例子,介绍如何使用CATextLayer实现一个简洁、流畅的跑马灯效果,帮助你在 iOS 项目中灵活运用这一技术。

CATextLayer基础

CATextLayer是Core Animation框架中的一种特殊图层(CALayer的子类),专门用于渲染文本。它与UILabel的最大区别在于,它并不直接处理用户交互和文本样式的布局,而是通过图层的方式,专注于高效的文本渲染和动画效果。

在Core Animation中,CATextLayer被设计为一个性能高效的图层,能够承载大量的文本内容并进行平滑的动画。与UILabel类不同,CATextLayer主要用于显示文本,而不涉及复杂的布局计算和视图管理,因此它非常适合用于需要高效渲染的场景,比如动画、动态图文等。

CATextLayer的主要特点:

  1. 专注于文本渲染:CATextLayer不像UILabel那样支持复杂的用户交互和动态布局,它主要负责在屏幕上渲染文本,减少了多余的开销。
  2. 高性能的文本绘制:由于其直接依赖于Core Animation,CATextLayer在渲染性能上笔UILabel更加优秀,特别是在处理大量文本和需要高帧率动画时,能够提供更平滑的效果。
  3. 支持基本的文本属性:CATextLayer支持字体、大小、颜色、对齐方式等基本的文本属性,但它并不完全支持 NSAtrributedString中的所有样式。这使得它非常适合需要简洁文本样式和高效动画的场景。
  4. 与Core Animation配合使用:CATextLayer天生与Core Animation配合,能够与其他图层(例如CALayer)进行协调,创建复杂的动画效果,如滚动、变换、透明度变化等。利用CATextLayer,可以实现诸如跑马灯、动态通知等动画效果。
  5. 不直接管理布局:与UILabel不同CATextLayer不负责复杂的自动布局或响应交互,它仅在指定的区域内渲染文本,适合用于自定义动画和轻量级显示。

通过CATextLayer,开发者可以更加灵活地控制文本的渲染和动画,尤其在需要高效且流畅显示大段文本的场景中,CATextLayer 提供了比 UILabel 更加高效的方案。

实现步骤

为了让我们自定义的跑马灯文字组件使用起来和普通的UILabel一样,可以直接继承自UILabel来构建自定义的文字自动滚动组件。在自定义的组件内部创建一个CATextLayer图层用来承载文字和执行动画。

准备工作

为了实现滚动效果,首先我们需要当前文本展示完成后所需的组件宽度和滚动速度,然后根据宽度再来创建特殊的文字图层CATextLayer以及执行滚动动画。

Swift 复制代码
    /// 文字图层
    private var marqueeLayer: CATextLayer?
    /// 速度
    private var speed: CGFloat = 50.0
Swift 复制代码
    private func setupTextLayer() {
        self.layer.masksToBounds = true
        // 清理图层
        clearLayer()
        // 计算文字宽度,优先考虑富文本
        var attributedText = self.attributedText
        if attributedText == nil {
            attributedText = NSAttributedString(string: text ?? "", attributes: [.font: font, .foregroundColor: textColor])
        }
        let textWidth = attributedText?.boundingRect(with: CGSize(width: CGFloat(MAXFLOAT), height: frame.height), options: .usesLineFragmentOrigin, context: nil).width ?? 0
        
        guard let attributedText = attributedText else {
            return
        }
        // 添加文字图层
        addMarqueeTextLayer(attributedString: attributedText, width: textWidth)
        if textWidth <= self.bounds.width {
            return
        }
        // 添加动画
        addMarqueeAnimation(width: textWidth, textLayer: marqueeLayer!)
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        if self.bounds.size.width > 0 {
            setupTextLayer()
        }
    }
  1. 声明了一个CATextLayer特殊图层。
  2. 定义了滚动的速度。
  3. setupTextLayer()方法中计算文本的宽度,来决定整个文字图层是否需要滚动。
  4. 为了适应约束布局,重写layoutSubviews()方法,并在组件已经有了宽度的时候重新执行setupTextLayer()方法。

创建CATextLayer

当获取到文本内容,计算出文本宽度之后就可以根据内容和宽度来创建CATextLayer添加到UILabel的图层之上并设置布局。

Swift 复制代码
    /// 添加文字图层
    /// - Parameters:
    ///  - attributedString: 富文本
    ///  - width: 文字宽度
    
    private func addMarqueeTextLayer(attributedString:NSAttributedString,width:CGFloat) {
        let textLayer = CATextLayer()
        textLayer.string = attributedString
        textLayer.contentsScale = UIScreen.main.scale
        textLayer.alignmentMode = .left
        textLayer.frame = CGRect(x: 0, y: 0, width: width, height: frame.height)
        layer.addSublayer(textLayer)
        self.marqueeLayer = textLayer
        print("string:\(attributedString)")
        
        textLayer.backgroundColor = UIColor.red.cgColor
        
    }
  1. 创建图层并设置图层的文字内容。
  2. 设置contentsScale这一步十分关键,否则在Retina会出现模糊。
  3. 设置frame,x和y分别从0开始,宽度为文字总宽度,高度为组件高度。
  4. 设置UILabel的原本颜色为透明色,目的是隐藏UILabel原来的显示内容。

添加动画

CATextLayer创建完成之后,我们只需要让它的最左端从组件的最右端开始执行动画,直到CATextLayer图层的最右端与组件的最左端对齐为止,为此我们需要计算起始位置和结束位置。

Swift 复制代码
    /// 添加图层动画
    /// - Parameters:
    /// - width: 文字宽度
    /// - textLayer: 文字图层
    private func addMarqueeAnimation(width: CGFloat, textLayer: CATextLayer) {
        //添加动画
        let startOffset = bounds.width + width / 2.0
        let endOffset = -width/2.0
        let duration = Double(width) / Double(speed)
        let animation = CAKeyframeAnimation(keyPath: "position.x")
        animation.values = [startOffset, endOffset]
        animation.keyTimes = [0, 1]
        animation.duration = duration
        animation.repeatCount = .infinity
        animation.isRemovedOnCompletion = false
        textLayer.add(animation, forKey: "marqueeScroll")
    }
  1. 我们针对图层的position.x执行关键帧动画,那么它的起点位置应该是组件宽度+文字宽度/2。
  2. 而结束为止应该是负的文字宽度。
  3. 根据速度计算动画时长。
  4. 设置重复次数无限大,让动画一直循环。

清理图层

最后还有一个重要的方法,清理特殊图层。

Swift 复制代码
    /// 清理图层
    private func clearLayer() {
        self.marqueeLayer?.removeAllAnimations()
        self.marqueeLayer?.removeFromSuperlayer()
        self.marqueeLayer = nil
    }

使用示例

在使用时,我们只需要像普通UILabel一样使用,支持绝对布局和相对布局。

Swift 复制代码
    /// 添加跑马灯文字
    private func addMarqueeLabel() {
        let marqueeLabel = PHMarqueeLabel(frame: CGRect(x: 100.0, y: 100.0, width: 200.0, height: 40.0))
        marqueeLabel.text = "今天是一个好天气,适合出去玩耍"
        marqueeLabel.textColor = .white
        marqueeLabel.font = UIFont.systemFont(ofSize: 20)
        marqueeLabel.backgroundColor = .orange
        view.addSubview(marqueeLabel)

    }
Swift 复制代码
        /// 添加跑马灯文字
    private func addMarqueeLabel() {
        let marqueeLabel = PHMarqueeLabel(frame: .zero)
        let text = "今天是一个好天气,适合出去玩耍"
        let attributedText = NSMutableAttributedString(string: text)
        attributedText.addAttributes([.foregroundColor: UIColor.blue], range: NSRange(location: 0, length: 2))
        attributedText.addAttributes([.foregroundColor: UIColor.green], range: NSRange(location: 2, length: 2))
        marqueeLabel.attributedText = attributedText
        marqueeLabel.font = UIFont.systemFont(ofSize: 20)
        marqueeLabel.backgroundColor = .orange
        view.addSubview(marqueeLabel)
        marqueeLabel.snp.makeConstraints { make in
            make.top.equalTo(100.0)
            make.leading.trailing.equalToSuperview().inset(100.0)
        }
    }

效果如下:

结语

通过本文,我们探讨了如何使用 CATextLayer 实现一个高效流畅的跑马灯效果。相比于 UILabel,CATextLayer在渲染性能上更具优势,特别是在需要动态更新和动画效果时,它能够提供更加平滑的用户体验。尽管 CATextLayer 支持的文本样式有限,但对于一些简单的文本显示需求,尤其是高效动画渲染,它无疑是一个理想的选择。

在实际开发中,使用 CATextLayer 实现跑马灯效果,能够帮助我们节省性能开销,减少无谓的视图层级,同时通过 Core Animation 提供流畅的视觉体验。无论是在广告轮播、消息通知还是动态标签的场景中,CATextLayer 都能够发挥出色的表现。

当然,CATextLayer 也并非万能,对于需要高度自定义富文本样式的场景,我们仍然可以结合 UILabel 或其他控件来实现更复杂的效果。但对于大多数简洁、流畅的滚动效果,CATextLayer 是一个值得推荐的解决方案。

希望通过本文的介绍,能够帮助你在项目中更好地使用 CATextLayer来实现跑马灯效果,提升动画表现与用户体验。

https://download.csdn.net/download/weixin_39339407/90341158https://download.csdn.net/download/weixin_39339407/90341158

相关推荐
若水无华11 小时前
fiddler 配置ios手机代理调试
ios·智能手机·fiddler
Aress"13 小时前
【ios越狱包安装失败?uniapp导出ipa文件如何安装到苹果手机】苹果IOS直接安装IPA文件
ios·uni-app·ipa安装
Jouzzy1 天前
【iOS安全】Dopamine越狱 iPhone X iOS 16.6 (20G75) | 解决Jailbreak failed with error
安全·ios·iphone
瓜子三百克1 天前
采用sherpa-onnx 实现 ios语音唤起的调研
macos·ios·cocoa
左钦杨1 天前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
努力成为包租婆1 天前
SDK does not contain ‘libarclite‘ at the path
ios
安和昂2 天前
【iOS】Tagged Pointer
macos·ios·cocoa
I烟雨云渊T2 天前
iOS 阅后即焚功能的实现
macos·ios·cocoa
struggle20252 天前
适用于 iOS 的 开源Ultralytics YOLO:应用程序和 Swift 软件包,用于在您自己的 iOS 应用程序中运行 YOLO
yolo·ios·开源·app·swift
Unlimitedz2 天前
iOS视频编码详细步骤(视频编码器,基于 VideoToolbox,支持硬件编码 H264/H265)
ios·音视频