<!--
{
  "availability" : [
    "iOS: 17.0.0 -",
    "iPadOS: 17.0.0 -",
    "macCatalyst: 17.0.0 -",
    "macOS: 14.0.0 -",
    "tvOS: 17.0.0 -",
    "visionOS: 1.0.0 -",
    "watchOS: 10.0.0 -"
  ],
  "documentType" : "symbol",
  "framework" : "SwiftUI",
  "identifier" : "/documentation/SwiftUI/CustomAnimation",
  "metadataVersion" : "0.1.0",
  "role" : "Protocol",
  "symbol" : {
    "kind" : "Protocol",
    "modules" : [
      "SwiftUI"
    ],
    "preciseIdentifier" : "s:7SwiftUI15CustomAnimationP"
  },
  "title" : "CustomAnimation"
}
-->

# CustomAnimation

A type that defines how an animatable value changes over time.

```
@preconcurrency protocol CustomAnimation : Hashable, Sendable
```

## Overview

Use this protocol to create a type that changes an animatable value over
time, which produces a custom visual transition of a view. For example, the
follow code changes an animatable value using an elastic ease-in ease-out
function:

```
struct ElasticEaseInEaseOutAnimation: CustomAnimation {
    let duration: TimeInterval

    func animate<V>(value: V, time: TimeInterval, context: inout AnimationContext<V>) -> V? where V : VectorArithmetic {
        if time > duration { return nil } // The animation has finished.

        let p = time / duration
        let s = sin((20 * p - 11.125) * ((2 * Double.pi) / 4.5))
        if p < 0.5 {
            return value.scaled(by: -(pow(2, 20 * p - 10) * s) / 2)
        } else {
            return value.scaled(by: (pow(2, -20 * p + 10) * s) / 2 + 1)
        }
    }
}
```

> Note: To maintain state during the life span of a custom animation, use
> the ``doc://com.apple.SwiftUI/documentation/SwiftUI/AnimationContext/state`` property available on the `context`
> parameter value. You can also use context’s
> ``doc://com.apple.SwiftUI/documentation/SwiftUI/AnimationContext/environment`` property to retrieve environment values
> from the view that created the custom animation. For more information, see
> ``doc://com.apple.SwiftUI/documentation/SwiftUI/AnimationContext``.

To create an [`Animation`](/documentation/SwiftUI/Animation) instance of a custom animation, use the
[`init(_:)`](/documentation/SwiftUI/Animation/init(_:)) initializer, passing in an instance of a custom
animation; for example:

```
Animation(ElasticEaseInEaseOutAnimation(duration: 5.0))
```

To help make view code more readable, extend [`Animation`](/documentation/SwiftUI/Animation) and add a static
property and function that returns an `Animation` instance of a custom
animation. For example, the following code adds the static property
`elasticEaseInEaseOut` that returns the elastic ease-in ease-out animation
with a default duration of `0.35` seconds. Next, the code adds a method
that returns the animation with a specified duration.

```
extension Animation {
    static var elasticEaseInEaseOut: Animation { elasticEaseInEaseOut(duration: 0.35) }
    static func elasticEaseInEaseOut(duration: TimeInterval) -> Animation {
        Animation(ElasticEaseInEaseOutAnimation(duration: duration))
    }
}
```

To animate a view with the elastic ease-in ease-out animation, a view calls
either `.elasticEaseInEaseOut` or `.elasticEaseInEaseOut(duration:)`. For
example, the follow code includes an Animate button that, when clicked,
animates a circle as it moves from one edge of the view to the other,
using the elastic ease-in ease-out animation with a duration of `5`
seconds:

```
struct ElasticEaseInEaseOutView: View {
    @State private var isActive = false

    var body: some View {
        VStack(alignment: isActive ? .trailing : .leading) {
            Circle()
                .frame(width: 100.0)
                .foregroundColor(.accentColor)

            Button("Animate") {
                withAnimation(.elasticEaseInEaseOut(duration: 5.0)) {
                    isActive.toggle()
                }
            }
            .frame(maxWidth: .infinity)
        }
        .padding()
    }
}
```

![A video that shows a circle that moves from one edge of the view to the other using an elastic ease-in ease-out animation. The circle's initial position is near the leading edge of the view. The circle begins moving slightly towards the leading, then towards trail edges of the view before it moves off the leading edge showing only two-thirds of the circle. The circle then moves quickly to the trailing edge of the view, going slightly beyond the edge so that only two-thirds of the circle is visible. The circle bounces back into full view before settling into position near the trailing edge of the view. The circle repeats this animation in reverse, going from the trailing edge of the view to the leading edge.](videos/com.apple.SwiftUI/animation-20-elastic.mp4)

## Topics

### Animating a value

[`animate(value:time:context:)`](/documentation/SwiftUI/CustomAnimation/animate(value:time:context:))

Calculates the value of the animation at the specified time.

### Getting the velocity

[`velocity(value:time:context:)`](/documentation/SwiftUI/CustomAnimation/velocity(value:time:context:))

Calculates the velocity of the animation at a specified time.

### Determining whether to merge

[`shouldMerge(previous:value:time:context:)`](/documentation/SwiftUI/CustomAnimation/shouldMerge(previous:value:time:context:))

Determines whether an instance of the animation can merge with other
running animations.

## Relationships

### Inherits From

[`SendableMetatype`](/documentation/Swift/SendableMetatype)

[`Hashable`](/documentation/Swift/Hashable)

[`Equatable`](/documentation/Swift/Equatable)

[`Sendable`](/documentation/Swift/Sendable)

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)