Skip to content

Instantly share code, notes, and snippets.

@karlis
Created February 16, 2021 13:58
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save karlis/1f90c297568844ca1d6f9222aba95dc6 to your computer and use it in GitHub Desktop.
Save karlis/1f90c297568844ca1d6f9222aba95dc6 to your computer and use it in GitHub Desktop.
Work in progress. Example SwiftUI gesture. One finger rotation around center.
//
// Example.swift
// Vinyl
//
// Created by Karlis Lukstins on 16/02/2021.
//
import SwiftUI
// MARK: - Example
struct ExampleView: View {
@ObservedObject
var gestureModel: GestureModel
let gesture: KLRotationGesture
init() {
gesture = KLRotationGesture(centre: CGPoint(x: 150, y: 150))
gestureModel = gesture.model
}
var body: some View {
ZStack {
Color.black
circle
.rotationEffect(gestureModel.angle)
.gesture(gesture)
}
.edgesIgnoringSafeArea(.all)
}
var circle: some View {
Color.white
.frame(width: 300, height: 300)
.clipShape(Circle())
.overlay(Circle().frame(width: 8, height: 8).foregroundColor(.black))
.overlay(Color.red.frame(width: 5, height: 25), alignment: .top)
}
}
// MARK: - Gesture
class GestureModel: ObservableObject {
@Published
var angle: Angle = .zero
var previous: Angle? = nil
}
struct KLRotationGesture: Gesture {
var model = GestureModel()
var centre: CGPoint
var body: some Gesture {
DragGesture(coordinateSpace: .local)
.onChanged { value in
let angle = Geometry.angle(point: value.location, centre: centre)
if let previous = model.previous {
let diff = Geometry.diff(angle: angle, previous: previous.degrees)
withAnimation(.easeOut) {
model.angle = Angle(degrees: model.angle.degrees + diff)
}
}
model.previous = angle
}
.onEnded { value in
let angle = Geometry.angle(point: value.location, centre: centre)
// Move around circle at current radius with predicted velocity.
let currentRadius = Geometry.pythagor(
a: value.location.x - centre.x,
b: value.location.y - centre.y
)
let circumference = 2 * currentRadius * .pi
let predictedMove = Geometry.pythagor(
a: value.predictedEndLocation.x - value.location.x,
b: value.predictedEndLocation.y - value.location.y
)
var degrees = (predictedMove / circumference) * 360
// Check drag direction
if
let previous = model.previous,
Geometry.diff(angle: angle, previous: previous.degrees) < 0 {
degrees *= -1
}
withAnimation(.easeOut) {
model.angle = Angle(degrees: model.angle.degrees + Double(degrees))
}
model.previous = nil
}
}
}
// MARK: - Helper functions
struct Geometry {
/// Get location on a circle with _radius_ from given _center_ and _angle_.
static func location(radius: CGFloat, center: CGPoint, angle: Angle) -> CGPoint {
let x = center.x + radius * CGFloat(sin(angle.radians))
let y = center.y + radius * CGFloat(cos(angle.radians))
return CGPoint(x: x, y: y)
}
static func angle(point: CGPoint, centre: CGPoint) -> Angle {
let dx = point.x - centre.x
let dy = -(point.y - centre.y)
let radians = atan2(dx, dy)
return Angle(radians: Double(radians))
}
static func diff(angle: Angle, previous: Double) -> Double {
let diff = angle.degrees - previous.truncatingRemainder(dividingBy: 360)
if abs(diff) > 180 {
return diff > 0 ? diff - 360 : diff + 360
}
return diff
}
static func pythagor(a: CGFloat, b: CGFloat) -> CGFloat {
sqrt(square(a) + square(b))
}
}
private func square(_ x: CGFloat) -> CGFloat { x * x }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment