Last active
July 22, 2023 03:34
-
-
Save Koshimizu-Takehito/0325cc7c67e1b5bc867a12b1e72d5dfd to your computer and use it in GitHub Desktop.
おしゃれなローディングアニメーション
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
// Thanks to @Ren_yello | |
// https://twitter.com/ren_yello/status/1681856952090112000?s=61&t=SNv1ZCU_S3Y8TobPv4MBww | |
import SwiftUI | |
struct PhisicsLoadingView: View { | |
@State private var color: CGColor = .black | |
@State private var angle1: CGFloat = .pi/2 | |
@State private var angle2: CGFloat = .pi/2 | |
@State private var angle3: CGFloat = .pi/2 | |
var body: some View { | |
ZStack { | |
LoadingCircle(angle: angle1, color: color) | |
LoadingCircle(angle: angle2, color: color) | |
LoadingCircle(angle: angle3, color: color) | |
} | |
.padding() | |
.task { | |
await updateColor() | |
} | |
.task { | |
let animation = timingAnimation | |
.repeatForever(autoreverses: false) | |
withAnimation(animation) { angle1 += 2 * .pi } | |
try? await Task.sleep(nanoseconds: 400_000_000) | |
withAnimation(animation) { angle2 += 2 * .pi } | |
try? await Task.sleep(nanoseconds: 400_000_000) | |
withAnimation(animation) { angle3 += 2 * .pi } | |
} | |
} | |
func updateColor() async { | |
withAnimation(timingAnimation) { | |
color = .random | |
} | |
try? await Task.sleep(nanoseconds: 3_000_000_000) | |
Task { await updateColor() } | |
} | |
var timingAnimation: Animation { | |
let start = UnitPoint(x: 0.1, y: 0.7) | |
let end = UnitPoint(x: 0.9, y: 0.3) | |
let bezier = UnitCurve.bezier(startControlPoint: start, endControlPoint: end) | |
return .timingCurve(bezier, duration: 3) | |
} | |
} | |
private struct LoadingCircle: View, Animatable { | |
var angle: CGFloat | |
var color: CGColor | |
var animatableData: CGFloat { get { angle } set { angle = newValue } } | |
var body: some View { | |
GeometryReader { geometory in | |
let width = 0.1 * min(geometory.size.width, geometory.size.height) | |
let x = (geometory.size.width - width) / 2 | |
let y = (geometory.size.height - width) / 2 | |
let r = min(x, y) | |
Circle().frame(width: width) | |
.offset(x: x, y: y) | |
.offset(x: r * cos(angle), y: r * sin(angle)) | |
.foregroundStyle(Color(cgColor: color)) | |
} | |
.frame(maxWidth: .infinity, maxHeight: .infinity) | |
} | |
} | |
extension CGColor { | |
class var black: Self { | |
self.init(red: 0, green: 0, blue: 0, alpha: 1) | |
} | |
class var random: Self { | |
var value: CGFloat { .random(in: 0...1) } | |
return self.init(red: value, green: value, blue: value, alpha: 1) | |
} | |
} | |
struct PhisicsLoadingView_Previews: PreviewProvider { | |
static var previews: some View { | |
PhisicsLoadingView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment