Skip to content

Instantly share code, notes, and snippets.

@DonMag
Created October 30, 2019 14:46
Show Gist options
  • Save DonMag/a2154e70a3c67193a7b19bee41c8fe95 to your computer and use it in GitHub Desktop.
Save DonMag/a2154e70a3c67193a7b19bee41c8fe95 to your computer and use it in GitHub Desktop.
//
import UIKit
class CurvedSliderViewController: UIViewController {
var bezier: QuadBezier!
let pathLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 5
layer.strokeColor = UIColor.blue.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
// DonMag - layer to mask curve past the "thumb"
let maskLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.black.cgColor
return layer
}()
var animation: CAKeyframeAnimation!
var shape: CircleView = {
let shape = CircleView()
shape.backgroundColor = .red
shape.frame = CGRect(origin: .zero, size: CGSize(width: 50, height: 50))
return shape
}()
override func viewDidLoad() {
super.viewDidLoad()
view.layer.addSublayer(pathLayer)
// DonMag - set the mask for the pathLayer
pathLayer.mask = maskLayer
view.addSubview(shape)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bezier = buildCurvedPath()
pathLayer.path = bezier.path.cgPath
shape.center = bezier.point(at: 0.5)
// DonMag - update the mask
updateMask(at: shape.center)
}
func buildCurvedPath() -> QuadBezier {
let bounds = view.bounds
let point1 = CGPoint(x: bounds.minX, y: bounds.midY)
let point2 = CGPoint(x: bounds.maxX, y: bounds.midY)
let controlPoint = CGPoint(x: bounds.midX, y: bounds.midY + 100)
let path = QuadBezier(point1: point1, point2: point2, controlPoint: controlPoint)
return path
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePosition(for: touch)
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
if let touch = event?.predictedTouches(for: touch)?.last {
updatePosition(for: touch)
} else {
updatePosition(for: touch)
}
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let touch = touches.first else { return }
updatePosition(for: touch)
}
func updatePosition(for touch: UITouch) {
let location = touch.location(in: view)
let t = (location.x - view.bounds.minX) / view.bounds.width
shape.center = bezier.point(at: t)
// DonMag - update mask frame when "thumb" moves
updateMask(at: shape.center)
}
func updateMask(at point: CGPoint) -> Void {
// DonMag -
// layer frame changes have default animations, which causes
// the mask to "lag behind" the position update
// so, disable layer animation
var f = view.bounds
f.size.width = point.x
CATransaction.begin()
CATransaction.setDisableActions(true)
maskLayer.frame = f
CATransaction.commit()
}
}
struct QuadBezier {
var point1: CGPoint
var point2: CGPoint
var controlPoint: CGPoint
var path: UIBezierPath {
let path = UIBezierPath()
path.move(to: point1)
path.addQuadCurve(to: point2, controlPoint: controlPoint)
return path
}
func point(at t: CGFloat) -> CGPoint {
let t1 = 1 - t
return CGPoint(
x: t1 * t1 * point1.x + 2 * t * t1 * controlPoint.x + t * t * point2.x,
y: t1 * t1 * point1.y + 2 * t * t1 * controlPoint.y + t * t * point2.y
)
}
}
@IBDesignable
public class CircleView: UIView {
override public func layoutSubviews() {
super.layoutSubviews()
layer.cornerRadius = min(bounds.width, bounds.height) / 2
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment