Skip to content

CrownControl is a tiny accessory that makes scrolling through scrollable content possible without lifting your thumb.

License

Notifications You must be signed in to change notification settings

huri000/CrownControl

Repository files navigation

CrownControl

Platform Language Version codecov License

Overview

Inspired by Apple Watch Digital Crown, CrownControl is a tiny accessory view that makes scrolling through scrollable content possible without lifting your thumb.

Features

The crown consists of background and foreground surfaces. The foreground is an indicator which spins around the center as the attached scroll view offset changes.

  • Can be repositioned either using force touch or long press.
  • Can be spinned clockwise and counter clockwise.
  • Most of the user interaction actions are configurable.
  • The background and foreground sizes are configurable.
  • Can be fully stylized.

Example Project

The example project contains samples where each demonstrates the CrownControl usability in a scrollable context.

Web View / PDF Contacts Photo Collection
pdf_example contacts_example photos_example

Requirements

  • iOS 9 or any higher version.
  • Swift 4.2 or any higher version.
  • CrownControl leans heavily on QuickLayout - A lightwight library written in Swift that is used to easily layout views programmatically.

Installation

CocoaPods

CocoaPods is a dependency manager for Cocoa projects. You can install it with the following command:

$ gem install cocoapods

To integrate CrownControl into your Xcode project using CocoaPods, specify it in your Podfile:

source 'https://github.com/cocoapods/specs.git'
platform :ios, '9.0'
use_frameworks!

pod 'CrownControl', '1.0.0'

Then, run the following command:

$ pod install

Usage

Quick Usage

Using CrownControl is really simple.

In your view:

  1. Define and bind CrownAttributes to a scroll view instance, and optionally customize the attributes.
  2. Instantiate and bind the CrownControl instance to the CrownAttributes instance.
  3. Setup the CrownControl instance in a given superview using constraints to determine its position.
private var crownControl: CrownControl!
private var scrollView: UIScrollView!

private func setupCrownViewController() {
    let attributes = CrownAttributes(scrollView: scrollView, scrollAxis: .vertical)
    
    // Cling the bottom of the crown to the bottom of a view with -50 offset
    let verticalConstraint = CrownAttributes.AxisConstraint(crownEdge: .bottom, anchorView: scrollView, anchorViewEdge: .bottom, offset: -50)
    
    // Cling the trailing edge of the crown to the trailing edge of a view with -50 offset
    let horizontalConstraint = CrownAttributes.AxisConstraint(crownEdge: .trailing, anchorView: scrollView, anchorViewEdge: .trailing, offset: -50)

    // Setup the crown control within *self*
    crownControl = CrownControl(attributes: attributes, delegate: self)
    crownControl.layout(in: view, horizontalConstaint: horizontalConstraint, verticalConstraint: verticalConstraint)
}

To make the crown respond to scrolling events that emanates from any other invoker but the crown, add to scrollViewDidScroll(_:) the following:

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    crownControl?.spinToMatchScrollViewOffset()
}

Crown Attributes

CrownAttributes is the crown appearance descriptor. Its nested properties describe the look and feel of the crown.

Scroll Axis

The axis of the scroll view is a .horizontal or .vertical. It must be set during CrownAttributes initialization.

Anchor Position

The anchor position of the foreground indicator. Indicates where the foreground is initially positioned.

attributes.anchorPosition = .left

The posssible values are .left, .right, .top, .bottom. The default value is .top.

Spin Direction

The direction to which the the indicator spins.

Example for setting the spin direction to be counter-clockwise.

attributes.spinDirection = .counterClockwise

The default value is clockwise.

User Interaction

Describes the user interaction with the crown. Currently supported user interaction gestures: tap, double tap, long-press, and force-touch events.

Tap Gestures

When a single tap event occurs, scroll forward with the specified offset value:

attributes.userInteraction.singleTap = .scrollsForwardWithOffset(value: 20, animated: true)

When a single tap event occurs, perform a custom action.

attributes.userInteraction.singleTap = .custom(action: {
    /* Do something */
})

When a double tap event occurs, scroll to the leading edge of the scroll view.

attributes.userInteraction.doubleTap = .scrollsToLeadingEdge
Drag and Drop

The crown can be dragged and dropped using force-touch if the force-touch trait is supported by the device hardware. If not, there is a fallback to long-press gesture.

attributes.repositionGesture = .prefersForceTouch(attributes: .init())

Style

The background and foreground surfaces can be customized with various styles.

Example for setting the crown background to a gradient style, and its border to a specific color and width.

attributes.backgroundStyle.content = .gradient(gradient: .init(colors: [.white, .gray], startPoint: .zero, endPoint: CGPoint(x: 1, y: 1)))
attributes.backgroundStyle.border = .value(color: .gray, width: 1)

Sizes

Describes the size of the crown and relations between the foreground and the background surfaces.

In the following example, setting scrollRelation property to 10 means that 10 full spins of the foreground would make the scroll view offset reach its trailing edge.

attributes.sizes.scrollRelation = 10

Example for setting the edge size (width and height) of the crown to 60pts, and the foreground edge ratio to 25 precent of that size, which is 15pts.

attributes.sizes.backgroundSurfaceDiameter = 60
attributes.sizes.foregroundSurfaceEdgeRatio = 0.25

Feedback

Feedback descriptor for the foreground when it reaches the anchor point on the crown surface.

The device generates impact-haptic-feedback when the scroll-view offset reaches the leading edge. The background surface of the crown flashes with color.

attributes.feedback.leading.impactHaptic = .light
attributes.feedback.leading.backgroundFlash = .active(color: .white, fadeDuration: 0.3)

Author

Daniel Huri, huri000@gmail.com

License

CrownControl is available under the MIT license. See the LICENSE file for more info.