A tactile, momentum-based radial control for SwiftUI β inspired by analog flywheels.
A SwiftUI-based, physics-inspired radial scroller for zooming, scrubbing, and precision value adjustments.
FlywheelControl mimics the feel of a real-world dial β complete with momentum, resistance, haptic feedback, and pen-friendly input.
We needed a more natural way to zoom β something better than pinch and expand.
So we built a rotary-style control that works with a finger or stylus, and feels real thanks to physics and CoreHaptics.
- ποΈ Inertial spinning like a physical dial
- π± One-finger- and Apple Pencil-friendly
- π₯ Haptic ticks for tactile feedback
- π¨ Fully SwiftUI and easy to customize
- π§ Binding-driven: tracks a zoom
position
with clamped min/max offsets and live span adjustments - π Smooth momentum decay and natural stopping behavior
- π¨ Custom skin support: Apply your own ruler artwork for a fully branded experience
- π Clamp limits: minValue and maxValue must be > 0
- π spanCM defines visible range (must be > 0)
In Xcode:
- Go to
File β Add Packagesβ¦
- Enter the URL:
https://github.com/aweiner42/FlywheelControl
- Choose the latest version (e.g.,
1.1.0
)
Or add it to your Package.swift
:
.package(url: "https://github.com/aweiner42/FlywheelControl.git", from: "1.1.0")
Then add the dependency to your target:
.target(
name: "YourApp",
dependencies: ["FlywheelControl"]
)
Import the module where needed:
import FlywheelControl
Place your ruler artwork (e.g., RulerSkin.png
) in your appβs Asset Catalog (Assets.xcassets
) and assign it the name RulerSkin
. FlywheelControl will automatically load this image if present.
The package includes a sample ruler skin (DefaultRuler.png
). You can copy it into your appβs Asset Catalog and rename it RulerSkin
for immediate use.
@State private var zoomPosition: Double = 0
@State private var minZoom: Double = 1
@State private var maxZoom: Double = 90
@State private var zoomSpan: Double = 20
var body: some View {
FlywheelControl(
trackImage: Image("RulerSkin"), // custom ruler skin from Assets
position: $zoomPosition, // current scroll/zoom value
maxOffset: $maxZoom, // maximum scroll value (must be > 0)
minOffset: $minZoom, // minimum scroll value (must be > 0)
spanCM: $zoomSpan // visible range in cm (must be > 0)
)
.onChange(of: zoomPosition) { newValue in
zoomManager.adjustZoom(by: CGFloat(newValue))
}
}
- iOS 17.0+
- macOS 12.0+
- Swift 5.9+
- SwiftUI + Combine
FlywheelControl includes:
- π SwiftUI Previews
- π§± Modular design (no app dependencies)
Clone this repo and open FlywheelDemoApp/FlywheelDemoApp.xcodeproj
to explore the latest refinements including improved momentum physics and control skinning.
Alan Weiner β’ SIME Corp
Inventor. Designer. Engineer. Collaborating with AI to shape intuitive interfaces.
Version 1.1.0 includes performance tuning, refined gesture handling, and support for custom ruler skins with clamped value ranges.