Created
September 18, 2019 12:24
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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