Last active
October 30, 2019 16:24
-
-
Save DonMag/397dfbe4779e817531ef7a663365b2e7 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
// | |
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