Features | Roadmap | Installing | How To Use | Contributing | Changelog | License
Revolutionary was built due to a personal need - in essence, the intuit was to create a circle that would behave like a countdown and a stopwatch, but on watchOS
. One of the "problems" is that we don't have Core Animation
, so we may eventually try using a bunch of images (and call it on WKInterfaceImage in our assets folder), which is completely fine - if the animation is not complex -, but if you want something more detailed (more fluid without a ton of assets) and "controllable", you will probably end ask for help to our beloved SpriteKit
and its SKAction
s.
With all of this in mind, an API was created to manage a SKNode
, which basically control the UI behavior and do the necessary callbacks.
Relevant info.: Because the same behavior was needed in both iOS
and tvOS
, "class helpers" were created to be instantiated directly - a SKView
and/or a SKScene
- so we can manipulate the Revolutionary SKNode
without other SpriteKit
UI elements creating "noise" over the instantiation of our main SKNode
- the Revolutionary.swift
. These helpers make this framework works seamlessly on any platform.
- iOS support
- Fully customizable UI properties of the drawn arcs
- Manage a Progress behavior
- Manage a Stopwatch/Countdown behavior
Features implemented/planned for 2019 (in order of priority):
- Framework for iOS
- Examples for iOS
- Support Carthage
- Support CocoaPods
- Add TravisCI
- Repository description + how to use
- Implement Textures on the
SKNode
s - iOS Showcases + Improved iOS Examples
- Add Swiftlint
- Add Jazzy Docs
- Support watchOS
- Examples for watchOS
- Expose more properties to ease the customization of the
Revolutionary.swift
+ improve the iOS examples with it - Find a better way to replicate the commentaries on the Builder (not just copy pasting the docs from
Revolutionary.swift
) - Add Tests (+ support with some check tool, like Coveralls)
- Support/Examples/Showcases for watchOS
- Support/Examples/Showcases for macOS
- Support/Examples/Showcases for tvOS
- Carthage: add
github "matuella/Revolutionary" ~> 0.3.0
to yourCartfile
; - CocoaPods: add
pod 'Revolutionary'
to yourPodfile
; - Manual: copy all the files in the Revolutionary folder to your project and you're good to go.
Because this framework is UI-heavy, it uses a Builder pattern - required in the classes init
-, so you can explicitly set the desired parameters with a much more clear and concise syntax.
Example of creating a RevolutionaryBuilder
:
let revolutionaryBuilder = RevolutionaryBuilder { builder in
//Customize properties here
//I.E.:
builder.mainArcColor = .coolPurple
builder.mainArcWidth = 10
builder.backgroundArcWidth = 10
builder.displayStyle = .percentage(decimalPlaces: 2)
}
Using Interface Builder:
`@IBOutlet private weak var myWrapperView: UIView!`
`private var revolutionary: Revolutionary!`
private func viewDidLoad() {
super.viewDidLoad()
let myBuilder = RevolutionaryBuilder { builder in
builder.mainArcColor = .black
}
let revolutionaryView = RevolutionaryView(revolutionaryBuilder, frame: myWrapperView.bounds)
//or by calling a default init with its default properties
//let revolutionaryView = RevolutionaryView(frame: myWrapperView.bounds)
//glueing the revolutionary view to my wrapper view
revolutionaryView.translatesAutoresizingMaskIntoConstraints = false
revolutionaryViewWrapper.addSubview(revolutionaryView)
revolutionaryView.leadingAnchor.constraint(equalTo: revolutionaryViewWrapper.leadingAnchor).isActive = true
revolutionaryView.trailingAnchor.constraint(equalTo: revolutionaryViewWrapper.trailingAnchor).isActive = true
revolutionaryView.topAnchor.constraint(equalTo: revolutionaryViewWrapper.topAnchor).isActive = true
revolutionaryView.bottomAnchor.constraint(equalTo: revolutionaryViewWrapper.bottomAnchor).isActive = true
//Because Revolutionary is a SKNode, we must stay with its reference to manipulate its state
revolutionary = revolutionaryView.rev
//If you don't want to create a custom `SKLabel` on the builder, just customize the default one after instantiation. I.e:
revolutionary.displayLabel.fontColor = .purple
//If you don't want to use the builder, just instante the RevolutionaryView with default values (just passing the frame)
//and set the same properties that you would've passed in the RevolutionaryBuilder
revolutionary.mainArcColor = .cyan
}
Using Programmatically-created UI:
To exemplify, we will center the Revolutionary
in the middle of the screen:
private var revolutionary: Revolutionary!
private func viewDidLoad() {
super.viewDidLoad()
let myBuilder = RevolutionaryBuilder { builder in
builder.mainArcColor = .black
}
let revolutionaryViewFrame = CGRect(x: 0, y: 0, width: 200, height: 200)
let revolutionaryView = RevolutionaryView(revolutionaryBuilder, frame: revolutionaryViewFrame)
//or by calling a default init with its default properties
//let revolutionaryView = RevolutionaryView(frame: revolutionaryViewFrame)
let centeredView = UIView()
centeredView.backgroundColor = UIColor.lightGray.withAlphaComponent(0.5)
centeredView.translatesAutoresizingMaskIntoConstraints = false
centeredView.addSubview(revView)
revolutionaryView.topAnchor.constraint(equalTo: centeredView.topAnchor).isActive = true
revolutionaryView.bottomAnchor.constraint(equalTo: centeredView.bottomAnchor).isActive = true
revolutionaryView.leadingAnchor.constraint(equalTo: centeredView.leadingAnchor).isActive = true
revolutionaryView.trailingAnchor.constraint(equalTo: centeredView.trailingAnchor).isActive = true
view.addSubview(centeredView)
centeredView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
centeredView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//Because Revolutionary is a SKNode, we must stay with its reference to manipulate its state
revolutionary = revolutionaryView.rev
//If you don't want to create a custom `SKLabel` on the builder, just customize the default one after instantiation. I.e:
revolutionary.displayLabel.fontColor = .purple
//If you don't want to use the builder, just instante the RevolutionaryView with default values (just passing the frame)
//and set the same properties that you would've passed in the RevolutionaryBuilder
revolutionary.mainArcColor = .cyan
}
Alternatively - instantiating the SpriteKit
classes directly:
If you intend to use the SpriteKit
classes directly (like the Revolutionary
which is a SKNode
, or the RevolutionaryScene
which is a SKScene
):
RevolutionaryScene
- SKScene
:
let revolutionarySize = CGSize(width: 100, height: 100)
let myRevolutionaryScene = RevolutionaryScene(size: revolutionarySize)
//or with builder
//let myBuilder = RevolutionaryBuilder { builder in
// builder.mainArcColor = .black
//}
//let myRevolutionaryScene = RevolutionaryScene(myBuilder, size: revolutionarySize)
let revolutionary = myRevolutionaryScene.rev
Revolutionary
- SKNode
:
let myRevolutionary = Revolutionary(withRadius: 50)
//or with builder
//let myBuilder = RevolutionaryBuilder { builder in
// builder.mainArcColor = .black
//}
//let myRevolutionary = Revolutionary(withRadius: 50, builder: myBuilder)
let revolutionary = myRevolutionaryScene.rev
IMPORTANT: As you can see, the init
of both RevolutionaryView
and RevolutionaryScene
requires a padding: CGFloat
, which defaults to 16
, but this is basically the padding in which the Revolutionary
will draw its circle. This is needed because the UIBezierPath
which will draw the arcs may get out of the SKScene
.
To clarify, lets say you need a circle of radius = 100
. If you set the padding = 8
, you'll need a frame of 116
of height/width, because the padding will be 8 points in each "side".
Now that we have a reference to our Revolutionary
node, we can call the necessary functions, given our use-case.
Progress usage:
Used when you need to manage the arc state, like a download progress, a completion ratio of some arbitrary in-game progress, a progress of a onboarding, etc.
let progressAnimationDuration = 3.5
//Animating the new progress - in terms of 0-100% - to 50%.
//Important to notice that 0%/0 degress means `CGFloat = 0` and 100%/360 degrees means `CGFloat = 1`
let newProgress: CGFloat = 0.5
revolutionary.run(toProgress: newProgress, withDuration: progressAnimationDuration) {
print("Completed Progress in")
}
Countdown usage:
There's basically two modes when running Countdown: indefinite and definite. This means to pick if you want the animation to keep going until stopped (indefinite) or using predetermined duration/amounts of revolutions.
Definite countdown:
//This is in seconds. Meaning half of a second for each revolution in this case
let countdownDuration = 0.5
//Total revolution times
let totalRevolutions = 5
revolutionary.runCountdown(withRevolutionDuration: countdownDuration, amountOfRevolutions: revolutionsAmount) {
print("The countdown finished in \(countdownDuration * Double(totalRevolutions))")
}
Indefinite countdown:
//This is in seconds. Meaning half of a second for each revolution in this case
let countdownDuration = 0.5
revolutionary.runCountdownIndefinitely(withRevolutionDuration: countdownDuration)
Stopwatch usage:
Just like the Countdown, the Stopwatch use the same indefinite/definite separation.
Definite stopwatch:
//This is in seconds. Meaning half of a second for each revolution in this case
let stopwatchDuration = 0.5
//Total revolution times
let totalRevolutions = 5
revolutionary.runStopwatch(withRevolutionDuration: stopwatchDuration, amountOfRevolutions: revolutionsAmount) {
print("The stopwatch finished in \(stopwatchDuration * Double(totalRevolutions))")
}
Indefinite stopwatch:
//This is in seconds. Meaning half of a second for each revolution in this case
let countdownDuration = 0.5
revolutionary.runStopwatchIndefinitely(withRevolutionDuration: countdownDuration)
Managing the state:
Resetting:
//Completed in this case, means if it should reset to the full arc (360 degrees) / `true` or no arc (0 degress) / `false`
revolutionary.reset(completed: true)
Pausing:
revolutionary.pause()
Resuming:
revolutionary.resume()
If you have any suggestion, issue or idea, please contribute with what you've in your mind. Also, read CONTRIBUTING.
The version history and meaningful changes will all be available in the CHANGELOG.
Revolutionary is licensed under MIT - LICENSE.