Skip to content

Instantly share code, notes, and snippets.

@jmcd
Last active March 22, 2018 12:48
Show Gist options
  • Save jmcd/2499a901136df6f5c327216b27e4e6d4 to your computer and use it in GitHub Desktop.
Save jmcd/2499a901136df6f5c327216b27e4e6d4 to your computer and use it in GitHub Desktop.
import UIKit
import PlaygroundSupport
class MySlider: UISlider {
// indent the tracking so the thumb does not hang outside the view
private let padding = CGFloat(18)
// the default rect for tracking is indented by a few pixels; keep note of that value here
private var trackIndent = CGFloat(0)
// how far the thumb is indented from the edge of the control; expose this so things outside the control can use it, e.g. laying out labels
var thumbIndent: CGFloat { return padding - trackIndent}
// take the default track-rect and pad it left and right, and keep note of the few pixels of base implementation indent in trackIndent
override func trackRect(forBounds bounds: CGRect) -> CGRect {
let superTrackRect = super.trackRect(forBounds: bounds)
trackIndent = superTrackRect.origin.x
return CGRect(
x: superTrackRect.origin.x + padding,
y: superTrackRect.origin.y,
width: superTrackRect.size.width - padding*2,
height: superTrackRect.size.height)
}
// take the default thumb-rect and allow it to overhang the left and right of the track-rect by half of its width
override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect {
let superThumbRect = super.thumbRect(forBounds: bounds, trackRect: rect, value: value)
let valueAsDecimalFraction = value == minimumValue ? 0 : (value - minimumValue)/(maximumValue - minimumValue)
let valueAsDecimalFractionOfMinusOneToOne = (valueAsDecimalFraction*2 - 1)/2
let xOffset = CGFloat(valueAsDecimalFractionOfMinusOneToOne) * superThumbRect.width
return CGRect(
x: superThumbRect.origin.x + xOffset,
y: superThumbRect.origin.y,
width: superThumbRect.size.width,
height: superThumbRect.size.height)
}
}
class MyViewController : UIViewController {
static let range = 1...7
let labels: [UILabel] = {
range.map { (i: Int) -> (UILabel) in
let l = UILabel()
l.text = "\(i)"
l.textAlignment = .center
return l
}
}()
let slider: MySlider = {
let s = MySlider()
s.minimumValue = Float(range.lowerBound)
s.maximumValue = Float(range.upperBound)
return s
}()
override func loadView() {
let view = UIView()
view.backgroundColor = .white
slider.backgroundColor = .orange
labels.forEach { $0.backgroundColor = .magenta }
let subviews: [UIView] = [slider] + labels
subviews.forEach {
$0.translatesAutoresizingMaskIntoConstraints = false
view.addSubview($0)
}
self.view = view
}
override func viewWillLayoutSubviews() {
let sliderConstraints = [
slider.widthAnchor.constraint(equalTo: view.widthAnchor),
slider.centerYAnchor.constraint(equalTo: view.centerYAnchor),
]
let labelConstraints = labels.enumerated().map { (i, label) -> [NSLayoutConstraint] in
let pct = CGFloat(i)/CGFloat(labels.count-1)
let centerX = (view.bounds.size.width-2*self.slider.thumbIndent) * pct + self.slider.thumbIndent
return [
label.centerXAnchor.constraint(equalTo: slider.leftAnchor, constant: centerX),
label.bottomAnchor.constraint(equalTo: slider.topAnchor, constant: 0),
]
}.flatMap { $0 }
view.removeConstraints(view.constraints)
(sliderConstraints + labelConstraints).forEach {
view.addConstraint($0)
$0.isActive = true
}
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment