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

New activity indicator newtonCradle and circlePendulum #573

Merged
merged 4 commits into from
Jul 4, 2018
Merged
Show file tree
Hide file tree
Changes from 3 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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ N/A
- Add `preferred` presentation modal size. [#566](https://github.com/IBAnimatable/IBAnimatable/pull/566) by [@phimage](https://github.com/phimage)
- Add `conical` gradient type. [#567](https://github.com/IBAnimatable/IBAnimatable/pull/567) by [@phimage](https://github.com/phimage)
- Animatable `timingFunction` parameter could now be used with all animation types. [#571](https://github.com/IBAnimatable/IBAnimatable/pull/571) by [@phimage](https://github.com/phimage)
- Add new activity indicators `newtonCradle` and `circlePendulum`. [#573](https://github.com/IBAnimatable/IBAnimatable/pull/573) by [@phimage](https://github.com/phimage)

#### Bugfixes

Expand Down
11 changes: 11 additions & 0 deletions Documentation/ActivityIndicators.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ You can see an example of each animation in the demo app. Launch the app, then t
36. [HeartBeat](#heartbeat)
37. [Triforce](#triforce)
37. [Rupe](#rupe)
38. [NewtonCradle](#triforce)
39. [CirclePendulum](#circlependulum)


### AudioEqualizer

Expand Down Expand Up @@ -213,3 +216,11 @@ You can see an example of each animation in the demo app. Launch the app, then t
### Rupe

![ActivityIndicator - Rupe](https://raw.githubusercontent.com/IBAnimatable/IBAnimatable-Misc/master/IBAnimatable/ActivityIndicatorRupe.gif)

### NewtonCradle

![ActivityIndicator - NewtonCradle](https://raw.githubusercontent.com/IBAnimatable/IBAnimatable-Misc/master/IBAnimatable/ActivityIndicatorNewtonCradle.gif)

### CirclePendulum

![ActivityIndicator - CirclePendulum](https://raw.githubusercontent.com/IBAnimatable/IBAnimatable-Misc/master/IBAnimatable/ActivityIndicatorCirclePendulum.gif)
8 changes: 8 additions & 0 deletions IBAnimatable/IBAnimatable.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,15 @@
3676E7DF2059457A0003B9DF /* DesignableNavigationBar.swift in Headers */ = {isa = PBXBuildFile; fileRef = E27FD2391ECD7D8A00F74840 /* DesignableNavigationBar.swift */; settings = {ATTRIBUTES = (Public, ); }; };
48A167122063CD0D00D7BCFB /* ConicalGradientLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48A167112063CD0D00D7BCFB /* ConicalGradientLayer.swift */; };
48D80DCA20403DAC00D3137B /* AnimatableTabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 48D80DC920403DAC00D3137B /* AnimatableTabBarItem.swift */; };
C40AE6A220E6210100B24A44 /* ActivityIndicatorAnimationNewtonCradle.swift in Sources */ = {isa = PBXBuildFile; fileRef = C40AE6A120E6210100B24A44 /* ActivityIndicatorAnimationNewtonCradle.swift */; };
C414765F209C1BD700945C8B /* ActivityIndicatorAnimationTripleGear.swift in Sources */ = {isa = PBXBuildFile; fileRef = C414765E209C1BD700945C8B /* ActivityIndicatorAnimationTripleGear.swift */; };
C41A0E80209A39660069555A /* ActivityIndicatorAnimationHeartBeat.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A0E7F209A39660069555A /* ActivityIndicatorAnimationHeartBeat.swift */; };
C41A0E82209A3C1B0069555A /* ActivityIndicatorAnimationTriforce.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41A0E81209A3C1B0069555A /* ActivityIndicatorAnimationTriforce.swift */; };
C4511F371F93F1B2002E0FAF /* UIBezierPathExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4511F361F93F1B2002E0FAF /* UIBezierPathExtension.swift */; };
C456071F209BAACB002B1DF4 /* ActivityIndicatorAnimationCircleDashStrokeSpin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C456071E209BAACB002B1DF4 /* ActivityIndicatorAnimationCircleDashStrokeSpin.swift */; };
C46DC3E12099863A00501E63 /* ActivityIndicatorAnimationCircleStrokeSpin.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC3E02099863A00501E63 /* ActivityIndicatorAnimationCircleStrokeSpin.swift */; };
C46DC3E32099895E00501E63 /* ActivityIndicatorAnimationGear.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46DC3E22099895E00501E63 /* ActivityIndicatorAnimationGear.swift */; };
C489AD1E20E640B300273B77 /* ActivityIndicatorAnimationCirclePendulum.swift in Sources */ = {isa = PBXBuildFile; fileRef = C489AD1D20E640B300273B77 /* ActivityIndicatorAnimationCirclePendulum.swift */; };
C49C08DD20A05072002FCAFD /* ActivityIndicatorAnimationRupee.swift in Sources */ = {isa = PBXBuildFile; fileRef = C49C08DC20A05072002FCAFD /* ActivityIndicatorAnimationRupee.swift */; };
E20DAEB82003662900BE1C88 /* GradientMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20DAEB72003662900BE1C88 /* GradientMode.swift */; };
E20DAEBA2003682F00BE1C88 /* RadialGradientLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = E20DAEB92003682F00BE1C88 /* RadialGradientLayer.swift */; };
Expand Down Expand Up @@ -229,13 +231,15 @@
/* Begin PBXFileReference section */
48A167112063CD0D00D7BCFB /* ConicalGradientLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConicalGradientLayer.swift; sourceTree = "<group>"; };
48D80DC920403DAC00D3137B /* AnimatableTabBarItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatableTabBarItem.swift; sourceTree = "<group>"; };
C40AE6A120E6210100B24A44 /* ActivityIndicatorAnimationNewtonCradle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationNewtonCradle.swift; sourceTree = "<group>"; };
C414765E209C1BD700945C8B /* ActivityIndicatorAnimationTripleGear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationTripleGear.swift; sourceTree = "<group>"; };
C41A0E7F209A39660069555A /* ActivityIndicatorAnimationHeartBeat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationHeartBeat.swift; sourceTree = "<group>"; };
C41A0E81209A3C1B0069555A /* ActivityIndicatorAnimationTriforce.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationTriforce.swift; sourceTree = "<group>"; };
C4511F361F93F1B2002E0FAF /* UIBezierPathExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIBezierPathExtension.swift; sourceTree = "<group>"; };
C456071E209BAACB002B1DF4 /* ActivityIndicatorAnimationCircleDashStrokeSpin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationCircleDashStrokeSpin.swift; sourceTree = "<group>"; };
C46DC3E02099863A00501E63 /* ActivityIndicatorAnimationCircleStrokeSpin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationCircleStrokeSpin.swift; sourceTree = "<group>"; };
C46DC3E22099895E00501E63 /* ActivityIndicatorAnimationGear.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationGear.swift; sourceTree = "<group>"; };
C489AD1D20E640B300273B77 /* ActivityIndicatorAnimationCirclePendulum.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationCirclePendulum.swift; sourceTree = "<group>"; };
C49C08DC20A05072002FCAFD /* ActivityIndicatorAnimationRupee.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityIndicatorAnimationRupee.swift; sourceTree = "<group>"; };
E20DAEB72003662900BE1C88 /* GradientMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientMode.swift; sourceTree = "<group>"; };
E20DAEB92003682F00BE1C88 /* RadialGradientLayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadialGradientLayer.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -614,6 +618,8 @@
C41A0E81209A3C1B0069555A /* ActivityIndicatorAnimationTriforce.swift */,
C456071E209BAACB002B1DF4 /* ActivityIndicatorAnimationCircleDashStrokeSpin.swift */,
C49C08DC20A05072002FCAFD /* ActivityIndicatorAnimationRupee.swift */,
C40AE6A120E6210100B24A44 /* ActivityIndicatorAnimationNewtonCradle.swift */,
C489AD1D20E640B300273B77 /* ActivityIndicatorAnimationCirclePendulum.swift */,
);
path = Animations;
sourceTree = "<group>";
Expand Down Expand Up @@ -1093,6 +1099,7 @@
E27FD25F1ECD7D8A00F74840 /* PresentationPresenter.swift in Sources */,
C4511F371F93F1B2002E0FAF /* UIBezierPathExtension.swift in Sources */,
E27FD28F1ECD7D8A00F74840 /* TransitionType.swift in Sources */,
C40AE6A220E6210100B24A44 /* ActivityIndicatorAnimationNewtonCradle.swift in Sources */,
E27FD2CF1ECD7D8A00F74840 /* AnimatableTableViewCell.swift in Sources */,
E27FD2821ECD7D8A00F74840 /* ColorType.swift in Sources */,
E27FD2641ECD7D8A00F74840 /* PanInteractiveAnimator.swift in Sources */,
Expand Down Expand Up @@ -1204,6 +1211,7 @@
E20DAEBA2003682F00BE1C88 /* RadialGradientLayer.swift in Sources */,
E27FD2CB1ECD7D8A00F74840 /* AnimatableScrollView.swift in Sources */,
E27FD2C41ECD7D8A00F74840 /* AnimatableActivityIndicatorView.swift in Sources */,
C489AD1E20E640B300273B77 /* ActivityIndicatorAnimationCirclePendulum.swift in Sources */,
E27FD2911ECD7D8A00F74840 /* CALayerExtension.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
//
// ActivityIndicatorAnimationCirclePendulum.swift
// IBAnimatable
//
// Created by phimage on 29/06/2018.
// Copyright © 2018 IBAnimatable. All rights reserved.
//

import Foundation

import UIKit

public class ActivityIndicatorAnimationCirclePendulum: ActivityIndicatorAnimating {

// MARK: Properties

fileprivate let duration: CFTimeInterval = 1
fileprivate let ratio: CGFloat = 7
fileprivate let ballCount: Int = 3

#if LG
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What means LG?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Life's Good
This activity indicator has been inspired from my new tv, to watch the world cup

This override the color defined and set 3 colors.
Maybe LG could be used accidentally, If I could keep my eastern eggs, a longer compilation flag could be used

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we have a property instead of a custom flag? Otherwise, the user needs to set the configuration in Podfile to enable it like build_settings['OTHER_SWIFT_FLAGS'] = '-LG'. If you think that's fun, shall we add it to ActivityIndicators.md . BTW: I have LG big TV at home too 📺

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityIndicatorAnimation classes are not very accessible, but I could make trueColor as a public var
The compilation will change only the default value.

Yes I will add in doc

ps: same trick on triforce

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@phimage cool, thanks, LG(Life's Good), you make it even LB (Life's Better)😉

fileprivate let trueColor = true
#else
fileprivate let trueColor = false
#endif
fileprivate let colors: [UIColor] = [.red, .blue, .yellow]

fileprivate var ballSize: CGFloat = 0

// MARK: ActivityIndicatorAnimating

public func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) {
ballSize = size.height / ratio
var xPos = (size.width - (CGFloat(ballCount) * ballSize)) / 2
let yPos = size.height - ballSize / 2

for i in 0 ..< ballCount {
let ball = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: ballSize, height: ballSize), color: trueColor ? colors[i]: color)
ball.frame = CGRect(x: xPos, y: yPos, width: ballSize, height: ballSize)
ball.add(makeAnimation(for: ball, in: layer, size: size, pos: i), forKey: "animation")
layer.addSublayer(ball)
xPos += ballSize
}
}
}

private extension ActivityIndicatorAnimationCirclePendulum {

func makeAnimation(for ball: CALayer, in layer: CALayer, size: CGSize, pos: Int) -> CAAnimation {
let angle = 2 * atan(ballSize / (size.height - ballSize / 2))

var animations: [CAKeyframeAnimation] = [] // only 3 balls, code not generic yet, how to compute angles?

let rotateAnimation = CAKeyframeAnimation(keyPath: .position)
var animPos = 0
rotateAnimation.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
radius: size.height / 2,
startAngle: CGFloat.pi / 2 + angle,
endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle,
clockwise: true).cgPath
rotateAnimation.duration = duration
rotateAnimation.timingFunctionType = .easeInOut
rotateAnimation.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
animations.append(rotateAnimation)

let rotateAnimation2 = CAKeyframeAnimation(keyPath: .position)
animPos += 1
rotateAnimation2.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
radius: size.height / 2,
startAngle: 2 * CGFloat.pi + CGFloat.pi / 2 - angle,
endAngle: 2 * CGFloat.pi + CGFloat.pi / 2,
clockwise: true).cgPath
rotateAnimation2.duration = duration
rotateAnimation2.timingFunctionType = .easeOutExpo
rotateAnimation2.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
animations.append(rotateAnimation2)

let rotateAnimation3 = CAKeyframeAnimation(keyPath: .position)
animPos += 1
rotateAnimation3.path = UIBezierPath(arcCenter: CGPoint(x: size.width / 2, y: size.height / 2),
radius: size.height / 2,
startAngle: 2 * CGFloat.pi + CGFloat.pi / 2,
endAngle: 2 * CGFloat.pi + CGFloat.pi / 2 + angle,
clockwise: true).cgPath
rotateAnimation3.duration = duration
rotateAnimation3.timingFunctionType = .easeOutExpo
rotateAnimation3.beginTime = duration * CFTimeInterval((pos + animPos) % ballCount)
animations.append(rotateAnimation3)

let animation = CAAnimationGroup()
animation.animations = animations
animation.duration = duration * CFTimeInterval(ballCount)
animation.repeatCount = .infinity
animation.isRemovedOnCompletion = false
return animation
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ActivityIndicatorAnimationHeartBeat: ActivityIndicatorAnimating {

// MARK: Properties

fileprivate let duration: CFTimeInterval = 1
fileprivate let duration: CFTimeInterval = 0.8

// MARK: ActivityIndicatorAnimating

Expand All @@ -33,12 +33,12 @@ private extension ActivityIndicatorAnimationHeartBeat {

var defaultAnimation: CABasicAnimation {
let scaleAnimation = CABasicAnimation(keyPath: .scale)
scaleAnimation.timingFunctionType = .easeIn
scaleAnimation.timingFunctionType = .easeOutBack
scaleAnimation.duration = duration
scaleAnimation.repeatCount = .infinity
scaleAnimation.autoreverses = true
scaleAnimation.fromValue = 1
scaleAnimation.toValue = 0.7
scaleAnimation.toValue = 0.8
scaleAnimation.isRemovedOnCompletion = false
return scaleAnimation
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//
// ActivityIndicatorAnimationNewtonCradle.swift
// IBAnimatable
//
// Created by phimage on 29/06/2018.
// Copyright © 2018 IBAnimatable. All rights reserved.
//

import UIKit

public class ActivityIndicatorAnimationNewtonCradle: ActivityIndicatorAnimating {

// MARK: Properties

fileprivate let duration: CFTimeInterval = 0.4
fileprivate let ratio: CGFloat = 7
fileprivate let ballCount: Int = 5
fileprivate let timingFunction: TimingFunctionType = .easeOutQuad

fileprivate var ballSize: CGFloat = 0

// MARK: ActivityIndicatorAnimating

public func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) {
ballSize = size.height / ratio
var xPos = (size.width - (CGFloat(ballCount) * ballSize)) / 2
let yPos = size.height - ballSize / 2

var balls: [CALayer] = []
for _ in 0 ..< ballCount {
let ball = ActivityIndicatorShape.circle.makeLayer(size: CGSize(width: ballSize, height: ballSize), color: color)
ball.frame = CGRect(x: xPos, y: yPos, width: ballSize, height: ballSize)
balls.append(ball)
xPos += ballSize
}

let firstBall = balls.removeFirst()
firstBall.add(makeAnimation(for: firstBall, in: layer, size: size, reverse: true), forKey: "animation")
layer.addSublayer(firstBall)

let lastBall = balls.removeLast()
lastBall.add(makeAnimation(for: lastBall, in: layer, size: size, reverse: false), forKey: "animation")
layer.addSublayer(lastBall)

for ball in balls {
layer.addSublayer(ball)
}
}
}

private extension ActivityIndicatorAnimationNewtonCradle {

func makeAnimation(for ball: CALayer, in layer: CALayer, size: CGSize, reverse: Bool) -> CAAnimation {
let rotateAnimation = CAKeyframeAnimation(keyPath: .position)
rotateAnimation.path = UIBezierPath(arcCenter: CGPoint(x: ball.frame.origin.x + ballSize / 2, y: size.height / 2),
radius: size.height / 2,
startAngle: CGFloat.pi / 2,
endAngle: reverse ? CGFloat.pi: 0,
clockwise: reverse).cgPath
rotateAnimation.duration = duration
rotateAnimation.autoreverses = true
rotateAnimation.timingFunctionType = timingFunction

if reverse {
rotateAnimation.beginTime = duration * 2
}

let animation = CAAnimationGroup()
animation.animations = [rotateAnimation]
animation.duration = duration * 4
animation.repeatCount = .infinity
animation.isRemovedOnCompletion = false
return animation
}
}
17 changes: 16 additions & 1 deletion Sources/ActivityIndicators/Common/ActivityIndicatorFactory.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,18 @@ import UIKit

public struct ActivityIndicatorFactory {
public static func makeActivityIndicator(activityIndicatorType: ActivityIndicatorType) -> ActivityIndicatorAnimating {
switch activityIndicatorType {
return activityIndicatorType.animator
}
}

extension ActivityIndicatorType {

func configureAnimation(in layer: CALayer, size: CGSize, color: UIColor) {
self.animator.configureAnimation(in: layer, size: size, color: color)
}

var animator: ActivityIndicatorAnimating {
switch self {
case .none:
fatalError("Invalid ActivityIndicatorAnimating")
case .audioEqualizer:
Expand Down Expand Up @@ -86,6 +97,10 @@ public struct ActivityIndicatorFactory {
return ActivityIndicatorAnimationTriforce()
case .rupee:
return ActivityIndicatorAnimationRupee()
case .newtonCradle:
return ActivityIndicatorAnimationNewtonCradle()
case .circlePendulum:
return ActivityIndicatorAnimationCirclePendulum()
}
}
}
2 changes: 2 additions & 0 deletions Sources/Enums/ActivityIndicatorType.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,6 @@ public enum ActivityIndicatorType: String, IBEnum {
case heartBeat
case triforce
case rupee
case newtonCradle
case circlePendulum
}
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ private extension ActivityIndicatorAnimatable where Self: UIView {
return
}

let activityIndicator = ActivityIndicatorFactory.makeActivityIndicator(activityIndicatorType: animationType)
activityIndicator.configureAnimation(in: layer, size: bounds.size, color: color)
animationType.configureAnimation(in: layer, size: bounds.size, color: color)
layer.speed = 1
}

Expand Down