Skip to content

Instantly share code, notes, and snippets.

@DonMag
Last active October 30, 2019 16:24
Show Gist options
  • Save DonMag/397dfbe4779e817531ef7a663365b2e7 to your computer and use it in GitHub Desktop.
Save DonMag/397dfbe4779e817531ef7a663365b2e7 to your computer and use it in GitHub Desktop.
//
import UIKit
class CurvedSliderViewController: UIViewController {
var bezier: QuadBezier!
let leftSidePathLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 5
layer.strokeColor = UIColor.blue.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
let rightSidePathLayer: CAShapeLayer = {
let layer = CAShapeLayer()
layer.lineWidth = 5
layer.strokeColor = UIColor.white.cgColor
layer.fillColor = UIColor.clear.cgColor
return layer
}()
// DonMag - layer to mask curve past the "thumb"
let leftSideMaskLayer: CALayer = {
let layer = CALayer()
layer.backgroundColor = UIColor.black.cgColor
return layer
}()
// DonMag - layer to mask curve prior to the "thumb"
let rightSideMaskLayer: 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.backgroundColor = .systemYellow
view.layer.addSublayer(leftSidePathLayer)
view.layer.addSublayer(rightSidePathLayer)
// DonMag - set the masks for the pathLayers
leftSidePathLayer.mask = leftSideMaskLayer
rightSidePathLayer.mask = rightSideMaskLayer
view.addSubview(shape)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
bezier = buildCurvedPath()
leftSidePathLayer.path = bezier.path.cgPath
rightSidePathLayer.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
// leftMask goes from left-edge to thumb-center
var leftMaskFrame = view.bounds
leftMaskFrame.size.width = point.x
// rightMask starts from thumb-center
var rightMaskFrame = view.bounds
rightMaskFrame.origin.x = point.x
CATransaction.begin()
CATransaction.setDisableActions(true)
leftSideMaskLayer.frame = leftMaskFrame
rightSideMaskLayer.frame = rightMaskFrame
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