Created
October 29, 2019 06:34
-
-
Save cyrilchandelier/602afdbf23ab02e2e9a77bde7bb2a105 to your computer and use it in GitHub Desktop.
This file contains 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 | |
@IBDesignable final class MediaProgressBar: UIControl { | |
// MARK: - Configuration | |
@IBInspectable var isThumbVisible: Bool = true { | |
didSet { | |
thumbView.alpha = isThumbVisible ? 1.0 : 0.0 | |
} | |
} | |
@IBInspectable var progress: Float = 0.0 { | |
didSet { | |
refreshProgress() | |
} | |
} | |
@IBInspectable var buffer: Float = 0.0 { | |
didSet { | |
refreshBuffer() | |
} | |
} | |
// MARK: - Customization | |
@IBInspectable var trackColor: UIColor = UIColor.white { | |
didSet { | |
trackView.backgroundColor = trackColor | |
} | |
} | |
@IBInspectable var trackHeight: CGFloat = 10.0 { | |
didSet { | |
trackHeightConstraint.constant = trackHeight | |
} | |
} | |
@IBInspectable var trackRadius: CGFloat = 5.0 { | |
didSet { | |
trackView.layer.cornerRadius = trackRadius | |
} | |
} | |
@IBInspectable var bufferColor: UIColor = UIColor.black.withAlphaComponent(0.7) { | |
didSet { | |
bufferView.backgroundColor = bufferColor | |
} | |
} | |
@IBInspectable var progressColor: UIColor = UIColor.black { | |
didSet { | |
progressView.backgroundColor = progressColor | |
} | |
} | |
@IBInspectable var thumbColor: UIColor = UIColor.black { | |
didSet { | |
thumbView.backgroundColor = thumbColor | |
} | |
} | |
@IBInspectable var thumbRadius: CGFloat = 20.0 { | |
didSet { | |
thumbHeightConstraint.constant = thumbRadius | |
thumbView.layer.cornerRadius = thumbRadius / 2.0 | |
} | |
} | |
// MARK: - Initializers | |
init() { | |
fatalError("Unsupported initializer init(), use init(frame:) instead") | |
} | |
override init(frame: CGRect) { | |
super.init(frame: frame) | |
commonInit() | |
} | |
required init?(coder: NSCoder) { | |
super.init(coder: coder) | |
commonInit() | |
} | |
private func commonInit() { | |
// View must be the same height as intrinsicContentSize | |
addConstraint(NSLayoutConstraint(item: self, | |
attribute: .height, | |
relatedBy: .equal, | |
toItem: nil, | |
attribute: .notAnAttribute, | |
multiplier: 1.0, | |
constant: thumbRadius)) | |
// Configure and add track view | |
trackView.backgroundColor = trackColor | |
trackView.layer.cornerRadius = trackRadius | |
addSubview(trackView) | |
addConstraints(NSLayoutConstraint.constraints(withVisualFormat: "H:|[trackView]|", | |
options: .init(), | |
metrics: nil, | |
views: ["trackView": trackView])) | |
addConstraint(NSLayoutConstraint(item: trackView, | |
attribute: .centerY, | |
relatedBy: .equal, | |
toItem: self, | |
attribute: .centerY, | |
multiplier: 1.0, | |
constant: 0)) | |
trackHeightConstraint = NSLayoutConstraint(item: trackView, | |
attribute: .height, | |
relatedBy: .equal, | |
toItem: nil, | |
attribute: .notAnAttribute, | |
multiplier: 1.0, | |
constant: trackHeight) | |
addConstraint(trackHeightConstraint) | |
// Configure and add buffer view | |
bufferView.backgroundColor = bufferColor | |
trackView.addSubview(bufferView) | |
bufferViewWidthConstraint = NSLayoutConstraint(item: bufferView, | |
attribute: .width, | |
relatedBy: .equal, | |
toItem: nil, | |
attribute: .notAnAttribute, | |
multiplier: 1.0, | |
constant: 0.0) | |
addConstraint(bufferViewWidthConstraint) | |
addConstraint(NSLayoutConstraint(item: bufferView, | |
attribute: .height, | |
relatedBy: .equal, | |
toItem: trackView, | |
attribute: .height, | |
multiplier: 1.0, | |
constant: 0)) | |
// Configure and add progress view | |
progressView.backgroundColor = progressColor | |
trackView.addSubview(progressView) | |
progressViewWidthConstraint = NSLayoutConstraint(item: progressView, | |
attribute: .width, | |
relatedBy: .equal, | |
toItem: nil, | |
attribute: .notAnAttribute, | |
multiplier: 1.0, | |
constant: 0.0) | |
addConstraint(progressViewWidthConstraint) | |
addConstraint(NSLayoutConstraint(item: progressView, | |
attribute: .height, | |
relatedBy: .equal, | |
toItem: trackView, | |
attribute: .height, | |
multiplier: 1.0, | |
constant: 0)) | |
// Configure and add thumb view | |
addSubview(thumbView) | |
thumbView.alpha = isThumbVisible ? 1.0 : 0.0 | |
thumbView.backgroundColor = thumbColor | |
thumbView.layer.cornerRadius = thumbRadius / 2.0 | |
addConstraint(NSLayoutConstraint(item: thumbView, | |
attribute: .centerY, | |
relatedBy: .equal, | |
toItem: self, | |
attribute: .centerY, | |
multiplier: 1.0, | |
constant: 0.0)) | |
addConstraint(NSLayoutConstraint(item: thumbView, | |
attribute: .width, | |
relatedBy: .equal, | |
toItem: thumbView, | |
attribute: .height, | |
multiplier: 1.0, | |
constant: 0.0)) | |
thumbHeightConstraint = NSLayoutConstraint(item: thumbView, | |
attribute: .height, | |
relatedBy: .equal, | |
toItem: nil, | |
attribute: .notAnAttribute, | |
multiplier: 1.0, | |
constant: thumbRadius) | |
addConstraint(thumbHeightConstraint) | |
addConstraint(NSLayoutConstraint(item: thumbView, | |
attribute: .centerX, | |
relatedBy: .equal, | |
toItem: progressView, | |
attribute: .trailing, | |
multiplier: 1.0, | |
constant: 0.0)) | |
} | |
// MARK: - UI Components & held constraints | |
private lazy var trackView: UIView = { | |
let view = UIView() | |
view.translatesAutoresizingMaskIntoConstraints = false | |
view.layer.masksToBounds = true | |
return view | |
}() | |
private lazy var progressView: UIView = { | |
let view = UIView() | |
view.translatesAutoresizingMaskIntoConstraints = false | |
return view | |
}() | |
private lazy var bufferView: UIView = { | |
let view = UIView() | |
view.translatesAutoresizingMaskIntoConstraints = false | |
return view | |
}() | |
private lazy var thumbView: UIView = { | |
let view = UIView() | |
view.translatesAutoresizingMaskIntoConstraints = false | |
view.layer.masksToBounds = true | |
return view | |
}() | |
private var trackHeightConstraint: NSLayoutConstraint! | |
private var thumbHeightConstraint: NSLayoutConstraint! | |
private var progressViewWidthConstraint: NSLayoutConstraint! | |
private var bufferViewWidthConstraint: NSLayoutConstraint! | |
// MARK: - Interface refres | |
private func refreshProgress() { | |
progressViewWidthConstraint.constant = bounds.width * CGFloat(min(max(progress, 0.0), 1.0)) | |
} | |
private func refreshBuffer() { | |
bufferViewWidthConstraint.constant = bounds.width * CGFloat(min(max(buffer, 0.0), 1.0)) | |
} | |
// MARK: - Gesture management | |
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesBegan(touches, with: event) | |
sendActions(for: .touchDown) | |
} | |
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesMoved(touches, with: event) | |
guard let touch = touches.first else { | |
return | |
} | |
let position = touch.location(in: self) | |
progress = min(max(Float(position.x / bounds.width), 0.0), 1.0) | |
sendActions(for: .valueChanged) | |
} | |
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesCancelled(touches, with: event) | |
sendActions(for: .touchUpInside) // Consider this as an ended event instead | |
} | |
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { | |
super.touchesEnded(touches, with: event) | |
sendActions(for: .touchUpInside) | |
} | |
override func point(inside point: CGPoint, with _: UIEvent?) -> Bool { | |
let expandedBounds = bounds.insetBy(dx: min(bounds.width - 44.0, 0), dy: min(bounds.height - 44.0, 0)) | |
expandedBounds.contains(point) | |
return bounds.contains(point) | |
} | |
// MARK: - Layout | |
override func layoutSubviews() { | |
super.layoutSubviews() | |
refreshProgress() | |
refreshBuffer() | |
} | |
override var intrinsicContentSize: CGSize { | |
let desiredHeight = max(trackHeight, thumbRadius) | |
return CGSize(width: UIView.noIntrinsicMetric, height: desiredHeight) | |
} | |
// MARK: - Interface builder | |
#if INTERFACE_BUILDER | |
override func prepareForInterfaceBuilder() { | |
super.prepareForInterfaceBuilder() | |
commonInit() | |
} | |
#endif | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment