I want to make a custom slider that has increasing height i.e it's height starts from 4.0 and goes to 6.0.
I have written code for creating a layer but I cannot find a way to increase its height in this manner. Here is my code :
let path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius)
ctx.addPath(path.cgPath)
ctx.setFillColor(UIColor.red.cgColor)
ctx.fillPath()
let lowerValuePosition = slider.positionForValue(slider.lowerValue)
let upperValuePosition = slider.positionForValue(slider.upperValue)
let rect = CGRect(x: 0, y: 0,
width: (bounds.width - 36),
height: bounds.height)
ctx.fill(rect)
Because the Minimum and Maximum (left-side & right-side) "Track" images stretch, you may not be able to get what you want with a default UISlider
.
Not too tough to get around it though.
Basically:
UISlider
on that custom viewHere's the idea, before overlaying them:
When we want to overlay the slider on the wedge, set the slider Min/Max track images to clear and it looks like this:
We can use a little trick to handle "filling" the shape by percentage:
red, red, gray, gray
[0.0, pct, pct, 1.0]
That way we get a clean edge, instead of a gradient fade.
Here's a complete example -- no @IBOutlet
or @IBAction
connections, so just set a view controller's custom class to WedgeSliderViewController
:
class RoundedWedgeSliderView: UIView {
var leftRadius: CGFloat = 4.0
var rightRadius: CGFloat = 6.0
// mask shape
private var cMask = CAShapeLayer()
var pct: Float = 0.0 {
didSet {
let p = pct as NSNumber
// disable layer built-in animation so the update won't "lag"
CATransaction.begin()
CATransaction.setDisableActions(true)
// update gradient locations
gradientLayer.locations = [
0.0, p, p, 1.0
]
CATransaction.commit()
}
}
// allows self.layer to be a CAGradientLayer
override class var layerClass: AnyClass { return CAGradientLayer.self }
private var gradientLayer: CAGradientLayer {
return self.layer as! CAGradientLayer
}
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
// gradient colors will be
// red, red, gray, gray
let colors = [
UIColor.red.cgColor,
UIColor.red.cgColor,
UIColor(white: 0.9, alpha: 1.0).cgColor,
UIColor(white: 0.9, alpha: 1.0).cgColor,
]
gradientLayer.colors = colors
// initial gradient color locations
gradientLayer.locations = [
0.0, 0.0, 0.0, 1.0
]
// horizontal gradient
gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
}
override func layoutSubviews() {
super.layoutSubviews()
let r = bounds
// define the "Rounded Wedge" shape
let leftCenter = CGPoint(x: r.minX + leftRadius, y: r.midY)
let rightCenter = CGPoint(x: r.maxX - rightRadius, y: r.midY)
let bez = UIBezierPath()
bez.addArc(withCenter: leftCenter, radius: leftRadius, startAngle: .pi * 0.5, endAngle: .pi * 1.5, clockwise: true)
bez.addArc(withCenter: rightCenter, radius: rightRadius, startAngle: .pi * 1.5, endAngle: .pi * 0.5, clockwise: true)
bez.close()
// set the mask layer's path
cMask.path = bez.cgPath
// mask self's layer
layer.mask = cMask
}
}
class WedgeSliderViewController: UIViewController {
let mySliderView = RoundedWedgeSliderView()
let theSlider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(mySliderView)
view.addSubview(theSlider)
mySliderView.translatesAutoresizingMaskIntoConstraints = false
theSlider.translatesAutoresizingMaskIntoConstraints = false
// respect safe area
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
// constrain slider 100-pts from top, 40-pts on each side
theSlider.topAnchor.constraint(equalTo: g.topAnchor, constant: 100.0),
theSlider.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
theSlider.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
// constrain mySliderView width to the slider width minus 16-pts
// (so we have 8-pt "padding" on each side for the thumb to cover)
mySliderView.widthAnchor.constraint(equalTo: theSlider.widthAnchor, constant: -16.0),
// constrain mySliderView to same height as the slider, centered X & Y
mySliderView.heightAnchor.constraint(equalTo: theSlider.heightAnchor),
mySliderView.centerXAnchor.constraint(equalTo: theSlider.centerXAnchor),
mySliderView.centerYAnchor.constraint(equalTo: theSlider.centerYAnchor),
])
// set left- and right-side "track" images to empty images
theSlider.setMinimumTrackImage(UIImage(), for: .normal)
theSlider.setMaximumTrackImage(UIImage(), for: .normal)
// add target for the slider
theSlider.addTarget(self, action: #selector(self.sliderValueChanged(_:)), for: .valueChanged)
// set intitial values
theSlider.value = 0.0
mySliderView.pct = 0.0
// end-radii of mySliderView defaults to 4.0 and 6.0
// un-comment next line to see the difference
//mySliderView.rightRadius = 10.0
}
@objc func sliderValueChanged(_ sender: Any) {
if let s = sender as? UISlider {
// update mySliderView when the slider changes
mySliderView.pct = s.value
}
}
}