ElegantColorPalette
The elegant color picker missed in UIKit and SwiftUI.
This example GIF is from ElegantTimeline. For a simpler demonstration, you can look at either of the 3 demo projects in this repository. And haha, that 2nd gif just me trying to tap as many nodes as fast as possible. And as you can see, there's no side effects.
Introduction
ElegantColorPalette
comes with 24 different themes and is inspired by TimePage and is part of a larger repository of elegant demonstrations like this: TimePage Clone.
The top level view is an SKView
that presents an SKScene
of colors nodes. The color nodes are SKShapeNode
subclasses. For more experienced developers that are or want to learn SpriteKit, the SKScene
that contains the color nodes is exposed for greater fine tuning on your end.
Features
- Dynamic color nodes - passing in a dynamic
UIColor
will allow the color node to properly adjust to light or dark mode - Extensive node customizability - font name, font color, node color, node radius, and much more
- Custom node animations - snapping, scaling, fading, highlighting, and much more
- Updating the color palette with new colors
- Portait and landscape support
- Extensively documentation - as you use the library, you'll notice documentation in XCode
Basic usage
For SwiftUI:
import ElegantColorPalette
struct ExampleSwiftUIView: View {
@State private var selectedColor: PaletteColor = .kiwiGreen
var body: some View {
ColorPaletteBindingView(selectedColor: $selectedColor, colors: PaletteColor.allColors)
}
}
For UIKit(programmatically):
import ElegantColorPalette
struct ExampleUIKitViewController: UIViewController {
...
private lazy var paletteView: ColorPaletteView = {
let paletteView = ColorPaletteView(colors: PaletteColor.allColors)
paletteView.translatesAutoresizingMaskIntoConstraints = false
return paletteView
}()
override func viewDidLoad() {
super.viewDidLoad()
...
view.addSubview(paletteView)
NSLayoutConstraint.activate([
...
])
paletteView
.didSelectColor { [unowned self] color in
...
}
}
}
For UIKit(storyboard and XIB):
import ElegantColorPalette
struct ExampleUIKitViewController: UIViewController {
...
@IBOutlet weak var paletteView: ColorPaletteView!
override func viewDidLoad() {
super.viewDidLoad()
...
paletteView
.update(withColors: PaletteColor.allColors)
.didSelectColor { [unowned self] color in
...
}
}
}
Customization
PaletteColor
PaletteColor represents the color model of a single node.
public struct PaletteColor: Equatable {
public let name: String
public let uiColor: UIColor
public var color: Color {
uiColor.asColor
}
}
As you aren't able to get a UIColor
from a Color
in iOS 13, you must initialize a PaletteColor
using a UIColor
. If you are supporting light and dark color themes, you will want to pass a dynamic UIColor
either through the asset bundle or other methods like a computed property.
For SwiftUI users, PaletteColor
exposes a color
property for your convenience.
Node Customization
Nodes are easily customizable through a number of node-modifier functions exposed the library itself. You can also make your own node-modifiers through NodeStyle
and NodeModifier
, like you would do in SwiftUI with ButtonStyle
and ViewModifier
.
See NodeConfiguration to better understand the various states of a node to determine when best to apply your modifier.
The following example demonstrates making a custom node style that further customizes the default node style. You must apply your custom modifiers to defaultStyledNode
in order to retain the snap effect, highlighted border, etc you see in the demo gif. However, you are always free to start from scratch and style your own node for different states.
struct CustomNodeStyle: NodeStyle {
func updateNode(configuration: Configuration) -> ColorNode {
configuration.defaultStyledNode
.radius(30)
.font(name: "Thonburi")
}
}
Creating your own node modifiers requires prerequisite knowledge of SKNode
. Let's take a look at the ScaleFadeModifier
. This modifier is responsible for scaling and fading the the node through SKActions
.
struct ScaleFadeModifier: NodeModifier {
let scale: CGFloat
let opacity: CGFloat
let animationDuration: TimeInterval
func body(content: Content) -> ColorNode {
let scaleAction = SKAction.scale(to: scale, duration: animationDuration)
let opacityAction = SKAction.fadeAlpha(to: opacity, duration: animationDuration)
content.run(.group([scaleAction, opacityAction]))
return content
}
}
To use your custom NodeStyle
, pass it into the nodeStyle
modifier:
// For UIKit
ColorPaletteView(...)
.nodeStyle(CustomNodeStyle())
// For SwiftUI
ColorPaletteBindingView(...)
.nodeStyle(CustomNodeStyle())
Focus Customization
Use focus
to customize the location, speed, or smoothing rate of the focus animation.
// For UIKit
ColorPaletteView(...)
.focus(location: .zero, speed: 1600, rate: 0.2)
// For SwiftUI
ColorPaletteBindingView(...)
.focus(location: .zero, speed: 1600, rate: 0.2)
Use canMoveFocusedNode
to customize whether the focused node is movable or not.
// For UIKit
ColorPaletteView(...)
.canMoveFocusedNode(false)
// For SwiftUI
ColorPaletteBindingView(...)
.canMoveFocusedNode(false)
For SwiftUI, you can also customize the binding animation that occurs when a new palette color is selected.
ColorPaletteBindingView(...)
.bindingAnimation(.easeInOut)
Scene Customization
Like mentioned in the introduction, the SKScene
that drives the view is exposed through a property called paletteScene
. If you are experienced with SpriteKit, you may tamper with the scene for greater flexibility.
Use spawnConfiguration
to customize the allowable area of where nodes can spawn:
// For UIKit
ColorPaletteView(...)
.spawnConfiguration(widthRatio: 1, heightRatio: 1)
// For SwiftUI
ColorPaletteBindingView(...)
.spawnConfiguration(widthRatio: 1, heightRatio: 1)
Use rotation
to customize how fast you want the nodes to rotate around your focus location:
// For UIKit
ColorPaletteView(...)
.rotation(multiplier: 4)
// For SwiftUI
ColorPaletteBindingView(...)
.rotation(multiplier: 4)
Events
Use didSelectColor
to react to any change of the selected palette color:
// For UIKit
ColorPaletteView(...)
.didSelectColor { paletteColor in
// do something
}
// For SwiftUI
ColorPaletteBindingView(...)
.didSelectColor { paletteColor in
// do something
}
Demos
There are 3 different demos, covering UIKit storyboards, XIBs and programmatic instantiation, and SwiftUI.
Requirements
- iOS 13+(Statistics show that 90% of users are on iOS 13)
- Xcode 11+
- Swift 5.1+
Installation
ElegantColorPalette
doesn't contain any external dependencies.
These are currently the supported installation options:
Manual
Inside Sources
, drag the ElegantColorPalette
folder into your project.
CocoaPods
# Podfile
use_frameworks!
target 'YOUR_TARGET_NAME' do
pod 'ElegantColorPalette', '~> 1.2'
end
Replace YOUR_TARGET_NAME
and then, in the Podfile
directory, type:
$ pod install
Carthage
Add this to Cartfile
github "ThasianX/ElegantColorPalette" ~> 1.2.0
$ carthage update
Swift Package Manager
Using Xcode 11, go to File -> Swift Packages -> Add Package Dependency
and enter https://github.com/ThasianX/ElegantColorPalette
If you are using Package.swift
, you can also add ElegantColorPalette
as a dependency easily.
// swift-tools-version:5.1
import PackageDescription
let package = Package(
name: "TestProject",
dependencies: [
.package(url: "https://github.com/ThasianX/ElegantColorPalette", from: "1.2.0")
],
targets: [
.target(name: "TestProject", dependencies: ["ElegantColorPalette"])
]
)
$ swift build
Contributing
If you find a bug, or would like to suggest a new feature or enhancement, it'd be nice if you could search the issue tracker first; while we don't mind duplicates, keeping issues unique helps us save time and considates effort. If you can't find your issue, feel free to file a new one.
License
This project is licensed under the MIT License - see the LICENSE file for details