Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Mangekyou sharingan #4

Merged
merged 5 commits into from
Jun 13, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 29 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
@@ -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...<br>
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)&nbsp;
[![GitHub license](https://img.shields.io/badge/License-Apache2.0-blue.svg)](LICENSE)&nbsp;
![Repository size](https://img.shields.io/github/repo-size/Fury-2K/Sharingan-Loader)&nbsp;
![GitHub stars](https://img.shields.io/github/stars/Fury-2K/Sharingan-Loader?style=social)&nbsp;
![GitHub forks](https://img.shields.io/github/forks/Fury-2K/Sharingan-Loader?style=social)&nbsp;
![GitHub follow](https://img.shields.io/github/followers/Fury-2K?style=social)

<p><b>
The mangekyou sharingan is awakened after 4x `animationDuration`.
</b></p>

Sharingan | Mangekyou Sharingan
--- | ---
<img align="right" src="/Resources/img1.png" width="280"> | <img align="right" src="/Resources/img2.png" width="280">

## 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
Expand All @@ -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?
<img align="right" src="/Resources/sharingan_loader.gif" height="500">
<img align="right" src="/Resources/mangekyou.gif" height="500">

First add the project using Swift Package Manager like this -
```
Expand All @@ -36,13 +46,15 @@ In the codebase you can do the following to use the loader -<br>
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<Bool>)
isVisible: Binding<Bool>,
shouldTransformToMangekyou: Bool)
```
This has the following properties to help customize -<br>
```swift
Expand All @@ -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.
Expand Down
Binary file added Resources/img1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/img2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Resources/mangekyou.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 57 additions & 0 deletions Sources/SharinganLoader/MangekyouFinView.swift
Original file line number Diff line number Diff line change
@@ -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()
}
}
163 changes: 108 additions & 55 deletions Sources/SharinganLoader/SharinganLoader.swift
Original file line number Diff line number Diff line change
@@ -1,36 +1,50 @@
//
// SharinganLoader.swift
// ShariganLoader
// SharinganLoader
//
// Created by Manas Aggarwal on 28/05/21.
//

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<Bool>) {
isVisible: Binding<Bool>,
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
Expand All @@ -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
}
}
}
}
Loading