PhaseAnimator on presented View crashes the app

Hi

I'm trying to use the new PhaseAnimator from iOS 17 and I noticed a bug. For some reason, the animation is not deallocated when the view is removed from hierarchy, leading to a crash app.

Example

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()
            }
        }
    }
}

Pushing the view works just fine, but after popping, the app becomes unresponsive 😓 It's the content update that fails actually. If I make no updates to the Circle, everything works fine. I also tried with the phaseAnimator trailing function, I get the same behaviour.

Interesting fact : if I use a trigger (and even a timer that flips that trigger to mimic the infinite loop), it does not crash the app. Here is the code


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()
}

Any idea why it crashes and how I can make it work ? Thanks!

Interesting fact : if I use a trigger, it does not crash. Is a use a timer that flips a boolean trigger to mimic a loop, it does not crash either (just edited the POST)

I'm having the exact same problem and it made me lose a lot of time.

I tried using opacity to show/hide the view that I wanted to animate and it worked until I tried to "navigate" to another view and the UI got unresponsive. There is a loop and a possible memory leak happening.

It seems I will need to use the timer trick for now.

Yeah same problem. Mine is a continuous animation when enabled, so I just added a trigger and this method to make it work. Otherwise the app would just stall out and do nothing.

.onAppear {
    trigger.toggle()
}

Thanks for your answers, glad I'm not the only one :D I hope Eskimo will have a look at it and file a radar, I do not find out to file these now :-/

Same Behaviour here 😓

Having the exact same issue. Took forever to isolate down to my navigation failing pop back to root because of a simple phase Animation in 17.0. Seeing if 17.2 has any fix for this, or else using that time workaround for the infinite repeat.

Upgraded to 17.2, still crashing but it atleast gives me a full report for why


Translated Report (Full Report Below)

Incident Identifier: 59983618-8289-47D5-A105-9B20C4FAF1E0 CrashReporter Key: CBBB8F29-9889-E65D-23E8-D5C42B3F1D39 Hardware Model: MacBookPro18,4 Code Type: ARM-64 (Native) Role: Foreground Parent Process: launchd_sim [46445] Coalition: com.apple.CoreSimulator.SimDevice.174BD9B5-AE47-48D4-BB97-32D9F63F0EB8 [30582] Responsible Process: SimulatorTrampoline [45578]

Date/Time: 2024-01-07 19:54:52.5279 +0800 Launch Time: 2024-01-07 19:54:39.4436 +0800 OS Version: macOS 14.1.1 (23B81) Release Type: User Report Version: 104

Exception Type: EXC_CRASH (SIGABRT) Exception Codes: 0x0000000000000000, 0x0000000000000000 Termination Reason: SIGNAL 6 Abort trap: 6 Terminating Process: PocketTarot [48010]

Triggered by Thread: 0

Thread 0 Crashed:: Dispatch queue: com.apple.main-thread 0 libsystem_kernel.dylib 0x104ce0a4c __pthread_kill + 8 1 libsystem_pthread.dylib 0x104eb31d0 pthread_kill + 256 2 libsystem_c.dylib 0x1801605cc abort + 104 3 libswiftCore.dylib 0x192c95b54 swift::fatalErrorv(unsigned int, char const*, char*) + 124 4 libswiftCore.dylib 0x192c95b70 swift::fatalError(unsigned int, char const*, ...) + 28 5 libswiftCore.dylib 0x192c960dc swift::runtime::AccessSet::insert(swift::runtime::Access*, void*, void*, swift::ExclusivityFlags) + 532 6 libswiftCore.dylib 0x192c96124 swift_beginAccess + 72 7 SwiftUI 0x1c571f230 0x1c4371000 + 20636208 8 SwiftUI 0x1c583d5d4 0x1c4371000 + 21808596 9 SwiftUI 0x1c4cced5c 0x1c4371000 + 9821532 10 SwiftUI 0x1c4ccec48 0x1c4371000 + 9821256 11 SwiftUI 0x1c5981aa4 0x1c4371000 + 23136932 12 SwiftUI 0x1c57c01dc 0x1c4371000 + 21295580 13 SwiftUI 0x1c57befe4 0x1c4371000 + 21290980 14 SwiftUI 0x1c57befbc 0x1c4371000 + 21290940 15 SwiftUI 0x1c57bf040 0x1c4371000 + 21291072 16 AttributeGraph 0x1b2c046e8 0x1b2bd3000 + 202472 17 AttributeGraph 0x1b2bf29ec AG::Node::destroy(AG::Graph&) + 188 18 AttributeGraph 0x1b2be8c50 AG::Subgraph::invalidate_now(AG::Graph&) + 1184 19 AttributeGraph 0x1b2be8738 AG::Graph::invalidate_subgraphs() + 192 20 AttributeGraph 0x1b2be1524 AG::Graph::Context::~Context() + 348 21 AttributeGraph 0x1b2bfa760 AGGraphInvalidate + 32 22 SwiftUI 0x1c571eb60 0x1c4371000 + 20634464 23 SwiftUI 0x1c58426f4 0x1c4371000 + 21829364 24 SwiftUI 0x1c5842614 0x1c4371000 + 21829140 25 SwiftUI 0x1c5842720 0x1c4371000 + 21829408 26 libobjc.A.dylib 0x180083148 AutoreleasePoolPage::releaseUntil(objc_object**) + 200 27 libobjc.A.dylib 0x180083018 objc_autoreleasePoolPop + 256 28 CoreFoundation 0x1804a62d0 _CFAutoreleasePoolPop + 28 29 CoreFoundation 0x1803f0ef8 __CFRunLoopPerCalloutARPEnd + 44 30 CoreFoundation 0x1803ec170 __CFRunLoopRun + 1976 31 CoreFoundation 0x1803eb5a4 CFRunLoopRunSpecific + 572 32 GraphicsServices 0x18e9fbae4 GSEventRunModal + 160 33 UIKitCore 0x1852f02e4 -[UIApplication _run] + 868 34 UIKitCore 0x1852f3f5c UIApplicationMain + 124 35 SwiftUI 0x1c51fc1b0 0x1c4371000 + 15249840 36 SwiftUI 0x1c51fc050 0x1c4371000 + 15249488 37 SwiftUI 0x1c4f02fa4 0x1c4371000 + 12132260 38 PocketTarot 0x104a2516c static PocketTarotApp.$main() + 40 39 PocketTarot 0x104a25268 main + 12 (PocketTarotApp.swift:12) 40 dyld_sim 0x104d61544 start_sim + 20 41 dyld 0x104ef60e0 start + 2360

Thread 3:: com.apple.uikit.eventfetch-thread 0 libsystem_kernel.dylib 0x104cd8c10 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x104ce9da4 mach_msg2_internal + 76 2 libsystem_kernel.dylib 0x104ce0e34 mach_msg_overwrite + 532 3 libsystem_kernel.dylib 0x104cd8f88 mach_msg + 20 4 CoreFoundation 0x1803f1754 __CFRunLoopServiceMachPort + 156 5 CoreFoundation 0x1803ebe34 __CFRunLoopRun + 1148 6 CoreFoundation 0x1803eb5a4 CFRunLoopRunSpecific + 572 7 Foundation 0x180dcd68c -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 208 8 Foundation 0x180dcd8b0 -[NSRunLoop(NSRunLoop) runUntilDate:] + 60 9 UIKitCore 0x185393b88 -[UIEventFetcher threadMain] + 404 10 Foundation 0x180df3e70 NSThread__start + 720 11 libsystem_pthread.dylib 0x104eb34c0 _pthread_start + 104 12 libsystem_pthread.dylib 0x104eae6f0 thread_start + 8

Thread 6:: com.apple.SwiftUI.AsyncRenderer 0 libsystem_kernel.dylib 0x104cd8c10 mach_msg2_trap + 8 1 libsystem_kernel.dylib 0x104ce9da4 mach_msg2_internal + 76 2 libsystem_kernel.dylib 0x104ce0e34 mach_msg_overwrite + 532 3 libsystem_kernel.dylib 0x104cd8f88 mach_msg + 20 4 CoreFoundation 0x1803f1754 __CFRunLoopServiceMachPort + 156 5 CoreFoundation 0x1803ebe34 __CFRunLoopRun + 1148 6 CoreFoundation 0x1803eb5a4 CFRunLoopRunSpecific + 572 7 Foundation 0x180dcd68c -[NSRunLoop(NSRunLoop) runMode:beforeDate:] + 208 8 Foundation 0x180dcd860 -[NSRunLoop(NSRunLoop) run] + 60 9 SwiftUI 0x1c49c8aac 0x1c4371000 + 6650540 10 SwiftUI 0x1c49c8bc4 0x1c4371000 + 6650820 11 Foundation 0x180df3e70 NSThread__start + 720 12 libsystem_pthread.dylib 0x104eb34c0 _pthread_start + 104 13 libsystem_pthread.dylib 0x104eae6f0 thread_start + 8

PhaseAnimator on presented View crashes the app
 
 
Q