Reputation: 843
In my Swift project, I should detect the user's move using Pan Gesture and draw a sinusoidal curve like this. Is there any library to draw this or any way I could achieve it ??
This sinusoidal curve should have control points also, where I can reshape it dynamically.
Upvotes: 0
Views: 358
Reputation: 16774
You can draw any mathematical function using points, straight lines or segments of curves. A sinusoidal is specifically easy because you can very easily determine extremes and other points of interest to construct an approximation of such a curve.
A very direct way of drawing it would be something like this:
class SinusoidalCurveView: UIView {
var resolution: CGFloat? = nil { didSet { setNeedsDisplay() } }
var function: (n: CGFloat, verticalScale: CGFloat, horizontalScale: CGFloat) = (100.0, 30.0, 30.0) { didSet { setNeedsDisplay() } }
private func solveY(_ x: CGFloat) -> CGFloat {
let n = function.n
let h = function.verticalScale
let m = function.horizontalScale
return h*sin(x/m) + n
}
override func draw(_ rect: CGRect) {
super.draw(rect)
let resolutionStep: CGFloat = {
if let resolution = resolution {
return rect.width/resolution
} else {
return 1.0/self.layer.contentsScale
}
}()
var x: CGFloat = 0.0
let path = UIBezierPath()
path.move(to: CGPoint(x: x, y: solveY(x)))
while x < rect.width {
x += resolutionStep
path.addLine(to: CGPoint(x: x, y: solveY(x)))
}
path.stroke()
}
}
It could use some improvements and extra parameters and so on. But I believe it should illustrate the approach.
As far as control points go you should have no problem either. Since you can determine all extremes and other points of interest you can simply detect where your pan gesture starts and manage behavior accordingly.
For instance, this handles changing of vertical scaling using a pan gesture:
enum PointOfInterest {
case extreme(at: CGPoint)
case zero(at: CGPoint)
}
private func getClosestPointOfInterest(to point: CGPoint) -> PointOfInterest? {
// Do the math here
return nil
}
private var selectedPointOfInterest: PointOfInterest?
@objc private func onPan(_ sender: UIGestureRecognizer) {
switch sender.state {
case .began:
if let pointOfInterest = getClosestPointOfInterest(to: sender.location(in: sender.view)) {
// Got a point that is near enough
selectedPointOfInterest = pointOfInterest
} else {
selectedPointOfInterest = nil
// Invalidate events
sender.isEnabled = false
sender.isEnabled = true
}
case .changed:
guard let selectedPointOfInterest = selectedPointOfInterest else { return }
switch selectedPointOfInterest {
case .extreme(let initialLocation):
let newLocation = sender.location(in: sender.view)
let offset = newLocation.y - sinusoidalCurveView.function.n
sinusoidalCurveView.function = (sinusoidalCurveView.function.n, offset, sinusoidalCurveView.function.horizontalScale)
default:
break
}
default:
selectedPointOfInterest = nil
}
}
There are no control points added so nothing happens on pan. But already adding this code will make things work after you have added a pan gesture recognizer to your view.
private func getClosestPointOfInterest(to point: CGPoint) -> PointOfInterest? {
// Do the math here
return .extreme(at: .zero)
}
From here on things should be quite straight forward. It is a bit of work but it should be nothing really drastic.
Upvotes: 1