Skip to content

ThasianX/ElegantColorPalette

Repository files navigation

ElegantColorPalette

platforms pod Carthage compatible Swift Package Manager compatible License: MIT

The elegant color picker missed in UIKit and SwiftUI.

These GIFs are from ElegantTimeline. For simpler demonstrations, you can look at either of the 3 demo projects in this repository.

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.

# 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

Add this to Cartfile

github "ThasianX/ElegantColorPalette" ~> 1.2.0
$ carthage update

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