Created
April 27, 2023 16:39
-
-
Save gbuela/70c6123762ab105ecfd277e5d9da73d9 to your computer and use it in GitHub Desktop.
RotatingView
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
import SwiftUI | |
struct PausableRotation: GeometryEffect { | |
@Binding var currentAngle: Double | |
private var currentAngleValue: Double = 0.0 | |
var animatableData: Double { | |
get { currentAngleValue } | |
set { currentAngleValue = newValue } | |
} | |
init(desiredAngle: Double, currentAngle: Binding<Double>) { | |
self.currentAngleValue = desiredAngle | |
self._currentAngle = currentAngle | |
} | |
func effectValue(size: CGSize) -> ProjectionTransform { | |
DispatchQueue.main.async { | |
self.currentAngle = currentAngleValue | |
} | |
let xOffset = size.width / 2 | |
let yOffset = size.height / 2 | |
let transform = CGAffineTransform(translationX: xOffset, y: yOffset) | |
.rotated(by: currentAngleValue) | |
.translatedBy(x: -xOffset, y: -yOffset) | |
return ProjectionTransform(transform) | |
} | |
} | |
struct RotatingView<RV: View>: View { | |
@State private var desiredAngle: Double = 0.0 | |
@State private var currentAngle: Double = 0.0 | |
@State private var isRotationInEffect = false | |
let isRotating: Bool | |
let completeTurnInterval: TimeInterval | |
let accelerationInterval: TimeInterval | |
let accelerationRadians: Double | |
@ViewBuilder let content: RV | |
private var foreverAnimation: Animation { | |
Animation.linear(duration: completeTurnInterval) | |
.repeatForever(autoreverses: false) | |
} | |
var body: some View { | |
Group { | |
if isRotating { | |
content | |
.modifier(PausableRotation(desiredAngle: desiredAngle, currentAngle: $currentAngle)) | |
} else { | |
content | |
.modifier(PausableRotation(desiredAngle: desiredAngle, currentAngle: $currentAngle)) | |
} | |
} | |
.onAppear { | |
// when it's expected to show up rotating right away without accelerating from 0 | |
if isRotating && !isRotationInEffect { | |
isRotationInEffect = true | |
let startAngle = currentAngle.truncatingRemainder(dividingBy: Double.pi * 2) | |
desiredAngle = startAngle | |
withAnimation(foreverAnimation) { | |
self.desiredAngle = startAngle + Double.pi * 2 | |
} | |
} | |
} | |
.onChange(of: isRotating) { isRotating in | |
if isRotating { | |
let startAngle = currentAngle | |
withAnimation(.easeIn(duration: accelerationInterval)) { | |
self.desiredAngle = startAngle + accelerationRadians | |
} | |
DispatchQueue.main.asyncAfter(deadline: .now() + accelerationInterval) { | |
isRotationInEffect = true | |
let startAngle = currentAngle.truncatingRemainder(dividingBy: Double.pi * 2) | |
self.desiredAngle = startAngle | |
withAnimation(foreverAnimation) { | |
self.desiredAngle = startAngle + Double.pi * 2 | |
} | |
} | |
} else { | |
isRotationInEffect = false | |
let startAngle = currentAngle.truncatingRemainder(dividingBy: Double.pi * 2) | |
let finalAngle = startAngle + accelerationRadians | |
desiredAngle = startAngle | |
withAnimation(.easeOut(duration: accelerationInterval)) { | |
self.desiredAngle = finalAngle | |
} | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment