Created
July 18, 2023 23:40
-
-
Save Koshimizu-Takehito/93c5891e89d65eadf2164351ca1f2d76 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
import SwiftUI | |
// Thanks to @Ren_yello | |
// https://twitter.com/ren_yello/status/1681135824145113089?s=61&t=Z69bUaia8ogjDHp-N7Xvvg | |
struct ContentView: View { | |
@State var ratio: Double = 0 | |
@State var angle: CGFloat = 0 | |
@State var delta: CGFloat = 0 | |
var body: some View { | |
ZStack { | |
PercentText(ratio: ratio) | |
LoadingCircles(angle: angle, delta: delta) | |
.frame(width: 180, height: 180) | |
} | |
.background(Color.white.opacity(0.0001)) | |
.onTapGesture { | |
ratio = 0 | |
angle = 0 | |
delta = .pi / 2 | |
withAnimation(.linear(duration: 6.0).repeatForever(autoreverses: false)) { | |
angle = 6.0 * .pi | |
} | |
withAnimation(.easeOut(duration: 12.0)) { | |
ratio = 1 | |
} | |
Task { | |
try await Task.sleep(nanoseconds: 12 * 1_000_000_000) | |
withAnimation(.easeOut(duration: 1.0)) { | |
angle = 0 | |
delta = 2 * .pi | |
} | |
} | |
} | |
} | |
} | |
struct PercentText: View, Animatable { | |
var ratio: Double | |
var animatableData: Double { | |
get { ratio } | |
set { ratio = newValue } | |
} | |
var body: some View { | |
let value = Int(min(max(ratio, 0), 1) * 100) | |
Text("\(value)%") | |
.font(.title) | |
.fontWeight(.semibold) | |
.monospacedDigit() | |
} | |
} | |
struct LoadingCircles: View { | |
var angle: CGFloat | |
var delta: CGFloat | |
var angle1: CGFloat { angle * 6.0 / 6.0 } | |
var angle2: CGFloat { angle * 8.0 / 6.0 } | |
var angle3: CGFloat { angle * 12.0 / 6.0 } | |
var body: some View { | |
ZStack { | |
PartialCircle(start: angle1, end: angle1 + delta) | |
.stroke(lineWidth: 3.0) | |
.foregroundStyle(Color.red) | |
PartialCircle(start: angle2, end: angle2 + delta) | |
.stroke(lineWidth: 3.0) | |
.padding(16) | |
.foregroundStyle(Color.blue) | |
PartialCircle(start: angle3, end: angle3 + delta) | |
.stroke(lineWidth: 3.0) | |
.padding(32) | |
.foregroundStyle(Color.yellow) | |
} | |
} | |
} | |
struct PartialCircle: Shape, Animatable { | |
var start: Double | |
var end: Double | |
var animatableData: AnimatablePair<Double, Double> { | |
get { AnimatablePair(start, end) } | |
set { (start, end) = (newValue.first, newValue.second) } | |
} | |
func path(in rect: CGRect) -> Path { | |
let radius: CGFloat = min(rect.width, rect.height)/2 | |
let center = CGPoint(x: rect.midX, y: rect.midY) | |
let start = Angle(radians: start) | |
let end = Angle(radians: end) | |
var path = Path() | |
path.addArc(center: center, radius: radius, startAngle: start, endAngle: end, clockwise: false) | |
return path | |
} | |
} | |
struct ContentView_Previews: PreviewProvider { | |
static var previews: some View { | |
ContentView() | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment