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

如何使用PhaseAnimator创建不循环回初始阶段的抽奖动画?

实现SwiftUI老虎机式抽奖阶段动画

问题核心

要实现依次展示非中奖奖品、最终停在预先确定中奖奖品的老虎机效果,使用PhaseAnimator时遇到两个问题:

  1. 默认循环播放阶段,无法停在最后一个中奖阶段
  2. 调整中奖奖品位置后,结果提前泄露

优雅解决方案

利用PhaseAnimator的阶段序列构造和动画闭包控制,既避免循环,又防止结果提前泄露:

步骤1:明确中奖索引与阶段序列

构造包含完整过渡阶段+最终停留阶段的序列,确保动画结束后停在中奖状态;同时保持中奖奖品在序列末尾,避免提前显示。

步骤2:控制动画逻辑

为非中奖阶段设置过渡动画,最后一个阶段返回nil动画,让视图停留在中奖状态;配合VStack统一管理奖品图标和中奖文本的过渡。

完整代码示例:

import SwiftUI

struct LotteryView: View {
    // 奖品数组,中奖奖品为最后一个元素
    let prizes = ["square.and.arrow.up", "pencil", "eraser", "scribble", "trash", "folder"]
    // 预定义中奖索引
    private let winningIndex: Int
    
    init() {
        winningIndex = prizes.count - 1
        // 如果是随机中奖,可提前在这里生成随机索引:
        // winningIndex = Int.random(in: 0..<prizes.count)
    }
    
    // 构造阶段序列:完整遍历所有奖品 + 最后停留在中奖阶段
    private let phases: [Int]
    
    init(winningIndex: Int) {
        self.winningIndex = winningIndex
        phases = Array(0..<prizes.count) + [winningIndex]
    }
    
    @State private var trigger = false
    
    var body: some View {
        PhaseAnimator(phases, trigger: trigger) { currentIndex in
            VStack(spacing: 20) {
                // 奖品图标
                Image(systemName: prizes[currentIndex])
                    .font(.system(size: 60))
                    .id(prizes[currentIndex]) // 确保每个图标触发过渡
                    .transition(.push(from: .trailing))
                
                // 中奖文本仅在最后阶段显示
                if currentIndex == winningIndex {
                    Text("Congratulations! You got \(prizes[currentIndex])!")
                        .font(.title)
                        .transition(.push(from: .bottom))
                }
            }
        } animation: { currentIndex in
            // 仅非中奖阶段应用过渡动画,最后阶段无动画(保持停留)
            currentIndex != winningIndex ? .easeInOut(duration: 0.3) : nil
        }
        .onAppear {
            // 触发一次动画序列
            trigger.toggle()
        }
    }
}

关键优化点

  • 避免循环:通过在阶段序列末尾重复中奖索引,并为最后阶段设置nil动画,让PhaseAnimator执行完所有过渡后停在中奖状态,不会循环回初始阶段。
  • 防止提前泄露:中奖奖品保持在序列末尾,仅当动画到达最后阶段时才显示中奖文本,全程不会提前暴露结果。
  • 灵活扩展:如果需要随机中奖,只需在初始化时提前生成随机winningIndex,完全符合“中奖结果在视图展示前已确定”的需求。

替代方案(无需PhaseAnimator)

如果不想使用PhaseAnimator,也可以通过@State变量配合withAnimation和Timer手动控制过渡,但代码复杂度会更高:

struct LotteryViewAlternative: View {
    let prizes = ["square.and.arrow.up", "pencil", "eraser", "scribble", "trash", "folder"]
    private let winningIndex = 5
    @State private var currentIndex = 0
    @State private var isAnimating = false
    
    var body: some View {
        VStack(spacing: 20) {
            Image(systemName: prizes[currentIndex])
                .font(.system(size: 60))
                .transition(.push(from: .trailing))
            
            if currentIndex == winningIndex {
                Text("Congratulations! You got \(prizes[currentIndex])!")
                    .font(.title)
                    .transition(.push(from: .bottom))
            }
        }
        .onAppear {
            startAnimation()
        }
    }
    
    private func startAnimation() {
        isAnimating = true
        let totalSteps = prizes.count
        var currentStep = 0
        
        Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { timer in
            guard isAnimating else {
                timer.invalidate()
                return
            }
            
            withAnimation(.easeInOut) {
                currentIndex = currentStep % prizes.count
            }
            
            currentStep += 1
            
            if currentStep == totalSteps {
                // 最后停在中奖索引
                withAnimation(.easeInOut(duration: 0.5)) {
                    currentIndex = winningIndex
                }
                isAnimating = false
                timer.invalidate()
            }
        }
    }
}

但显然PhaseAnimator的方案更简洁优雅,符合SwiftUI的声明式编程风格。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 10:17:11