You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为空TableView添加自定义动画:替代GIF的实现方案咨询

嘿,这个问题我太有共鸣了——GIF在UIImageView里确实经常掉链子,尤其是需要高画质和流畅度的时候。给你几个比GIF靠谱得多的实现思路,亲测好用:

方案1:逐帧图片+UIView基础动画(最易上手,画质拉满)

把风滚草的滚动动作拆成多张高清PNG(比如每旋转45度一张,8帧左右就足够流畅),然后用UIImageView的逐帧动画配合位移动画,完全原生实现,没有第三方依赖,画质和流畅度都能和App其他元素保持一致。

示例代码(Swift):

// 先添加线条视图,确保在TableView为空时显示
func setupEmptyStateAnimation() {
    guard tableView.numberOfRows(inSection: 0) == 0 else {
        // 有内容时移除所有动画视图
        tableView.subviews.filter { $0.tag == 999 }.forEach { $0.removeFromSuperview() }
        return
    }
    
    // 1. 添加线条并显示10-15秒
    let lineImageView = UIImageView(image: UIImage(named: "empty_line"))
    lineImageView.tag = 999
    lineImageView.frame = CGRect(x: 20, y: tableView.center.y, width: tableView.bounds.width - 40, height: 2)
    lineImageView.contentMode = .scaleToFill
    tableView.addSubview(lineImageView)
    
    // 2. 延迟12秒启动风滚草动画
    DispatchQueue.main.asyncAfter(deadline: .now() + 12) {
        // 加载风滚草的逐帧图片
        let tumbleweedFrames = (1...8).compactMap { UIImage(named: "tumbleweed_frame_\($0)") }
        guard !tumbleweedFrames.isEmpty else { return }
        
        let tumbleweedView = UIImageView()
        tumbleweedView.tag = 999
        tumbleweedView.animationImages = tumbleweedFrames
        tumbleweedView.animationDuration = 2 // 滚完一圈的时间
        tumbleweedView.animationRepeatCount = 1 // 只滚一次
        // 初始位置在屏幕右侧外
        tumbleweedView.frame = CGRect(x: tableView.bounds.width + 60, y: tableView.center.y - 30, width: 60, height: 60)
        self.tableView.addSubview(tumbleweedView)
        
        // 启动逐帧滚动
        tumbleweedView.startAnimating()
        
        // 位移动画:从右滚到左
        UIView.animate(withDuration: 3, delay: 0, options: .curveLinear) {
            tumbleweedView.frame.origin.x = -60
        } completion: { _ in
            // 动画结束后清理视图
            tumbleweedView.removeFromSuperview()
            // 可以选择隐藏线条,或者重新触发动画循环
            lineImageView.isHidden = true
        }
    }
}
方案2:Core Animation组合动画(更真实的物理效果)

如果想让风滚草的运动更贴近真实(比如滚动时轻微上下颠簸),可以用Core Animation的关键帧动画,把旋转、位移甚至缩放动画组合起来,自由度更高。

示例代码:

func setupAdvancedEmptyAnimation() {
    guard tableView.numberOfRows(inSection: 0) == 0 else {
        tableView.subviews.filter { $0.tag == 999 }.forEach { $0.removeFromSuperview() }
        return
    }
    
    // 添加线条
    let lineImageView = UIImageView(image: UIImage(named: "empty_line"))
    lineImageView.tag = 999
    lineImageView.frame = CGRect(x: 20, y: tableView.center.y, width: tableView.bounds.width - 40, height: 2)
    tableView.addSubview(lineImageView)
    
    DispatchQueue.main.asyncAfter(deadline: .now() + 10) {
        let tumbleweedView = UIImageView(image: UIImage(named: "tumbleweed_single"))
        tumbleweedView.tag = 999
        tumbleweedView.frame = CGRect(x: self.tableView.bounds.width + 60, y: self.tableView.center.y - 30, width: 60, height: 60)
        self.tableView.addSubview(tumbleweedView)
        
        // 1. 位移动画:从右到左,加一点上下颠簸
        let positionAnim = CAKeyframeAnimation(keyPath: "position")
        positionAnim.values = [
            CGPoint(x: self.tableView.bounds.width + 60, y: self.tableView.center.y - 30),
            CGPoint(x: self.tableView.bounds.width/2, y: self.tableView.center.y - 40),
            CGPoint(x: 0, y: self.tableView.center.y - 25),
            CGPoint(x: -60, y: self.tableView.center.y - 30)
        ]
        positionAnim.duration = 3.5
        positionAnim.timingFunction = CAMediaTimingFunction(name: .easeInOut)
        
        // 2. 旋转动画:同步滚动
        let rotationAnim = CABasicAnimation(keyPath: "transform.rotation.z")
        rotationAnim.toValue = CGFloat.pi * 5 // 滚2.5圈
        rotationAnim.duration = 3.5
        rotationAnim.timingFunction = CAMediaTimingFunction(name: .linear)
        
        // 把动画加到图层
        tumbleweedView.layer.add(positionAnim, forKey: "move")
        tumbleweedView.layer.add(rotationAnim, forKey: "rotate")
        
        // 动画结束后更新真实位置并清理
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
            tumbleweedView.frame.origin = CGPoint(x: -60, y: self.tableView.center.y - 30)
            tumbleweedView.removeFromSuperview()
            lineImageView.isHidden = true
        }
    }
}
方案3:Lottie矢量动画(零代码动画制作)

如果不想自己拆帧或者写动画逻辑,可以用Lottie:用After Effects制作你想要的动画(线条显示→风滚草滚动),然后导出成Lottie的JSON文件,集成Lottie库后直接播放就行。优点是画质是矢量的,放大不会糊,动画细节可以做得非常精致,而且控制起来很灵活(比如暂停、循环、调整速度)。

示例代码:

import Lottie

func setupLottieEmptyAnimation() {
    guard tableView.numberOfRows(inSection: 0) == 0 else {
        tableView.subviews.filter { $0 is LottieAnimationView }.forEach { $0.removeFromSuperview() }
        return
    }
    
    let animationView = LottieAnimationView(name: "empty_tumbleweed_animation")
    animationView.frame = tableView.bounds
    animationView.contentMode = .scaleAspectFit
    animationView.loopMode = .playOnce
    tableView.addSubview(animationView)
    
    // 播放动画,结束后移除
    animationView.play { finished in
        if finished {
            animationView.removeFromSuperview()
        }
    }
}

额外注意点

  • 记得在TableView有内容时,及时移除所有动画视图,避免遮挡内容
  • 图片资源要准备@2x、@3x版本,适配不同分辨率的设备
  • 如果需要循环播放动画,可以在动画结束后重新调用setup方法

内容的提问来源于stack exchange,提问作者Kárpáti András

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 10:04:43