Skip to content

Instantly share code, notes, and snippets.

@DevAndArtist
Created September 18, 2019 12:24
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save DevAndArtist/a1b583691659b0b8ba04ccdb7af6d117 to your computer and use it in GitHub Desktop.
Save DevAndArtist/a1b583691659b0b8ba04ccdb7af6d117 to your computer and use it in GitHub Desktop.
SwiftUI Fake Animation completion for animations that do not overshoot the final value
fileprivate struct _CompletionPreferenceKey: PreferenceKey {
typealias Value = CGFloat
static let defaultValue: CGFloat = 0
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value = nextValue()
}
}
fileprivate struct _CompletionModifier: AnimatableModifier {
var progress: CGFloat = 0
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func body(content: Content) -> some View {
return content
.preference(key: _CompletionPreferenceKey.self, value: progress)
}
}
fileprivate final class _Catcher {
var animation: Animation?
var disablesAnimations: Bool = false
}
fileprivate func _getDuration(from animation: Animation) -> Double {
fatalError("TODO")
}
extension View {
public func onCompletion(
condition: Bool,
_ completion: @escaping () -> Void
) -> some View {
let catcher = _Catcher()
var completed = false
return self
.transaction { transaction in
// Restore the transaction values
transaction.animation = catcher.animation
transaction.disablesAnimations = catcher.disablesAnimations
}
.modifier(_CompletionModifier(progress: condition ? 1 : 0))
.transaction { transaction in
// Catch transaction value before modification
catcher.animation = transaction.animation
catcher.disablesAnimations = transaction.disablesAnimations
// if let animation = transaction.animation {
// let duration = _getDuration(from: animation)
// transaction.animation = .linear(duration: duration)
// }
}
.onPreferenceChange(_CompletionPreferenceKey.self) { progress in
if completed == false && progress >= 1 {
completed = true
completion()
}
}
}
}
extension Animation {
static var none: Animation {
// MAGICAL VALUE 🧙‍♂️
.linear(duration: 0)
}
}
struct SomeView: View {
var offset: Double
var body: some View {
Circle()
.trim(from: CGFloat(offset), to: 1)
.stroke(Color.blue, lineWidth: 2)
.frame(width: 100, height: 100)
.rotationEffect(.degrees(-90))
}
}
struct ContainerView: View {
@State
private var _text: String = ""
@State
private var _offset: Double = 0
func reset() {
if let offset = Double(_text) {
var transaction = Transaction(animation: Animation.none)
transaction.disablesAnimations = true
withTransaction(transaction) {
_offset = offset
}
}
}
func animate() {
withAnimation(.linear(duration: 2)) {
_offset = 1
}
}
var body: some View {
return VStack {
Color.clear.overlay(
SomeView(offset: _offset)
.onCompletion(condition: _offset == 1) {
print("completed")
}
)
TextField("", text: $_text)
.border(Color.red)
Button("reset", action: reset)
Button("reset then animate") {
self.reset()
self.animate()
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment