Created
February 16, 2021 13:58
-
-
Save karlis/1f90c297568844ca1d6f9222aba95dc6 to your computer and use it in GitHub Desktop.
Work in progress. Example SwiftUI gesture. One finger rotation around center.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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