diff --git a/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
new file mode 100644
index 0000000..919434a
--- /dev/null
+++ b/.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
@@ -0,0 +1,7 @@
+
+
+
+
+
diff --git a/README.md b/README.md
index 7dee3bd..78d5701 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,27 @@
# Sharingan Loader for iOS
Animated sharingan loader for iOS projects.
-This is a fun project created using SwiftUI with love for the Uchiha clan.
+This is a fun project created using SwiftUI with love for the Uchiha clan. More types of eyes to come with tons of customizations...
+Feel free to create an issue for suggestions or feature requests.
-
-[](LICENSE)
-
-
-
+
+[](LICENSE)
+
+
+

+
+The mangekyou sharingan is awakened after 4x `animationDuration`.
+
+
+Sharingan | Mangekyou Sharingan
+--- | ---
+
|
+
## What's New ?
-- Sharingan loader with 3 tomoe.
+- [v0.2.0] Sharingan loader with Mangekyou transformation.
+- [v0.1.3] Sharingan loader with 3 tomoe.
## Languages / Frameworks Used
- SwiftUI
@@ -20,7 +30,7 @@ This is a fun project created using SwiftUI with love for the Uchiha clan.
**The project is built supporting iOS 14 and above.**
## How to use?
-
+
First add the project using Swift Package Manager like this -
```
@@ -36,13 +46,15 @@ In the codebase you can do the following to use the loader -
SharinganLoader(diameter: 150,
animationDuration: 1,
backgroundDarkness: 0.2,
- isVisible: $showingLoader)
+ isVisible: $showingLoader,
+ shouldTransformToMangekyou: true)
The initializer is like -
SharinganLoader(diameter: CGFloat,
animationDuration: Double,
backgroundDarkness: Double,
- isVisible: Binding)
+ isVisible: Binding,
+ shouldTransformToMangekyou: Bool)
```
This has the following properties to help customize -
```swift
@@ -52,15 +64,21 @@ var diameter: CGFloat
/// Time taken for the eye to complete 1 full rotation.
var animationDuration: Double
-/// Black background intensity.
+/// Black background intensity.
+/// Range => 0 to 1
var backgroundDarkness: Double
+/// Binding var to toggle loader visibility.
@Binding var isVisible: Bool
+
+/// Toggle to allow mangekyou sharingan awakening.
+var shouldTransformToMangekyou: Bool
```
- **diameter**- Diameter aka width and height for the sharingan eye. *This has a default value of 100.*
- **animationDuration**- As the name suggests, is used for animation duration for the loader view. *This is set to a default value of 1 seconds.*
- **backgroundDarkness**- This is the intensity of darkness behind the loader. 0 being the lowest and 1 being the highest. *This has a default value of 0.2.*
- **isVisible**- This is the binding boolean which shows the loading indicator view if set to true and vic versa.
+- **shouldTransformToMangekyou**- This sets if the mangekyou sharingan will awaken or not.
## Looking to contribute?
- Feel free to add other loaders related to the anime.
diff --git a/Resources/img1.png b/Resources/img1.png
new file mode 100644
index 0000000..b0d6996
Binary files /dev/null and b/Resources/img1.png differ
diff --git a/Resources/img2.png b/Resources/img2.png
new file mode 100644
index 0000000..cf4cffe
Binary files /dev/null and b/Resources/img2.png differ
diff --git a/Resources/mangekyou.gif b/Resources/mangekyou.gif
new file mode 100644
index 0000000..8679d75
Binary files /dev/null and b/Resources/mangekyou.gif differ
diff --git a/Sources/SharinganLoader/MangekyouFinView.swift b/Sources/SharinganLoader/MangekyouFinView.swift
new file mode 100644
index 0000000..34d4892
--- /dev/null
+++ b/Sources/SharinganLoader/MangekyouFinView.swift
@@ -0,0 +1,57 @@
+//
+// MangekyouFinView.swift
+// ShariganLoader
+//
+// Created by Manas Aggarwal on 12/06/21.
+//
+
+import SwiftUI
+
+struct MangekyouFin: Shape {
+ let screenCenter = UIScreen.main.bounds.midX
+ let mainCircleCenter: CGPoint = CGPoint(x: UIScreen.main.bounds.midX - 10, y: UIScreen.main.bounds.midY + 7)
+ let a: CGPoint = CGPoint(x: UIScreen.main.bounds.midX - 10, y: UIScreen.main.bounds.midY + 17)
+
+ func path(in rect: CGRect) -> Path {
+ var path = Path()
+ path.move(to: CGPoint(x: rect.midX * 0.75, y: rect.midY))
+ path.addQuadCurve(
+ to: CGPoint(x: rect.midX * 1.91, y: rect.maxY * 0.7),
+ control: CGPoint(x: rect.maxX * 0.7, y: rect.midY * 1.9))
+ path.addQuadCurve(
+ to: CGPoint(x: rect.midX * 1.15, y: rect.midY * 0.7),
+ control: CGPoint(x: rect.midX * 1.3, y: rect.midY * 1.6))
+ return path
+ }
+}
+
+struct MangekyouFinView: View {
+ let radius: CGFloat = 300
+ var body: some View {
+ ZStack {
+ Circle()
+ .foregroundColor(.blue)
+ .frame(width: radius, height: radius)
+
+ MangekyouFin()
+ .frame(width: radius, height: radius)
+ .rotationEffect(.degrees(120))
+ MangekyouFin()
+ .frame(width: radius, height: radius)
+ .rotationEffect(.degrees(240))
+ MangekyouFin()
+ .frame(width: radius, height: radius)
+
+ Circle()
+ .foregroundColor(.red)
+ .frame(width: 45.3, height: 45.3)
+ }
+ .rotation3DEffect(.degrees(180), axis: (x: 0, y: 1, z: 0))
+ }
+}
+
+struct MangekyouFinView_Previews: PreviewProvider {
+ static var previews: some View {
+ MangekyouFinView()
+ }
+}
diff --git a/Sources/SharinganLoader/SharinganLoader.swift b/Sources/SharinganLoader/SharinganLoader.swift
index b160ed6..3484226 100644
--- a/Sources/SharinganLoader/SharinganLoader.swift
+++ b/Sources/SharinganLoader/SharinganLoader.swift
@@ -1,6 +1,6 @@
//
// SharinganLoader.swift
-// ShariganLoader
+// SharinganLoader
//
// Created by Manas Aggarwal on 28/05/21.
//
@@ -8,29 +8,43 @@
import SwiftUI
public struct SharinganLoader: View {
- /// Diameter of the sharingan eye.
- var diameter: CGFloat
/// Time taken for the eye to complete 1 full rotation.
- var animationDuration: Double
+ private var animationDuration: Double
/// Black background intensity.
- var backgroundDarkness: Double
+ private var backgroundDarkness: Double
+ /// Switch to repeat mangekyou animation infinitely.
+ private var repeatMangekyouAnimation: Bool
- @Binding var isVisible: Bool
+ @Binding private var isVisible: Bool
+ /// Diameter of the sharingan eye.
+ @State private var diameter: CGFloat
@State private var rotationDegree: Double = 0
@State private var tomoe1Opacity: Double = 0
@State private var tomoe2Opacity: Double = 0
+ @State private var isMangekyou: Bool = false
+
+ private var bounceIntensity: CGFloat
+ private var oldDiameterValue: CGFloat
+ private var shouldTransformToMangekyou: Bool
// MARK: - Initialization
public init(diameter: CGFloat = 100,
animationDuration: Double = 1,
backgroundDarkness: Double = 0.2,
- isVisible: Binding) {
+ isVisible: Binding,
+ shouldTransformToMangekyou: Bool = true) {//,
+// repeatMangekyouAnimation: Bool = true) {
self.diameter = diameter
self.animationDuration = animationDuration
self.backgroundDarkness = backgroundDarkness
self._isVisible = isVisible
+ self.shouldTransformToMangekyou = shouldTransformToMangekyou
+ self.repeatMangekyouAnimation = false//repeatMangekyouAnimation
+
+ self.bounceIntensity = diameter * 0.3
+ self.oldDiameterValue = diameter
}
// MARK: - View
@@ -39,72 +53,111 @@ public struct SharinganLoader: View {
guard isVisible else {return AnyView(EmptyView()) }
return AnyView(
ZStack {
+ // MARK: Background
Color.black
- .opacity(backgroundDarkness)
+ .opacity(0.5)
.ignoresSafeArea()
- Circle()
- .frame(width: diameter, height: diameter, alignment: .center)
- .foregroundColor(.black)
- .opacity(0.8)
- .shadow(radius: 10)
- .shadow(radius: 5)
- .shadow(radius: 5)
-
- Circle()
- .foregroundColor(.shariRed)
- .frame(width: diameter * 0.969, height: diameter * 0.969, alignment: .center)
-
- // Inner ring
+ // MARK: Loader View
ZStack {
Circle()
+ .frame(width: diameter, height: diameter, alignment: .center)
.foregroundColor(.black)
- .opacity(0.3)
- .frame(width: diameter * 0.621, height: diameter * 0.621, alignment: .center)
+ .opacity(0.8)
+ .shadow(radius: 10)
+ .shadow(radius: 5)
+ .shadow(radius: 5)
+ .animation(.interpolatingSpring(stiffness: 10, damping: 2))
Circle()
.foregroundColor(.shariRed)
- .frame(width: diameter * 0.606, height: diameter * 0.606, alignment: .center)
+ .frame(width: diameter * 0.969, height: diameter * 0.969, alignment: .center)
+ .animation(.interpolatingSpring(stiffness: 10, damping: 2))
- Circle()
- .frame(width: diameter * 0.151, height: diameter * 0.151, alignment: .center)
-
- TomoeView(radius: diameter * 0.242)
- .rotationEffect(.degrees(30))
- .offset(x: -(diameter * 0.196), y: diameter * 0.257)
- .opacity(tomoe1Opacity)
- .onAppear() {
- Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration), repeats: false) { _ in
- withAnimation(Animation.easeIn(duration: 1.5)) {
- tomoe1Opacity = 1
+ // MARK: Inner Eye
+ ZStack {
+ Circle()
+ .foregroundColor(.black)
+ .opacity(0.2)
+ .frame(width: diameter * 0.621, height: diameter * 0.621, alignment: .center)
+
+ Circle()
+ .foregroundColor(.shariRed)
+ .frame(width: diameter * 0.606, height: diameter * 0.606, alignment: .center)
+
+ /// First tomoe
+ TomoeView(diameter: $diameter, isMangekyou: $isMangekyou)
+ .rotationEffect(.degrees(150))
+ .offset(x: isMangekyou ? 0 : -(diameter * 0.196), y: isMangekyou ? 0 : -(diameter * 0.257))
+
+ /// Second tomoe
+ TomoeView(diameter: $diameter, isMangekyou: $isMangekyou)
+ .rotationEffect(.degrees(30))
+ .offset(x: isMangekyou ? 0 : -(diameter * 0.196), y: isMangekyou ? 0 : diameter * 0.257)
+ .opacity(tomoe1Opacity)
+ .onAppear() {
+ Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration), repeats: false) { _ in
+ withAnimation(Animation.easeIn(duration: 1.5)) {
+ tomoe1Opacity = 1
+ }
}
}
- }
-
- TomoeView(radius: diameter * 0.242)
- .rotationEffect(.degrees(-90))
- .offset(x: diameter * 0.318, y: 0)
- .opacity(tomoe2Opacity)
- .onAppear() {
- Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration * 2), repeats: false) { _ in
- withAnimation(Animation.easeIn(duration: 1.5)) {
- tomoe2Opacity = 1
+
+ /// Thrid tomoe
+ TomoeView(diameter: $diameter, isMangekyou: $isMangekyou)
+ .rotationEffect(.degrees(-90))
+ .offset(x: isMangekyou ? 0 : diameter * 0.318, y: 0)
+ .opacity(tomoe2Opacity)
+ .onAppear() {
+ Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration * 2), repeats: false) { _ in
+ withAnimation(Animation.easeIn(duration: 1.5)) {
+ tomoe2Opacity = 1
+ }
}
}
+
+ /// Inner circle
+ Circle()
+ .foregroundColor(isMangekyou ? .shariRed : .black)
+ .frame(width: diameter * 0.151, height: diameter * 0.151, alignment: .center)
+ }
+ .onAppear() {
+ addRotationAnimation()
+ transformToMangekyou()
+ if shouldTransformToMangekyou {
+ addMangekyouSpringAnimation()
}
-
-
- TomoeView(radius: diameter * 0.242)
- .rotationEffect(.degrees(150))
- .offset(x: -(diameter * 0.196), y: -(diameter * 0.257))
- }
- .onAppear() {
- withAnimation(Animation.easeOut(duration: animationDuration).repeatForever(autoreverses: false)) {
- rotationDegree += 360
}
+ .rotationEffect(.degrees(rotationDegree))
}
- .rotationEffect(.degrees(rotationDegree))
}
)
}
+
+ private func addRotationAnimation() {
+ withAnimation(Animation.easeOut(duration: animationDuration).repeatForever(autoreverses: false)) {
+ rotationDegree -= 360
+ }
+ }
+
+ private func transformToMangekyou() {
+ Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration * 4), repeats: repeatMangekyouAnimation) { _ in
+ withAnimation(Animation.easeInOut(duration: animationDuration * 0.5)) {
+ isMangekyou.toggle()
+ }
+ }
+ }
+
+ private func addMangekyouSpringAnimation() {
+ Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration * 3.5), repeats: repeatMangekyouAnimation) { _ in
+ withAnimation(Animation.easeIn(duration: animationDuration * 0.2)) {
+ diameter -= bounceIntensity
+ }
+ }
+ Timer.scheduledTimer(withTimeInterval: TimeInterval(animationDuration * 3.7), repeats: repeatMangekyouAnimation) { _ in
+ withAnimation(Animation.easeOut(duration: animationDuration * 0.3)) {
+ diameter = oldDiameterValue
+ }
+ }
+ }
}
diff --git a/Sources/SharinganLoader/TomoeView.swift b/Sources/SharinganLoader/TomoeView.swift
index 20f21f4..3073e94 100644
--- a/Sources/SharinganLoader/TomoeView.swift
+++ b/Sources/SharinganLoader/TomoeView.swift
@@ -7,7 +7,8 @@
import SwiftUI
-private struct Tomoe: Shape {
+struct Tomoe: Shape {
+ @Binding var isMangekyou: Bool
let screenCenter = UIScreen.main.bounds.midX
let mainCircleCenter: CGPoint = CGPoint(x: UIScreen.main.bounds.midX - 10, y: UIScreen.main.bounds.midY + 7)
@@ -17,51 +18,66 @@ private struct Tomoe: Shape {
var path = Path()
// MARK:- Shit tomoe 1
-// path.move(to: CGPoint(x: 260, y: 280))
-// path.addArc(center: CGPoint(x: 200, y: 200), radius: 100, startAngle: .degrees(53), endAngle: .degrees(330), clockwise: false)
-// path.addArc(center: CGPoint(x: 114, y: 251), radius: 200, startAngle: .degrees(-30), endAngle: .degrees(9), clockwise: false)
-// path.move(to: CGPoint(x: 260, y: 280))
-// path.addArc(center: CGPoint(x: 152, y: 272), radius: 160, startAngle: .degrees(2), endAngle: .degrees(87), clockwise: false)
-// path.addArc(center: CGPoint(x: 110, y: 290), radius: 150, startAngle: .degrees(70), endAngle: .degrees(-4), clockwise: true)
-// path.closeSubpath()
+
+ // path.move(to: CGPoint(x: 260, y: 280))
+ // path.addArc(center: CGPoint(x: 200, y: 200), radius: 100, startAngle: .degrees(53), endAngle: .degrees(330), clockwise: false)
+ // path.addArc(center: CGPoint(x: 114, y: 251), radius: 200, startAngle: .degrees(-30), endAngle: .degrees(9), clockwise: false)
+ // path.move(to: CGPoint(x: 260, y: 280))
+ // path.addArc(center: CGPoint(x: 152, y: 272), radius: 160, startAngle: .degrees(2), endAngle: .degrees(87), clockwise: false)
+ // path.addArc(center: CGPoint(x: 110, y: 290), radius: 150, startAngle: .degrees(70), endAngle: .degrees(-4), clockwise: true)
+ // path.closeSubpath()
// MARK:- Shit tomoe 2
-// path.move(to: CGPoint(x: rect.midX, y: rect.minY))
-// path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), control: CGPoint(x: rect.maxX, y: rect.minY))
-// path.addQuadCurve(to: CGPoint(x: rect.midX * 0.8, y: rect.maxY), control: CGPoint(x: rect.maxX * 0.95, y: rect.maxY))
-// path.addQuadCurve(to: CGPoint(x: rect.midX * 1.5, y: rect.midY * 0.95), control: CGPoint(x: rect.maxX * 0.87, y: rect.maxY * 0.87))
-// path.addQuadCurve(to: CGPoint(x: rect.midX * 1.1, y: rect.midY * 1.2), control: CGPoint(x: rect.midX * 1.4, y: rect.midY * 1.2))
-// path.addQuadCurve(to: CGPoint(x: rect.midX * 0.55, y: rect.midY * 0.5), control: CGPoint(x: rect.midX * 0.5, y: rect.midY * 1.2))
-// path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), control: CGPoint(x: rect.midX * 0.65, y: rect.midY * 0.05))
+
+ // path.move(to: CGPoint(x: rect.midX, y: rect.minY))
+ // path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.midY), control: CGPoint(x: rect.maxX, y: rect.minY))
+ // path.addQuadCurve(to: CGPoint(x: rect.midX * 0.8, y: rect.maxY), control: CGPoint(x: rect.maxX * 0.95, y: rect.maxY))
+ // path.addQuadCurve(to: CGPoint(x: rect.midX * 1.5, y: rect.midY * 0.95), control: CGPoint(x: rect.maxX * 0.87, y: rect.maxY * 0.87))
+ // path.addQuadCurve(to: CGPoint(x: rect.midX * 1.1, y: rect.midY * 1.2), control: CGPoint(x: rect.midX * 1.4, y: rect.midY * 1.2))
+ // path.addQuadCurve(to: CGPoint(x: rect.midX * 0.55, y: rect.midY * 0.5), control: CGPoint(x: rect.midX * 0.5, y: rect.midY * 1.2))
+ // path.addQuadCurve(to: CGPoint(x: rect.midX, y: rect.minY), control: CGPoint(x: rect.midX * 0.65, y: rect.midY * 0.05))
// MARK:- Real tomoe
- path.move(to: CGPoint(x: rect.midX * 1.3, y: rect.midY * 0.6))
+
+ path.move(to: isMangekyou ?
+ CGPoint(x: rect.midX * 0.75, y: rect.midY * 1) :
+ CGPoint(x: rect.midX * 1.3, y: rect.midY * 0.6))
+
path.addQuadCurve(
- to: CGPoint(x: rect.midX * 0.5, y: rect.maxY * 1.05),
- control: CGPoint(x: rect.maxX * 1, y: rect.midY * 1.35))
+ to: isMangekyou ?
+ CGPoint(x: rect.maxX * 0.99, y: rect.maxY * 0.6) :
+ CGPoint(x: rect.midX * 0.5, y: rect.maxY * 1.05),
+ control: isMangekyou ?
+ CGPoint(x: rect.maxX * 0.7, y: rect.maxY * 0.2) :
+ CGPoint(x: rect.maxX * 1, y: rect.midY * 1.35))
+
path.addQuadCurve(
- to: CGPoint(x: rect.midX * 0.8, y: rect.midY * 0.6),
- control: CGPoint(x: rect.midX * 1.45, y: rect.midY * 1.2))
+ to: isMangekyou ?
+ CGPoint(x: rect.midX * 1.14, y: rect.midY * 1.2) :
+ CGPoint(x: rect.midX * 0.8, y: rect.midY * 0.6),
+ control: isMangekyou ?
+ CGPoint(x: rect.midX * 1.4, y: rect.midY * 0.8) :
+ CGPoint(x: rect.midX * 1.45, y: rect.midY * 1.2))
return path
}
}
-internal struct TomoeView: View {
- var radius: CGFloat
+struct TomoeView: View {
+ @Binding var diameter: CGFloat
+ @Binding var isMangekyou: Bool
var body: some View {
+ let radius: CGFloat = isMangekyou ? diameter : diameter * 0.242
+
ZStack {
Circle()
- .frame(width: radius/2, height: radius/2)
- Tomoe()
+ .foregroundColor(.black)
+ .frame(width: diameter * 0.121, height: diameter * 0.121)
+// .opacity(isMangekyou ? 0 : 1)
+ Tomoe(isMangekyou: $isMangekyou)
+ .foregroundColor(.black)
.frame(width: radius, height: radius)
+// .rotation3DEffect(.degrees(isMangekyou ? 180 : 0), axis: (x: 0, y: isMangekyou ? 1 : 0, z: 0))
}
- .offset(x: 0, y: -radius * 0.12)
- }
-}
-
-struct TomoeView_Previews: PreviewProvider {
- static var previews: some View {
- TomoeView(radius: 300)
}
}