Created
August 9, 2018 20:44
-
-
Save nathanmrtns/7d74c7c55f9f90d7c480d7e6a354d804 to your computer and use it in GitHub Desktop.
Revisions
-
nathanmrtns created this gist
Aug 9, 2018 .There are no files selected for viewing
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 charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,380 @@ import UIKit protocol MIPickerDelegate: AnyObject { func miPicker(_ amDatePicker: MIPicker, didSelect date: Date) //optional func miPickerDidCancelSelection(_ amDatePicker: MIPicker) } class MIPicker: UIView, UIPickerViewDelegate, UIPickerViewDataSource { // MARK: - Config struct Config { fileprivate let contentHeight: CGFloat = 220 fileprivate let bouncingOffset: CGFloat = 0 var type: String? var confirmButtonTitle = NSLocalizedString("done", comment: "done") var cancelButtonTitle = NSLocalizedString("cancel", comment: "cancel") var options: [[String]] = [Utils.getInternationalizedMonths(), Utils.generateYearOptions(maxYear: Calendar.current.component(.year, from: Date()))] var selectedMonthRow: Int? var selectedYearRow: Int? var maxYearRow: Int? var headerHeight: CGFloat = 50 var animationDuration: TimeInterval = 0.25 var contentBackgroundColor: UIColor = UIColor.white var headerBackgroundColor: UIColor = UIColor(red: 244 / 255.0, green: 244 / 255.0, blue: 244 / 255.0, alpha: 1) var confirmButtonColor: UIColor = UIColor.ApplicationColor.wineRed var cancelButtonColor: UIColor = UIColor.ApplicationColor.wineRed var overlayBackgroundColor: UIColor = UIColor.white } var config = Config() weak var delegate: MIPickerDelegate? // MARK: - IBOutlets @IBOutlet weak var datePicker: UIPickerView! @IBOutlet weak var confirmButton: UIButton! @IBOutlet weak var cancelButton: UIButton! @IBOutlet weak var headerView: UIView! @IBOutlet weak var backgroundView: UIView! @IBOutlet weak var headerViewHeightConstraint: NSLayoutConstraint! var bottomConstraint: NSLayoutConstraint! var overlayButton: UIButton! var isPickerBeingShown = false var selectedDate: Date? // MARK: - Init static func getFromNib() -> MIPicker { return UINib.init(nibName: String(describing: self), bundle: nil) .instantiate(withOwner: self, options: nil).last as! MIPicker // swiftlint:disable:this force_cast } override func awakeFromNib() { super.awakeFromNib() datePicker.dataSource = self datePicker.delegate = self config.selectedMonthRow = config.options[0].count - 1 config.selectedYearRow = config.options[1].count - 1 config.maxYearRow = config.options[1].count - 1 } // MARK: - IBAction @IBAction func confirmButtonDidTapped(_ sender: AnyObject) { var rows: [Int] = [] for component in 0...datePicker.numberOfComponents - 1 { rows.append(datePicker.selectedRow(inComponent: component)) } if config.type == "month" { config.selectedMonthRow = rows[0] config.selectedYearRow = rows[1] } else { config.selectedYearRow = rows[0] } let userCalendar = Calendar.current var components = DateComponents() let options = config.options components.year = Int(options[1][config.selectedYearRow!]) components.month = config.type == "month" ? config.selectedMonthRow! + 1 : 1 components.day = 1 let date = userCalendar.date(from: components)! selectedDate = date dismiss() delegate?.miPicker(self, didSelect: selectedDate!) } @IBAction func cancelButtonDidTapped(_ sender: AnyObject) { dismiss() //delegate?.miPickerDidCancelSelection(self) } // MARK: - Private fileprivate func setup(_ parentVC: UIViewController) { // Loading configuration headerViewHeightConstraint.constant = config.headerHeight setHeaderColors() // Overlay view constraints setup overlayButton = UIButton(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)) overlayButton.backgroundColor = config.overlayBackgroundColor overlayButton.alpha = 0 overlayButton.addTarget(self, action: #selector(cancelButtonDidTapped(_:)), for: .touchUpInside) if !overlayButton.isDescendant(of: parentVC.view) { parentVC.view.addSubview(overlayButton) } overlayButton.translatesAutoresizingMaskIntoConstraints = false parentVC.view.addConstraints([ NSLayoutConstraint(item: overlayButton, attribute: .bottom, relatedBy: .equal, toItem: parentVC.view, attribute: .bottom, multiplier: 1, constant: 0), NSLayoutConstraint(item: overlayButton, attribute: .top, relatedBy: .equal, toItem: parentVC.view, attribute: .top, multiplier: 1, constant: 0), NSLayoutConstraint(item: overlayButton, attribute: .leading, relatedBy: .equal, toItem: parentVC.view, attribute: .leading, multiplier: 1, constant: 0), NSLayoutConstraint(item: overlayButton, attribute: .trailing, relatedBy: .equal, toItem: parentVC.view, attribute: .trailing, multiplier: 1, constant: 0) ] ) // Setup picker constraints //config.contentHeight + config.headerHeight frame = CGRect(x: 0, y: UIScreen.main.bounds.height, width: UIScreen.main.bounds.width, height: config.contentHeight + config.headerHeight) translatesAutoresizingMaskIntoConstraints = false bottomConstraint = NSLayoutConstraint(item: self, attribute: .bottom, relatedBy: .equal, toItem: parentVC.view, attribute: .bottom, multiplier: 1, constant: 0) if !isDescendant(of: parentVC.view) { parentVC.view.addSubview(self) } parentVC.view.addConstraints([ //bottomConstraint, NSLayoutConstraint(item: self, attribute: .leading, relatedBy: .equal, toItem: parentVC.view, attribute: .leading, multiplier: 1, constant: 0), NSLayoutConstraint(item: self, attribute: .trailing, relatedBy: .equal, toItem: parentVC.view, attribute: .trailing, multiplier: 1, constant: 0) ] ) addConstraint( NSLayoutConstraint(item: self, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1.0, constant: frame.height) ) move(goUp: false) } func setHeaderColors() { confirmButton.setTitle(config.confirmButtonTitle, for: UIControlState()) cancelButton.setTitle(config.cancelButtonTitle, for: UIControlState()) confirmButton.setTitleColor(config.confirmButtonColor, for: UIControlState()) cancelButton.setTitleColor(config.cancelButtonColor, for: UIControlState()) headerView.backgroundColor = config.headerBackgroundColor backgroundView.backgroundColor = config.contentBackgroundColor } fileprivate func move(goUp: Bool) { bottomConstraint.constant = goUp ? config.bouncingOffset : config.contentHeight + config.headerHeight } // MARK: - Public func show(inVC parentVC: UIViewController, completion: (() -> Void)? = nil) { isPickerBeingShown = true parentVC.view.endEditing(true) setup(parentVC) move(goUp: false) UIView.animate( withDuration: config.animationDuration, delay: 0, usingSpringWithDamping: 0.7, initialSpringVelocity: 5, options: .curveEaseIn, animations: { parentVC.view.layoutIfNeeded() self.overlayButton.alpha = 1 }, completion: { (_) in completion?() } ) if config.type == "month" { let date = Date() let calendar = Calendar.current let month = calendar.component(.month, from: date) - 1 if config.selectedMonthRow == nil && config.selectedYearRow == nil { datePicker.selectRow(config.maxYearRow!, inComponent: 1, animated: false) datePicker.selectRow(config.selectedMonthRow!, inComponent: 0, animated: false) } else { if config.selectedYearRow != nil { datePicker.selectRow(config.selectedYearRow!, inComponent: 1, animated: false) } else { datePicker.selectRow(config.maxYearRow!, inComponent: 1, animated: false) } if config.selectedMonthRow != nil && config.selectedMonthRow! > month { datePicker.selectRow(month, inComponent: 0, animated: false) } else { datePicker.selectRow(config.selectedMonthRow!, inComponent: 0, animated: false) } } } else { if config.selectedYearRow != nil { datePicker.selectRow(config.selectedYearRow!, inComponent: 0, animated: false) } else { if config.maxYearRow != nil { datePicker.selectRow(config.maxYearRow!, inComponent: 0, animated: false) } else { datePicker.selectRow(0, inComponent: 0, animated: false) } } } } func dismiss(_ completion: (() -> Void)? = nil) { isPickerBeingShown = false move(goUp: false) UIView.animate( withDuration: 0, animations: { self.layoutIfNeeded() self.overlayButton.alpha = 0 }, completion: { (_) in completion?() self.removeFromSuperview() self.overlayButton.removeFromSuperview() } ) } // returns the number of 'columns' to display. func numberOfComponents(in pickerView: UIPickerView) -> Int { if config.type == "month" { return 2 } return 1 } // returns the # of rows in each component.. func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { if config.type == "month" { return config.options[component].count } else { return config.options[1].count } } // returns the title of a row func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { if config.type == "month" { return config.options[component][row] } else { return config.options[1][row] } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { let date = Date() let calendar = Calendar.current let month = calendar.component(.month, from: date) - 1 if config.type != nil && config.type == "month" { if pickerView.selectedRow(inComponent: 1) == config.maxYearRow && pickerView.selectedRow(inComponent: 0) > month { pickerView.selectRow(month, inComponent: 0, animated: true) } } } func goForward() { if config.type == "month" { if config.selectedMonthRow! + 1 <= 11 { config.selectedMonthRow = config.selectedMonthRow! + 1 } else { config.selectedMonthRow = 0 config.selectedYearRow = config.selectedYearRow! + 1 } } else { config.selectedYearRow = config.selectedYearRow! + 1 } delegate?.miPicker(self, didSelect: updateSelectedDate()) } func goBackward() { if config.type == "month" { if config.selectedMonthRow! - 1 >= 0 { config.selectedMonthRow = config.selectedMonthRow! - 1 } else if config.selectedMonthRow! - 1 < 0 && config.selectedYearRow!-1 >= 0 { config.selectedMonthRow = 11 config.selectedYearRow = config.selectedYearRow! - 1 } } else { config.selectedMonthRow = 0 config.selectedYearRow = config.selectedYearRow! - 1 } delegate?.miPicker(self, didSelect: updateSelectedDate()) //return [config.selectedMonthRow!, config.selectedYearRow!] } func updateSelectedDate() -> Date { let userCalendar = Calendar.current var components = DateComponents() let options = config.options components.year = Int(options[1][config.selectedYearRow!]) components.month = config.type == "month" ? config.selectedMonthRow! + 1 : 1 components.day = 1 let date = userCalendar.date(from: components)! selectedDate = date return selectedDate! } func isSelectedDateMaximum() -> Bool { if config.type == "month" { let nextDate = Calendar.current.date(byAdding: .month, value: 1, to: selectedDate!) if nextDate?.compare(Date()) == .orderedAscending { return false } else { return true } } else { let nextDate = Calendar.current.date(byAdding: .year, value: 1, to: selectedDate!) if nextDate?.compare(Date()) == .orderedAscending { return false } else { return true } } } func isSelectedDateMinimum() -> Bool { let comp = DateComponents(year: Constants.minimumYear, month: 1, day: 1) let date = Calendar.current.date(from: comp) if config.type == "month" { let nextDate = Calendar.current.date(byAdding: .month, value: -1, to: selectedDate!) if nextDate?.compare(date!) == .orderedAscending { return true } else { return false } } else { let nextDate = Calendar.current.date(byAdding: .year, value: -1, to: selectedDate!) if nextDate?.compare(date!) == .orderedAscending { return true } else { return false } } } func getSelectedDateString() -> String { _ = updateSelectedDate() let dateFormatter = DateFormatter() if config.type!.elementsEqual("month") { dateFormatter.dateFormat = "MMM - yyyy" return dateFormatter.string(from: selectedDate!).uppercased() } else { dateFormatter.dateFormat = "yyyy" return dateFormatter.string(from: selectedDate!) } } }