Last active
January 2, 2021 16:13
-
-
Save KrisRJack/1dbc796a4a2984e7ac26ce28adf3ab16 to your computer and use it in GitHub Desktop.
Labeled Picker View In Swift
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
// | |
// LabeledPickerView.swift | |
// Created by Kristopher Jackson | |
// | |
import UIKit | |
protocol LabeledPickerViewDataSource { | |
func numberOfComponents(in pickerView: UIPickerView) -> Int | |
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int | |
} | |
protocol LabeledPickerViewDelegate { | |
func pickerView(_ pickerView: UIPickerView, labelForComponent component: Int) -> String? | |
func pickerView(_ pickerView: UIPickerView, textAlignmentForComponent component: Int) -> NSTextAlignment? | |
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? | |
} | |
class LabeledPickerView: UIView { | |
var delegate: LabeledPickerViewDelegate? | |
var dataSource: LabeledPickerViewDataSource? | |
var pickerView: UIPickerView = UIPickerView() | |
var labelFont: UIFont = .boldSystemFont(ofSize: 18) | |
var font: UIFont = .systemFont(ofSize: 20, weight: .regular) | |
var textEdgeInsets: UIEdgeInsets { | |
get { | |
return self.insets | |
} | |
set { | |
self.insets = newValue | |
} | |
} | |
private var labels: [UILabel] = [] | |
private var insets: UIEdgeInsets = .zero | |
private var labelWidthAnchors: [NSLayoutConstraint] = [] | |
init() { | |
super.init(frame: .zero) | |
self.setView() | |
} | |
required init?(coder aDecoder: NSCoder) { | |
super.init(coder: aDecoder) | |
self.setView() | |
} | |
private func setView() { | |
self.backgroundColor = .clear | |
self.pickerView.delegate = self | |
self.pickerView.dataSource = self | |
self.addSubview(self.pickerView) | |
NSLayoutConstraint.activate([ | |
self.pickerView.widthAnchor.constraint(equalTo: self.widthAnchor), | |
self.pickerView.heightAnchor.constraint(equalTo: self.heightAnchor), | |
self.pickerView.centerXAnchor.constraint(equalTo: self.centerXAnchor), | |
self.pickerView.centerYAnchor.constraint(equalTo: self.centerYAnchor), | |
]) | |
} | |
private func setLabels() { | |
for component in 0..<self.pickerView.numberOfComponents { | |
/// Skip iteration if label was created | |
if self.labels.indices.contains(component) { continue } | |
/// Create label | |
let label: Label = Label() | |
label.font = self.labelFont | |
label.textAlignment = .right | |
label.paddingTop = self.insets.top | |
label.paddingLeft = self.insets.left | |
label.paddingRight = self.insets.right | |
label.paddingBottom = self.insets.bottom | |
label.translatesAutoresizingMaskIntoConstraints = false | |
label.text = self.delegate?.pickerView(self.pickerView, labelForComponent: component) | |
/// Add label to picker view | |
self.pickerView.addSubview(label) | |
self.labelWidthAnchors.append(label.widthAnchor.constraint(equalToConstant: 0)) | |
NSLayoutConstraint.activate([ | |
self.labelWidthAnchors[self.labelWidthAnchors.count - 1], | |
label.centerYAnchor.constraint(equalTo: self.pickerView.centerYAnchor), | |
self.labels.count == 0 ? (label.leftAnchor.constraint(equalTo: self.pickerView.leftAnchor)) | |
: (label.leftAnchor.constraint(equalTo: self.labels[component - 1].rightAnchor, constant: 5)), | |
]) | |
self.labels.append(label) | |
} | |
} | |
} | |
extension LabeledPickerView: UIPickerViewDataSource { | |
func numberOfComponents(in pickerView: UIPickerView) -> Int { | |
return self.dataSource?.numberOfComponents(in: pickerView) ?? 1 | |
} | |
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { | |
return self.dataSource?.pickerView(pickerView, numberOfRowsInComponent: component) ?? 0 | |
} | |
} | |
extension LabeledPickerView: UIPickerViewDelegate { | |
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView { | |
self.setLabels() | |
var attributedString: NSMutableAttributedString! | |
let labelString: String? = self.labels[component].text | |
let titleString: String = self.delegate?.pickerView(pickerView, titleForRow: row, forComponent: component) ?? "" | |
var pickerLabel: Label? | |
if (view as? UILabel) == nil { | |
pickerLabel = Label() | |
pickerLabel?.font = self.font | |
pickerLabel?.textAlignment = .right | |
pickerLabel?.paddingTop = self.insets.top | |
pickerLabel?.paddingLeft = self.insets.left | |
pickerLabel?.paddingRight = self.insets.right | |
pickerLabel?.paddingBottom = self.insets.bottom | |
} | |
if let label = labelString { | |
let string = "\(titleString) \(label)" | |
attributedString = NSMutableAttributedString(string: string) | |
attributedString.addAttributes([ | |
.font : self.labelFont, | |
.foregroundColor : UIColor.clear, | |
], range: NSRange(location: string.count - label.count, length: label.count)) | |
} else { | |
attributedString = NSMutableAttributedString(string: titleString) | |
pickerLabel?.textAlignment = self.delegate?.pickerView(pickerView, textAlignmentForComponent: component) ?? .center | |
} | |
pickerLabel?.attributedText = attributedString | |
self.labelWidthAnchors[component].constant = pickerView.rowSize(forComponent: component).width | |
return pickerLabel! | |
} | |
} | |
@IBDesignable | |
class Label: UILabel { | |
var textEdgeInsets = UIEdgeInsets.zero { | |
didSet { invalidateIntrinsicContentSize() } | |
} | |
open override func textRect(forBounds bounds: CGRect, limitedToNumberOfLines numberOfLines: Int) -> CGRect { | |
let insetRect = bounds.inset(by: textEdgeInsets) | |
let textRect = super.textRect(forBounds: insetRect, limitedToNumberOfLines: numberOfLines) | |
let invertedInsets = UIEdgeInsets(top: -textEdgeInsets.top, | |
left: -textEdgeInsets.left, | |
bottom: -textEdgeInsets.bottom, | |
right: -textEdgeInsets.right) | |
return textRect.inset(by: invertedInsets) | |
} | |
override func drawText(in rect: CGRect) { | |
super.drawText(in: rect.inset(by: textEdgeInsets)) | |
} | |
@IBInspectable | |
var paddingLeft: CGFloat { | |
set { textEdgeInsets.left = newValue } | |
get { return textEdgeInsets.left } | |
} | |
@IBInspectable | |
var paddingRight: CGFloat { | |
set { textEdgeInsets.right = newValue } | |
get { return textEdgeInsets.right } | |
} | |
@IBInspectable | |
var paddingTop: CGFloat { | |
set { textEdgeInsets.top = newValue } | |
get { return textEdgeInsets.top } | |
} | |
@IBInspectable | |
var paddingBottom: CGFloat { | |
set { textEdgeInsets.bottom = newValue } | |
get { return textEdgeInsets.bottom } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment