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. -![platform](https://img.shields.io/badge/platform-iOS-orange) -[![GitHub license](https://img.shields.io/badge/License-Apache2.0-blue.svg)](LICENSE) -![Repository size](https://img.shields.io/github/repo-size/Fury-2K/Sharingan-Loader) -![GitHub stars](https://img.shields.io/github/stars/Fury-2K/Sharingan-Loader?style=social) -![GitHub forks](https://img.shields.io/github/forks/Fury-2K/Sharingan-Loader?style=social) +![platform](https://img.shields.io/badge/platform-iOS-orange)  +[![GitHub license](https://img.shields.io/badge/License-Apache2.0-blue.svg)](LICENSE)  +![Repository size](https://img.shields.io/github/repo-size/Fury-2K/Sharingan-Loader)  +![GitHub stars](https://img.shields.io/github/stars/Fury-2K/Sharingan-Loader?style=social)  +![GitHub forks](https://img.shields.io/github/forks/Fury-2K/Sharingan-Loader?style=social)  ![GitHub follow](https://img.shields.io/github/followers/Fury-2K?style=social) +

+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) } }