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

使用FLAnimatedImage实现鱼类往返动画失效问题排查

解决鱼类往返游动动画的问题

看起来你在给鱼类添加往返翻转动画时遇到了麻烦,我先梳理下你代码里的几个核心问题,然后给你一个修复后的版本:

你的代码里的主要问题

  • 嵌套函数与作用域混乱:在for numFish in 1...numberOfFish循环内部定义goLeft、goRight这些函数,每次循环都会重新定义它们,而且它们引用的fish3是循环内的局部变量,多次循环后会导致引用错乱,动画行为不可控。
  • 重复加载GIF资源:每次循环都重新读取本地GIF文件创建FLAnimatedImage实例,这不仅浪费性能,还会导致不必要的内存占用。
  • 动画选项冲突:你同时使用了.repeat和在completion里触发下一个动画,.repeat的动画不会触发completion回调(除非动画被手动停止),这会导致你的往返逻辑根本无法正常触发。
  • FLAnimatedImage切换方式不当:直接替换animatedImage属性虽然可行,但没有考虑动画的连续性,而且你只操作了fish3,循环里创建的fish2完全没被使用,还会造成内存泄漏。

修复后的代码示例

首先,提前在类的属性里加载GIF资源,避免重复读取:

class YourViewController: UIViewController {
    // 提前加载GIF资源,复用实例
    private let fishGif1: FLAnimatedImage!
    private let fishGif2: FLAnimatedImage!
    
    // 存储所有鱼的实例,避免引用丢失
    private var fishViews = [FLAnimatedImageView]()
    
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        // 初始化时加载GIF
        let path1 = Bundle.main.path(forResource: "fishgif", ofType: "gif")!
        let data1 = try! Data(contentsOf: URL(fileURLWithPath: path1))
        fishGif1 = FLAnimatedImage(animatedGIFData: data1)
        
        let path2 = Bundle.main.path(forResource: "fishFriendtoo", ofType: "gif")!
        let data2 = try! Data(contentsOf: URL(fileURLWithPath: path2))
        fishGif2 = FLAnimatedImage(animatedGIFData: data2)
        
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }
    
    required init?(coder: NSCoder) {
        // 初始化时加载GIF
        let path1 = Bundle.main.path(forResource: "fishgif", ofType: "gif")!
        let data1 = try! Data(contentsOf: URL(fileURLWithPath: path1))
        fishGif1 = FLAnimatedImage(animatedGIFData: data1)
        
        let path2 = Bundle.main.path(forResource: "fishFriendtoo", ofType: "gif")!
        let data2 = try! Data(contentsOf: URL(fileURLWithPath: path2))
        fishGif2 = FLAnimatedImage(animatedGIFData: data2)
        
        super.init(coder: coder)
    }
    
    @IBAction func animateFish(_ sender: Any) {
        guard let numFishSlider = numFish else { return }
        let numberOfFish = Int(roundf(numFishSlider.value))
        
        for _ in 1...numberOfFish {
            // 随机生成鱼的尺寸和初始位置
            let wFish = (Int(arc4random() % 10) + 1) * 20 // 20-200
            let hFish = (wFish * 3) / 4
            let xStart = -wFish
            let xEnd = view.bounds.width + wFish
            let yPos = Int(arc4random() % UInt32(view.bounds.height - hFish)) + hFish/2 // 确保鱼在屏幕内
            
            // 创建鱼的ImageView
            let fishView = FLAnimatedImageView()
            fishView.animatedImage = fishGif1
            fishView.frame = CGRect(x: xStart, y: yPos, width: wFish, height: hFish)
            view.addSubview(fishView)
            fishViews.append(fishView)
            
            // 启动该鱼的往返动画
            startSwimCycle(for: fishView, startX: xStart, endX: xEnd, yPos: yPos, width: wFish, height: hFish)
        }
    }
    
    // 封装单个鱼的往返动画逻辑
    private func startSwimCycle(for fishView: FLAnimatedImageView, startX: Int, endX: Int, yPos: Int, width: Int, height: Int) {
        // 先游向右
        let swimRightDuration = Double(arc4random() % 10) + 5 // 5-15秒,避免过快
        let swimRightDelay = Double(arc4random() % 2)
        
        UIView.animate(withDuration: swimRightDuration, delay: swimRightDelay, options: .curveEaseInOut, animations: {
            fishView.frame = CGRect(x: endX, y: yPos, width: width, height: height)
        }, completion: { [weak self] _ in
            guard let self = self else { return }
            // 切换到翻转后的GIF
            fishView.animatedImage = self.fishGif2
            // 再游向左
            self.swimLeft(for: fishView, startX: startX, endX: endX, yPos: yPos, width: width, height: height)
        })
    }
    
    private func swimLeft(for fishView: FLAnimatedImageView, startX: Int, endX: Int, yPos: Int, width: Int, height: Int) {
        let swimLeftDuration = Double(arc4random() % 10) + 5
        let swimLeftDelay = Double(arc4random() % 2)
        
        UIView.animate(withDuration: swimLeftDuration, delay: swimLeftDelay, options: .curveEaseInOut, animations: {
            fishView.frame = CGRect(x: startX, y: yPos, width: width, height: height)
        }, completion: { [weak self] _ in
            guard let self = self else { return }
            // 切换回原GIF
            fishView.animatedImage = self.fishGif1
            // 循环往复
            self.startSwimCycle(for: fishView, startX: startX, endX: endX, yPos: yPos, width: width, height: height)
        })
    }
}

关键修复点说明

  1. 提前加载资源:在视图控制器初始化时就加载好两个GIF的FLAnimatedImage实例,循环里直接复用,提升性能。
  2. 封装动画逻辑:把单个鱼的往返动画拆成startSwimCycle和swimLeft方法,每个鱼实例独立管理自己的动画,避免作用域混乱。
  3. 移除.repeat选项:改用completion回调手动触发反向动画,确保每次动画完成后都能切换GIF并启动下一段动画。
  4. 存储鱼实例:用fishViews数组保存所有创建的FLAnimatedImageView,避免循环结束后实例被释放,同时方便后续清理(比如停止动画时)。
  5. 优化位置计算:使用view.bounds.width来适配不同屏幕尺寸,而不是硬编码380或450,让动画在不同设备上都能正常显示。

这样调整后,你的鱼类往返游动加翻转的动画应该就能正常工作了,而且代码结构更清晰,也避免了内存泄漏的问题。

内容的提问来源于stack exchange,提问作者Vera Boukhonine

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:24:40