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

iOS17中PhaseAnimator循环动画视图销毁后崩溃问题求助

iOS 17 PhaseAnimator 返回视图后崩溃问题排查与解决

使用iOS 17新增的PhaseAnimator实现循环呼吸动画时,出现以下问题:推送PhaseView视图时动画正常,但从该视图返回后,应用无响应甚至崩溃。排查发现:

  • 不对Circle视图做属性更新时无异常
  • 使用phaseAnimator尾随函数也存在相同问题
  • 结合trigger与定时器模拟循环动画则不会崩溃

崩溃场景代码

enum OuterBreathState: CaseIterable {
    case exhale, inhale
    var scale: CGFloat {
        switch self {
            case .inhale: return 2
            case .exhale: return 1
        }
    }
    var color: Color {
        switch self {
            case .inhale: return .white
            case .exhale: return .blue
        }
    }
}

struct PhaseView: View {
    var body: some View {
        PhaseAnimator(OuterBreathState.allCases) { state in
            Circle()
                .background(.clear)
                .frame(width: 150, height: 150)
                .padding(.vertical)
                .scaleEffect(state.scale)
                .background(in: Circle())
                .foregroundStyle(state.color)
        } animation: {state in
            switch state {
                case .inhale: return .easeInOut(duration: 1.5)
                case .exhale: return .easeInOut(duration: 3.5)
            }
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("push") {
                PhaseView()
            }
        }
    }
}

正常场景代码

enum OuterBreathState: CaseIterable {
    case exhale, inhale
    var scale: CGFloat {
        switch self {
            case .inhale: return 2
            case .exhale: return 1
        }
    }
    var color: Color {
        switch self {
            case .inhale: return .white
            case .exhale: return .blue
        }
    }
}

struct PhaseView: View {
    @State var animate = false
    let timer = Timer.publish(every: 5, tolerance: .zero, on: .main, in: .default).autoconnect()
    var body: some View {
        PhaseAnimator(OuterBreathState.allCases, trigger: animate) { state in
            Circle()
                .background(.clear)
                .frame(width: 150, height: 150)
                .padding(.vertical)
                .scaleEffect(state.scale)
                .background(in: Circle())
                .foregroundStyle(state.color)
        } animation: {state in
            switch state {
                case .inhale: return .easeInOut(duration: 1.5)
                case .exhale: return .easeInOut(duration: 3.5)
            }
        }
        .onReceive(timer, perform: { _ in
            animate.toggle()
        })
        .onTapGesture {
            animate.toggle()
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            NavigationLink("push") {
                PhaseView()
            }
        }
    }
}

#Preview {
    ContentView()
}

崩溃原因

这是iOS 17.0至17.2版本中PhaseAnimator的已知bug:当使用无trigger的自动循环动画时,视图被销毁(返回上一页)后,PhaseAnimator内部的动画循环或定时器未被正确取消,导致主线程被持续占用,进而引发应用无响应或崩溃。而带trigger的版本中,trigger绑定的@State变量会随视图销毁而被释放,定时器也会自动停止,动画循环因此终止,避免了崩溃。

解决方案

1. 升级iOS版本

将设备或模拟器的iOS版本升级至17.3及以上,苹果在该版本中修复了PhaseAnimator的生命周期管理问题。

2. 手动管理动画生命周期

在视图消失时主动停止动画,通过添加trigger并在onDisappear中更新状态来终止循环:

struct PhaseView: View {
    @State private var isAnimating = true
    
    var body: some View {
        PhaseAnimator(OuterBreathState.allCases, trigger: isAnimating) { state in
            Circle()
                .background(.clear)
                .frame(width: 150, height: 150)
                .padding(.vertical)
                .scaleEffect(state.scale)
                .background(in: Circle())
                .foregroundStyle(state.color)
        } animation: { state in
            switch state {
                case .inhale: return .easeInOut(duration: 1.5)
                case .exhale: return .easeInOut(duration: 3.5)
            }
        }
        .onDisappear {
            isAnimating = false
        }
    }
}

3. 替代实现方案

若无法升级系统,可使用withAnimation结合定时器实现相同的呼吸动画效果:

struct PhaseView: View {
    @State private var scale: CGFloat = 1
    @State private var color: Color = .blue
    let timer = Timer.publish(every: 5, on: .main, in: .default).autoconnect()
    
    var body: some View {
        Circle()
            .background(.clear)
            .frame(width: 150, height: 150)
            .padding(.vertical)
            .scaleEffect(scale)
            .background(in: Circle())
            .foregroundStyle(color)
            .onReceive(timer) { _ in
                withAnimation(.easeInOut(duration: 1.5)) {
                    scale = 2
                    color = .white
                }
                DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
                    withAnimation(.easeInOut(duration: 3.5)) {
                        scale = 1
                        color = .blue
                    }
                }
            }
            .onDisappear {
                timer.upstream.connect().cancel()
            }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 06:05:55