diff --git a/DropDown.podspec b/DropDown.podspec
index f38c955..1134d77 100644
--- a/DropDown.podspec
+++ b/DropDown.podspec
@@ -16,7 +16,7 @@ Pod::Spec.new do |s|
s.author = { "kevin-hirsch" => "kevin.hirsch.be@gmail.com" }
s.social_media_url = "http://twitter.com/kevinh6113"
- s.platform = :ios, '8.0'
+ s.platform = :ios, '9.0'
s.source = {
:git => "https://github.com/AssistoLab/DropDown.git",
:tag => "v#{s.version.to_s}"
diff --git a/DropDown/helpers/DPDConstants.swift b/DropDown/helpers/DPDConstants.swift
index b5caa30..67026af 100644
--- a/DropDown/helpers/DPDConstants.swift
+++ b/DropDown/helpers/DPDConstants.swift
@@ -19,6 +19,7 @@ internal struct DPDConstant {
internal struct ReusableIdentifier {
static let DropDownCell = "DropDownCell"
+ static let MoreDropDownCell = "MoreDropDownCell"
}
diff --git a/DropDown/resources/MoreDropDownCell.xib b/DropDown/resources/MoreDropDownCell.xib
new file mode 100644
index 0000000..e2483a9
--- /dev/null
+++ b/DropDown/resources/MoreDropDownCell.xib
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/DropDown/src/DropDown.swift b/DropDown/src/DropDown.swift
index 25fa2b2..2ab31b7 100644
--- a/DropDown/src/DropDown.swift
+++ b/DropDown/src/DropDown.swift
@@ -14,75 +14,79 @@ public typealias SelectionClosure = (Index, String) -> Void
public typealias MultiSelectionClosure = ([Index], [String]) -> Void
public typealias ConfigurationClosure = (Index, String) -> String
public typealias CellConfigurationClosure = (Index, String, DropDownCell) -> Void
+public typealias MoreCellConfigurationClosure = (MoreDropDownCell) -> Void
private typealias ComputeLayoutTuple = (x: CGFloat, y: CGFloat, width: CGFloat, offscreenHeight: CGFloat)
/// Can be `UIView` or `UIBarButtonItem`.
@objc
-public protocol AnchorView: class {
+public protocol AnchorView: AnyObject {
- var plainView: UIView { get }
+ var plainView: UIView { get }
}
extension UIView: AnchorView {
- public var plainView: UIView {
- return self
- }
+ public var plainView: UIView {
+ return self
+ }
}
extension UIBarButtonItem: AnchorView {
- public var plainView: UIView {
- return value(forKey: "view") as! UIView
- }
+ public var plainView: UIView {
+ return value(forKey: "view") as! UIView
+ }
}
/// A Material Design drop down in replacement for `UIPickerView`.
public final class DropDown: UIView {
- //TODO: handle iOS 7 landscape mode
-
- /// The dismiss mode for a drop down.
- public enum DismissMode {
+ //TODO: handle iOS 7 landscape mode
- /// A tap outside the drop down is required to dismiss.
- case onTap
+ /// The dismiss mode for a drop down.
+ public enum DismissMode {
- /// No tap is required to dismiss, it will dimiss when interacting with anything else.
- case automatic
+ /// A tap outside the drop down is required to dismiss.
+ case onTap
- /// Not dismissable by the user.
- case manual
+ /// No tap is required to dismiss, it will dimiss when interacting with anything else.
+ case automatic
- }
+ /// Not dismissable by the user.
+ case manual
- /// The direction where the drop down will show from the `anchorView`.
- public enum Direction {
+ }
- /// The drop down will show below the anchor view when possible, otherwise above if there is more place than below.
- case any
+ /// The direction where the drop down will show from the `anchorView`.
+ public enum Direction {
- /// The drop down will show above the anchor view or will not be showed if not enough space.
- case top
+ /// The drop down will show below the anchor view when possible, otherwise above if there is more place than below.
+ case any
- /// The drop down will show below or will not be showed if not enough space.
- case bottom
+ /// The drop down will show above the anchor view or will not be showed if not enough space.
+ case top
- }
+ /// The drop down will show below or will not be showed if not enough space.
+ case bottom
- //MARK: - Properties
+ }
- /// The current visible drop down. There can be only one visible drop down at a time.
- public static weak var VisibleDropDown: DropDown?
+ //MARK: - Properties
- //MARK: UI
- fileprivate let dismissableView = UIView()
- fileprivate let tableViewContainer = UIView()
- fileprivate let tableView = UITableView()
- fileprivate var templateCell: DropDownCell!
+ /// The current visible drop down. There can be only one visible drop down at a time.
+ public static weak var VisibleDropDown: DropDown?
+
+ private var shortListEnabled: Bool = false
+ public var sortListMaxEntries: Int = -1
+
+ //MARK: UI
+ fileprivate let dismissableView = UIView()
+ fileprivate let tableViewContainer = UIView()
+ fileprivate let tableView = UITableView()
+ fileprivate var templateCell: DropDownCell!
fileprivate lazy var arrowIndication: UIImageView = {
UIGraphicsBeginImageContextWithOptions(CGSize(width: 20, height: 10), false, 0)
let path = UIBezierPath()
@@ -101,39 +105,39 @@ public final class DropDown: UIView {
}()
- /// The view to which the drop down will displayed onto.
- public weak var anchorView: AnchorView? {
- didSet { setNeedsUpdateConstraints() }
- }
+ /// The view to which the drop down will displayed onto.
+ public weak var anchorView: AnchorView? {
+ didSet { setNeedsUpdateConstraints() }
+ }
- /**
- The possible directions where the drop down will be showed.
+ /**
+ The possible directions where the drop down will be showed.
- See `Direction` enum for more info.
- */
- public var direction = Direction.any
+ See `Direction` enum for more info.
+ */
+ public var direction = Direction.any
- /**
- The offset point relative to `anchorView` when the drop down is shown above the anchor view.
+ /**
+ The offset point relative to `anchorView` when the drop down is shown above the anchor view.
- By default, the drop down is showed onto the `anchorView` with the top
- left corner for its origin, so an offset equal to (0, 0).
- You can change here the default drop down origin.
- */
- public var topOffset: CGPoint = .zero {
- didSet { setNeedsUpdateConstraints() }
- }
+ By default, the drop down is showed onto the `anchorView` with the top
+ left corner for its origin, so an offset equal to (0, 0).
+ You can change here the default drop down origin.
+ */
+ public var topOffset: CGPoint = .zero {
+ didSet { setNeedsUpdateConstraints() }
+ }
- /**
- The offset point relative to `anchorView` when the drop down is shown below the anchor view.
+ /**
+ The offset point relative to `anchorView` when the drop down is shown below the anchor view.
- By default, the drop down is showed onto the `anchorView` with the top
- left corner for its origin, so an offset equal to (0, 0).
- You can change here the default drop down origin.
- */
- public var bottomOffset: CGPoint = .zero {
- didSet { setNeedsUpdateConstraints() }
- }
+ By default, the drop down is showed onto the `anchorView` with the top
+ left corner for its origin, so an offset equal to (0, 0).
+ You can change here the default drop down origin.
+ */
+ public var bottomOffset: CGPoint = .zero {
+ didSet { setNeedsUpdateConstraints() }
+ }
/**
The offset from the bottom of the window when the drop down is shown below the anchor view.
@@ -143,194 +147,194 @@ public final class DropDown: UIView {
didSet { setNeedsUpdateConstraints() }
}
- /**
- The width of the drop down.
-
- Defaults to `anchorView.bounds.width - offset.x`.
- */
- public var width: CGFloat? {
- didSet { setNeedsUpdateConstraints() }
- }
-
- /**
- arrowIndication.x
-
- arrowIndication will be add to tableViewContainer when configured
- */
- public var arrowIndicationX: CGFloat? {
- didSet {
- if let arrowIndicationX = arrowIndicationX {
- tableViewContainer.addSubview(arrowIndication)
- arrowIndication.tintColor = tableViewBackgroundColor
- arrowIndication.frame.origin.x = arrowIndicationX
- } else {
- arrowIndication.removeFromSuperview()
- }
- }
- }
-
- //MARK: Constraints
- fileprivate var heightConstraint: NSLayoutConstraint!
- fileprivate var widthConstraint: NSLayoutConstraint!
- fileprivate var xConstraint: NSLayoutConstraint!
- fileprivate var yConstraint: NSLayoutConstraint!
-
- //MARK: Appearance
- @objc public dynamic var cellHeight = DPDConstant.UI.RowHeight {
- willSet { tableView.rowHeight = newValue }
- didSet { reloadAllComponents() }
- }
-
- @objc fileprivate dynamic var tableViewBackgroundColor = DPDConstant.UI.BackgroundColor {
- willSet {
+ /**
+ The width of the drop down.
+
+ Defaults to `anchorView.bounds.width - offset.x`.
+ */
+ public var width: CGFloat? {
+ didSet { setNeedsUpdateConstraints() }
+ }
+
+ /**
+ arrowIndication.x
+
+ arrowIndication will be add to tableViewContainer when configured
+ */
+ public var arrowIndicationX: CGFloat? {
+ didSet {
+ if let arrowIndicationX = arrowIndicationX {
+ tableViewContainer.addSubview(arrowIndication)
+ arrowIndication.tintColor = tableViewBackgroundColor
+ arrowIndication.frame.origin.x = arrowIndicationX
+ } else {
+ arrowIndication.removeFromSuperview()
+ }
+ }
+ }
+
+ //MARK: Constraints
+ fileprivate var heightConstraint: NSLayoutConstraint!
+ fileprivate var widthConstraint: NSLayoutConstraint!
+ fileprivate var xConstraint: NSLayoutConstraint!
+ fileprivate var yConstraint: NSLayoutConstraint!
+
+ //MARK: Appearance
+ @objc public dynamic var cellHeight = DPDConstant.UI.RowHeight {
+ willSet { tableView.rowHeight = newValue }
+ didSet { reloadAllComponents() }
+ }
+
+ @objc fileprivate dynamic var tableViewBackgroundColor = DPDConstant.UI.BackgroundColor {
+ willSet {
tableView.backgroundColor = newValue
if arrowIndicationX != nil { arrowIndication.tintColor = newValue }
}
- }
-
- public override var backgroundColor: UIColor? {
- get { return tableViewBackgroundColor }
- set { tableViewBackgroundColor = newValue! }
- }
-
- /**
- The color of the dimmed background (behind the drop down, covering the entire screen).
- */
- public var dimmedBackgroundColor = UIColor.clear {
- willSet { super.backgroundColor = newValue }
- }
-
- /**
- The background color of the selected cell in the drop down.
-
- Changing the background color automatically reloads the drop down.
- */
- @objc public dynamic var selectionBackgroundColor = DPDConstant.UI.SelectionBackgroundColor
-
- /**
- The separator color between cells.
-
- Changing the separator color automatically reloads the drop down.
- */
- @objc public dynamic var separatorColor = DPDConstant.UI.SeparatorColor {
- willSet { tableView.separatorColor = newValue }
- didSet { reloadAllComponents() }
- }
-
- /**
- The corner radius of DropDown.
-
- Changing the corner radius automatically reloads the drop down.
- */
- @objc public dynamic var cornerRadius = DPDConstant.UI.CornerRadius {
- willSet {
- tableViewContainer.layer.cornerRadius = newValue
- tableView.layer.cornerRadius = newValue
- }
- didSet { reloadAllComponents() }
- }
-
- /**
- Alias method for `cornerRadius` variable to avoid ambiguity.
- */
- @objc public dynamic func setupCornerRadius(_ radius: CGFloat) {
- tableViewContainer.layer.cornerRadius = radius
- tableView.layer.cornerRadius = radius
- reloadAllComponents()
- }
-
- /**
- The masked corners of DropDown.
-
- Changing the masked corners automatically reloads the drop down.
- */
- @available(iOS 11.0, *)
- @objc public dynamic func setupMaskedCorners(_ cornerMask: CACornerMask) {
- tableViewContainer.layer.maskedCorners = cornerMask
- tableView.layer.maskedCorners = cornerMask
- reloadAllComponents()
- }
-
- /**
- The color of the shadow.
-
- Changing the shadow color automatically reloads the drop down.
- */
- @objc public dynamic var shadowColor = DPDConstant.UI.Shadow.Color {
- willSet { tableViewContainer.layer.shadowColor = newValue.cgColor }
- didSet { reloadAllComponents() }
- }
-
- /**
- The offset of the shadow.
-
- Changing the shadow color automatically reloads the drop down.
- */
- @objc public dynamic var shadowOffset = DPDConstant.UI.Shadow.Offset {
- willSet { tableViewContainer.layer.shadowOffset = newValue }
- didSet { reloadAllComponents() }
- }
-
- /**
- The opacity of the shadow.
-
- Changing the shadow opacity automatically reloads the drop down.
- */
- @objc public dynamic var shadowOpacity = DPDConstant.UI.Shadow.Opacity {
- willSet { tableViewContainer.layer.shadowOpacity = newValue }
- didSet { reloadAllComponents() }
- }
-
- /**
- The radius of the shadow.
-
- Changing the shadow radius automatically reloads the drop down.
- */
- @objc public dynamic var shadowRadius = DPDConstant.UI.Shadow.Radius {
- willSet { tableViewContainer.layer.shadowRadius = newValue }
- didSet { reloadAllComponents() }
- }
-
- /**
- The duration of the show/hide animation.
- */
- @objc public dynamic var animationduration = DPDConstant.Animation.Duration
-
- /**
- The option of the show animation. Global change.
- */
- public static var animationEntranceOptions = DPDConstant.Animation.EntranceOptions
-
- /**
- The option of the hide animation. Global change.
- */
- public static var animationExitOptions = DPDConstant.Animation.ExitOptions
-
- /**
- The option of the show animation. Only change the caller. To change all drop down's use the static var.
- */
- public var animationEntranceOptions: UIView.AnimationOptions = DropDown.animationEntranceOptions
-
- /**
- The option of the hide animation. Only change the caller. To change all drop down's use the static var.
- */
- public var animationExitOptions: UIView.AnimationOptions = DropDown.animationExitOptions
-
- /**
- The downScale transformation of the tableview when the DropDown is appearing
- */
- public var downScaleTransform = DPDConstant.Animation.DownScaleTransform {
- willSet { tableViewContainer.transform = newValue }
- }
-
- /**
- The color of the text for each cells of the drop down.
-
- Changing the text color automatically reloads the drop down.
- */
- @objc public dynamic var textColor = DPDConstant.UI.TextColor {
- didSet { reloadAllComponents() }
- }
+ }
+
+ public override var backgroundColor: UIColor? {
+ get { return tableViewBackgroundColor }
+ set { tableViewBackgroundColor = newValue! }
+ }
+
+ /**
+ The color of the dimmed background (behind the drop down, covering the entire screen).
+ */
+ public var dimmedBackgroundColor = UIColor.clear {
+ willSet { super.backgroundColor = newValue }
+ }
+
+ /**
+ The background color of the selected cell in the drop down.
+
+ Changing the background color automatically reloads the drop down.
+ */
+ @objc public dynamic var selectionBackgroundColor = DPDConstant.UI.SelectionBackgroundColor
+
+ /**
+ The separator color between cells.
+
+ Changing the separator color automatically reloads the drop down.
+ */
+ @objc public dynamic var separatorColor = DPDConstant.UI.SeparatorColor {
+ willSet { tableView.separatorColor = newValue }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ The corner radius of DropDown.
+
+ Changing the corner radius automatically reloads the drop down.
+ */
+ @objc public dynamic var cornerRadius = DPDConstant.UI.CornerRadius {
+ willSet {
+ tableViewContainer.layer.cornerRadius = newValue
+ tableView.layer.cornerRadius = newValue
+ }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ Alias method for `cornerRadius` variable to avoid ambiguity.
+ */
+ @objc public dynamic func setupCornerRadius(_ radius: CGFloat) {
+ tableViewContainer.layer.cornerRadius = radius
+ tableView.layer.cornerRadius = radius
+ reloadAllComponents()
+ }
+
+ /**
+ The masked corners of DropDown.
+
+ Changing the masked corners automatically reloads the drop down.
+ */
+ @available(iOS 11.0, *)
+ @objc public dynamic func setupMaskedCorners(_ cornerMask: CACornerMask) {
+ tableViewContainer.layer.maskedCorners = cornerMask
+ tableView.layer.maskedCorners = cornerMask
+ reloadAllComponents()
+ }
+
+ /**
+ The color of the shadow.
+
+ Changing the shadow color automatically reloads the drop down.
+ */
+ @objc public dynamic var shadowColor = DPDConstant.UI.Shadow.Color {
+ willSet { tableViewContainer.layer.shadowColor = newValue.cgColor }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ The offset of the shadow.
+
+ Changing the shadow color automatically reloads the drop down.
+ */
+ @objc public dynamic var shadowOffset = DPDConstant.UI.Shadow.Offset {
+ willSet { tableViewContainer.layer.shadowOffset = newValue }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ The opacity of the shadow.
+
+ Changing the shadow opacity automatically reloads the drop down.
+ */
+ @objc public dynamic var shadowOpacity = DPDConstant.UI.Shadow.Opacity {
+ willSet { tableViewContainer.layer.shadowOpacity = newValue }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ The radius of the shadow.
+
+ Changing the shadow radius automatically reloads the drop down.
+ */
+ @objc public dynamic var shadowRadius = DPDConstant.UI.Shadow.Radius {
+ willSet { tableViewContainer.layer.shadowRadius = newValue }
+ didSet { reloadAllComponents() }
+ }
+
+ /**
+ The duration of the show/hide animation.
+ */
+ @objc public dynamic var animationduration = DPDConstant.Animation.Duration
+
+ /**
+ The option of the show animation. Global change.
+ */
+ public static var animationEntranceOptions = DPDConstant.Animation.EntranceOptions
+
+ /**
+ The option of the hide animation. Global change.
+ */
+ public static var animationExitOptions = DPDConstant.Animation.ExitOptions
+
+ /**
+ The option of the show animation. Only change the caller. To change all drop down's use the static var.
+ */
+ public var animationEntranceOptions: UIView.AnimationOptions = DropDown.animationEntranceOptions
+
+ /**
+ The option of the hide animation. Only change the caller. To change all drop down's use the static var.
+ */
+ public var animationExitOptions: UIView.AnimationOptions = DropDown.animationExitOptions
+
+ /**
+ The downScale transformation of the tableview when the DropDown is appearing
+ */
+ public var downScaleTransform = DPDConstant.Animation.DownScaleTransform {
+ willSet { tableViewContainer.transform = newValue }
+ }
+
+ /**
+ The color of the text for each cells of the drop down.
+
+ Changing the text color automatically reloads the drop down.
+ */
+ @objc public dynamic var textColor = DPDConstant.UI.TextColor {
+ didSet { reloadAllComponents() }
+ }
/**
The color of the text for selected cells of the drop down.
@@ -341,66 +345,81 @@ public final class DropDown: UIView {
didSet { reloadAllComponents() }
}
- /**
- The font of the text for each cells of the drop down.
-
- Changing the text font automatically reloads the drop down.
- */
- @objc public dynamic var textFont = DPDConstant.UI.TextFont {
- didSet { reloadAllComponents() }
- }
+ /**
+ The font of the text for each cells of the drop down.
+
+ Changing the text font automatically reloads the drop down.
+ */
+ @objc public dynamic var textFont = DPDConstant.UI.TextFont {
+ didSet { reloadAllComponents() }
+ }
/**
The NIB to use for DropDownCells
Changing the cell nib automatically reloads the drop down.
*/
- public var cellNib = UINib(nibName: "DropDownCell", bundle: Bundle(for: DropDownCell.self)) {
- didSet {
- tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell)
- templateCell = nil
- reloadAllComponents()
- }
- }
-
- //MARK: Content
-
- /**
- The data source for the drop down.
-
- Changing the data source automatically reloads the drop down.
- */
- public var dataSource = [String]() {
- didSet {
+ public var cellNib = UINib(nibName: "DropDownCell", bundle: Bundle(for: DropDownCell.self)) {
+ didSet {
+ tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell)
+ templateCell = nil
+ reloadAllComponents()
+ }
+ }
+
+ public var moreCellNib = UINib(nibName: "MoreDropDownCell", bundle: Bundle(for: MoreDropDownCell.self)) {
+ didSet {
+ tableView.register(moreCellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.MoreDropDownCell)
+ reloadAllComponents()
+ }
+ }
+
+ //MARK: Content
+
+ /**
+ The data source for the drop down.
+
+ Changing the data source automatically reloads the drop down.
+ */
+ public var dataSource = [String]() {
+ didSet {
+
+ if sortListMaxEntries > 0,
+ dataSource.count > sortListMaxEntries {
+ shortListEnabled = true
+ } else {
+ shortListEnabled = false
+ }
+
deselectRows(at: selectedRowIndices)
- reloadAllComponents()
- }
- }
-
- /**
- The localization keys for the data source for the drop down.
-
- Changing this value automatically reloads the drop down.
- This has uses for setting accibility identifiers on the drop down cells (same ones as the localization keys).
- */
- public var localizationKeysDataSource = [String]() {
- didSet {
- dataSource = localizationKeysDataSource.map { NSLocalizedString($0, comment: "") }
- }
- }
-
- /// The indicies that have been selected
- fileprivate var selectedRowIndices = Set()
-
- /**
- The format for the cells' text.
-
- By default, the cell's text takes the plain `dataSource` value.
- Changing `cellConfiguration` automatically reloads the drop down.
- */
- public var cellConfiguration: ConfigurationClosure? {
- didSet { reloadAllComponents() }
- }
+ reloadAllComponents()
+ }
+ }
+
+ /**
+ The localization keys for the data source for the drop down.
+
+ Changing this value automatically reloads the drop down.
+ This has uses for setting accibility identifiers on the drop down cells (same ones as the localization keys).
+ */
+ public var localizationKeysDataSource = [String]() {
+ didSet {
+ dataSource = localizationKeysDataSource.map { NSLocalizedString($0, comment: "") }
+ }
+ }
+
+ /// The indicies that have been selected
+ fileprivate var selectedRowIndices = Set()
+
+ /**
+ The format for the cells' text.
+
+ By default, the cell's text takes the plain `dataSource` value.
+ Changing `cellConfiguration` automatically reloads the drop down.
+ */
+ public var cellConfiguration: ConfigurationClosure? {
+ didSet { reloadAllComponents() }
+ }
/**
A advanced formatter for the cells. Allows customization when custom cells are used
@@ -410,9 +429,13 @@ public final class DropDown: UIView {
public var customCellConfiguration: CellConfigurationClosure? {
didSet { reloadAllComponents() }
}
+
+ public var customMoreCellConfiguration: MoreCellConfigurationClosure? {
+ didSet { reloadAllComponents() }
+ }
- /// The action to execute when the user selects a cell.
- public var selectionAction: SelectionClosure?
+ /// The action to execute when the user selects a cell.
+ public var selectionAction: SelectionClosure?
/**
The action to execute when the user selects multiple cells.
@@ -422,80 +445,80 @@ public final class DropDown: UIView {
*/
public var multiSelectionAction: MultiSelectionClosure?
- /// The action to execute when the drop down will show.
- public var willShowAction: Closure?
-
- /// The action to execute when the user cancels/hides the drop down.
- public var cancelAction: Closure?
-
- /// The dismiss mode of the drop down. Default is `OnTap`.
- public var dismissMode = DismissMode.onTap {
- willSet {
- if newValue == .onTap {
- let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissableViewTapped))
- dismissableView.addGestureRecognizer(gestureRecognizer)
- } else if let gestureRecognizer = dismissableView.gestureRecognizers?.first {
- dismissableView.removeGestureRecognizer(gestureRecognizer)
- }
- }
- }
-
- fileprivate var minHeight: CGFloat {
- return tableView.rowHeight
- }
-
- fileprivate var didSetupConstraints = false
-
- //MARK: - Init's
-
- deinit {
- stopListeningToNotifications()
- }
-
- /**
- Creates a new instance of a drop down.
- Don't forget to setup the `dataSource`,
- the `anchorView` and the `selectionAction`
- at least before calling `show()`.
- */
- public convenience init() {
- self.init(frame: .zero)
- }
-
- /**
- Creates a new instance of a drop down.
-
- - parameter anchorView: The view to which the drop down will displayed onto.
- - parameter selectionAction: The action to execute when the user selects a cell.
- - parameter dataSource: The data source for the drop down.
- - parameter topOffset: The offset point relative to `anchorView` used when drop down is displayed on above the anchor view.
- - parameter bottomOffset: The offset point relative to `anchorView` used when drop down is displayed on below the anchor view.
- - parameter cellConfiguration: The format for the cells' text.
- - parameter cancelAction: The action to execute when the user cancels/hides the drop down.
-
- - returns: A new instance of a drop down customized with the above parameters.
- */
- public convenience init(anchorView: AnchorView, selectionAction: SelectionClosure? = nil, dataSource: [String] = [], topOffset: CGPoint? = nil, bottomOffset: CGPoint? = nil, cellConfiguration: ConfigurationClosure? = nil, cancelAction: Closure? = nil) {
- self.init(frame: .zero)
-
- self.anchorView = anchorView
- self.selectionAction = selectionAction
- self.dataSource = dataSource
- self.topOffset = topOffset ?? .zero
- self.bottomOffset = bottomOffset ?? .zero
- self.cellConfiguration = cellConfiguration
- self.cancelAction = cancelAction
- }
-
- override public init(frame: CGRect) {
- super.init(frame: frame)
- setup()
- }
-
- public required init?(coder aDecoder: NSCoder) {
- super.init(coder: aDecoder)
- setup()
- }
+ /// The action to execute when the drop down will show.
+ public var willShowAction: Closure?
+
+ /// The action to execute when the user cancels/hides the drop down.
+ public var cancelAction: Closure?
+
+ /// The dismiss mode of the drop down. Default is `OnTap`.
+ public var dismissMode = DismissMode.onTap {
+ willSet {
+ if newValue == .onTap {
+ let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(dismissableViewTapped))
+ dismissableView.addGestureRecognizer(gestureRecognizer)
+ } else if let gestureRecognizer = dismissableView.gestureRecognizers?.first {
+ dismissableView.removeGestureRecognizer(gestureRecognizer)
+ }
+ }
+ }
+
+ fileprivate var minHeight: CGFloat {
+ return tableView.rowHeight
+ }
+
+ fileprivate var didSetupConstraints = false
+
+ //MARK: - Init's
+
+ deinit {
+ stopListeningToNotifications()
+ }
+
+ /**
+ Creates a new instance of a drop down.
+ Don't forget to setup the `dataSource`,
+ the `anchorView` and the `selectionAction`
+ at least before calling `show()`.
+ */
+ public convenience init() {
+ self.init(frame: .zero)
+ }
+
+ /**
+ Creates a new instance of a drop down.
+
+ - parameter anchorView: The view to which the drop down will displayed onto.
+ - parameter selectionAction: The action to execute when the user selects a cell.
+ - parameter dataSource: The data source for the drop down.
+ - parameter topOffset: The offset point relative to `anchorView` used when drop down is displayed on above the anchor view.
+ - parameter bottomOffset: The offset point relative to `anchorView` used when drop down is displayed on below the anchor view.
+ - parameter cellConfiguration: The format for the cells' text.
+ - parameter cancelAction: The action to execute when the user cancels/hides the drop down.
+
+ - returns: A new instance of a drop down customized with the above parameters.
+ */
+ public convenience init(anchorView: AnchorView, selectionAction: SelectionClosure? = nil, dataSource: [String] = [], topOffset: CGPoint? = nil, bottomOffset: CGPoint? = nil, cellConfiguration: ConfigurationClosure? = nil, cancelAction: Closure? = nil) {
+ self.init(frame: .zero)
+
+ self.anchorView = anchorView
+ self.selectionAction = selectionAction
+ self.dataSource = dataSource
+ self.topOffset = topOffset ?? .zero
+ self.bottomOffset = bottomOffset ?? .zero
+ self.cellConfiguration = cellConfiguration
+ self.cancelAction = cancelAction
+ }
+
+ override public init(frame: CGRect) {
+ super.init(frame: frame)
+ setup()
+ }
+
+ public required init?(coder aDecoder: NSCoder) {
+ super.init(coder: aDecoder)
+ setup()
+ }
}
@@ -503,44 +526,45 @@ public final class DropDown: UIView {
private extension DropDown {
- func setup() {
- tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell)
+ func setup() {
+ tableView.register(cellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.DropDownCell)
+ tableView.register(moreCellNib, forCellReuseIdentifier: DPDConstant.ReusableIdentifier.MoreDropDownCell)
- DispatchQueue.main.async {
- //HACK: If not done in dispatch_async on main queue `setupUI` will have no effect
- self.updateConstraintsIfNeeded()
- self.setupUI()
- }
+ DispatchQueue.main.async {
+ //HACK: If not done in dispatch_async on main queue `setupUI` will have no effect
+ self.updateConstraintsIfNeeded()
+ self.setupUI()
+ }
- tableView.rowHeight = cellHeight
- setHiddentState()
- isHidden = true
+ tableView.rowHeight = cellHeight
+ setHiddentState()
+ isHidden = true
- dismissMode = .onTap
+ dismissMode = .onTap
- tableView.delegate = self
- tableView.dataSource = self
-
- startListeningToKeyboard()
+ tableView.delegate = self
+ tableView.dataSource = self
+
+ startListeningToKeyboard()
- accessibilityIdentifier = "drop_down"
- }
+ accessibilityIdentifier = "drop_down"
+ }
- func setupUI() {
- super.backgroundColor = dimmedBackgroundColor
+ func setupUI() {
+ super.backgroundColor = dimmedBackgroundColor
- tableViewContainer.layer.masksToBounds = false
- tableViewContainer.layer.cornerRadius = cornerRadius
- tableViewContainer.layer.shadowColor = shadowColor.cgColor
- tableViewContainer.layer.shadowOffset = shadowOffset
- tableViewContainer.layer.shadowOpacity = shadowOpacity
- tableViewContainer.layer.shadowRadius = shadowRadius
+ tableViewContainer.layer.masksToBounds = false
+ tableViewContainer.layer.cornerRadius = cornerRadius
+ tableViewContainer.layer.shadowColor = shadowColor.cgColor
+ tableViewContainer.layer.shadowOffset = shadowOffset
+ tableViewContainer.layer.shadowOpacity = shadowOpacity
+ tableViewContainer.layer.shadowRadius = shadowRadius
- tableView.backgroundColor = tableViewBackgroundColor
- tableView.separatorColor = separatorColor
- tableView.layer.cornerRadius = cornerRadius
- tableView.layer.masksToBounds = true
- }
+ tableView.backgroundColor = tableViewBackgroundColor
+ tableView.separatorColor = separatorColor
+ tableView.layer.cornerRadius = cornerRadius
+ tableView.layer.masksToBounds = true
+ }
}
@@ -548,253 +572,258 @@ private extension DropDown {
extension DropDown {
- public override func updateConstraints() {
- if !didSetupConstraints {
- setupConstraints()
- }
-
- didSetupConstraints = true
-
- let layout = computeLayout()
-
- if !layout.canBeDisplayed {
- super.updateConstraints()
- hide()
+ public override func updateConstraints() {
+ if !didSetupConstraints {
+ setupConstraints()
+ }
+
+ didSetupConstraints = true
+
+ let layout = computeLayout()
+
+ if !layout.canBeDisplayed {
+ super.updateConstraints()
+ hide()
+
+ return
+ }
+
+ if UIView.appearance().semanticContentAttribute == .forceLeftToRight {
+ xConstraint.constant = layout.x
+ }else {
+ xConstraint.constant = (UIWindow.visibleWindow()?.frame.width ?? 0) - (layout.x + layout.width)
+ }
+
+ yConstraint.constant = layout.y
+ widthConstraint.constant = layout.width
+ heightConstraint.constant = layout.visibleHeight
+
+ tableView.isScrollEnabled = layout.offscreenHeight > 0
+
+ DispatchQueue.main.async { [weak self] in
+ self?.tableView.flashScrollIndicators()
+ }
+
+ super.updateConstraints()
+ }
+
+ fileprivate func setupConstraints() {
+ translatesAutoresizingMaskIntoConstraints = false
+
+ // Dismissable view
+ addSubview(dismissableView)
+ dismissableView.translatesAutoresizingMaskIntoConstraints = false
+
+ addUniversalConstraints(format: "|[dismissableView]|", views: ["dismissableView": dismissableView])
+
+
+ // Table view container
+ addSubview(tableViewContainer)
+ tableViewContainer.translatesAutoresizingMaskIntoConstraints = false
+
+ xConstraint = NSLayoutConstraint(
+ item: tableViewContainer,
+ attribute: .leading,
+ relatedBy: .equal,
+ toItem: self,
+ attribute: .leading,
+ multiplier: 1,
+ constant: 0)
+ addConstraint(xConstraint)
+
+ yConstraint = NSLayoutConstraint(
+ item: tableViewContainer,
+ attribute: .top,
+ relatedBy: .equal,
+ toItem: self,
+ attribute: .top,
+ multiplier: 1,
+ constant: 0)
+ addConstraint(yConstraint)
+
+ widthConstraint = NSLayoutConstraint(
+ item: tableViewContainer,
+ attribute: .width,
+ relatedBy: .equal,
+ toItem: nil,
+ attribute: .notAnAttribute,
+ multiplier: 1,
+ constant: 0)
+ tableViewContainer.addConstraint(widthConstraint)
+
+ heightConstraint = NSLayoutConstraint(
+ item: tableViewContainer,
+ attribute: .height,
+ relatedBy: .equal,
+ toItem: nil,
+ attribute: .notAnAttribute,
+ multiplier: 1,
+ constant: 0)
+ tableViewContainer.addConstraint(heightConstraint)
+
+ // Table view
+ tableViewContainer.addSubview(tableView)
+ tableView.translatesAutoresizingMaskIntoConstraints = false
+
+ tableViewContainer.addUniversalConstraints(format: "|[tableView]|", views: ["tableView": tableView])
+ }
+
+ public override func layoutSubviews() {
+ super.layoutSubviews()
+
+ // When orientation changes, layoutSubviews is called
+ // We update the constraint to update the position
+ setNeedsUpdateConstraints()
+
+ let shadowPath = UIBezierPath(roundedRect: tableViewContainer.bounds, cornerRadius: cornerRadius)
+ tableViewContainer.layer.shadowPath = shadowPath.cgPath
+ }
+
+ fileprivate func computeLayout() -> (x: CGFloat, y: CGFloat, width: CGFloat, offscreenHeight: CGFloat, visibleHeight: CGFloat, canBeDisplayed: Bool, Direction: Direction) {
+ var layout: ComputeLayoutTuple = (0, 0, 0, 0)
+ var direction = self.direction
+
+ guard let window = UIWindow.visibleWindow() else { return (0, 0, 0, 0, 0, false, direction) }
+
+ barButtonItemCondition: if let anchorView = anchorView as? UIBarButtonItem {
+ let isRightBarButtonItem = anchorView.plainView.frame.minX > window.frame.midX
+
+ guard isRightBarButtonItem else { break barButtonItemCondition }
+
+ let width = self.width ?? fittingWidth()
+ let anchorViewWidth = anchorView.plainView.frame.width
+ let x = -(width - anchorViewWidth)
+
+ bottomOffset = CGPoint(x: x, y: 0)
+ }
+
+ if anchorView == nil {
+ layout = computeLayoutBottomDisplay(window: window)
+ direction = .any
+ } else {
+ switch direction {
+ case .any:
+ layout = computeLayoutBottomDisplay(window: window)
+ direction = .bottom
+
+ if layout.offscreenHeight > 0 {
+ let topLayout = computeLayoutForTopDisplay(window: window)
+
+ if topLayout.offscreenHeight < layout.offscreenHeight {
+ layout = topLayout
+ direction = .top
+ }
+ }
+ case .bottom:
+ layout = computeLayoutBottomDisplay(window: window)
+ direction = .bottom
+ case .top:
+ layout = computeLayoutForTopDisplay(window: window)
+ direction = .top
+ }
+ }
+
+ constraintWidthToFittingSizeIfNecessary(layout: &layout)
+ constraintWidthToBoundsIfNecessary(layout: &layout, in: window)
+
+ let visibleHeight = tableHeight - layout.offscreenHeight
+ let canBeDisplayed = visibleHeight >= minHeight
+
+ return (layout.x, layout.y, layout.width, layout.offscreenHeight, visibleHeight, canBeDisplayed, direction)
+ }
- return
- }
-
- xConstraint.constant = layout.x
- yConstraint.constant = layout.y
- widthConstraint.constant = layout.width
- heightConstraint.constant = layout.visibleHeight
-
- tableView.isScrollEnabled = layout.offscreenHeight > 0
-
- DispatchQueue.main.async { [weak self] in
- self?.tableView.flashScrollIndicators()
- }
-
- super.updateConstraints()
- }
-
- fileprivate func setupConstraints() {
- translatesAutoresizingMaskIntoConstraints = false
-
- // Dismissable view
- addSubview(dismissableView)
- dismissableView.translatesAutoresizingMaskIntoConstraints = false
-
- addUniversalConstraints(format: "|[dismissableView]|", views: ["dismissableView": dismissableView])
-
-
- // Table view container
- addSubview(tableViewContainer)
- tableViewContainer.translatesAutoresizingMaskIntoConstraints = false
-
- xConstraint = NSLayoutConstraint(
- item: tableViewContainer,
- attribute: .leading,
- relatedBy: .equal,
- toItem: self,
- attribute: .leading,
- multiplier: 1,
- constant: 0)
- addConstraint(xConstraint)
-
- yConstraint = NSLayoutConstraint(
- item: tableViewContainer,
- attribute: .top,
- relatedBy: .equal,
- toItem: self,
- attribute: .top,
- multiplier: 1,
- constant: 0)
- addConstraint(yConstraint)
-
- widthConstraint = NSLayoutConstraint(
- item: tableViewContainer,
- attribute: .width,
- relatedBy: .equal,
- toItem: nil,
- attribute: .notAnAttribute,
- multiplier: 1,
- constant: 0)
- tableViewContainer.addConstraint(widthConstraint)
-
- heightConstraint = NSLayoutConstraint(
- item: tableViewContainer,
- attribute: .height,
- relatedBy: .equal,
- toItem: nil,
- attribute: .notAnAttribute,
- multiplier: 1,
- constant: 0)
- tableViewContainer.addConstraint(heightConstraint)
-
- // Table view
- tableViewContainer.addSubview(tableView)
- tableView.translatesAutoresizingMaskIntoConstraints = false
-
- tableViewContainer.addUniversalConstraints(format: "|[tableView]|", views: ["tableView": tableView])
- }
-
- public override func layoutSubviews() {
- super.layoutSubviews()
-
- // When orientation changes, layoutSubviews is called
- // We update the constraint to update the position
- setNeedsUpdateConstraints()
-
- let shadowPath = UIBezierPath(roundedRect: tableViewContainer.bounds, cornerRadius: cornerRadius)
- tableViewContainer.layer.shadowPath = shadowPath.cgPath
- }
-
- fileprivate func computeLayout() -> (x: CGFloat, y: CGFloat, width: CGFloat, offscreenHeight: CGFloat, visibleHeight: CGFloat, canBeDisplayed: Bool, Direction: Direction) {
- var layout: ComputeLayoutTuple = (0, 0, 0, 0)
- var direction = self.direction
-
- guard let window = UIWindow.visibleWindow() else { return (0, 0, 0, 0, 0, false, direction) }
-
- barButtonItemCondition: if let anchorView = anchorView as? UIBarButtonItem {
- let isRightBarButtonItem = anchorView.plainView.frame.minX > window.frame.midX
-
- guard isRightBarButtonItem else { break barButtonItemCondition }
-
- let width = self.width ?? fittingWidth()
- let anchorViewWidth = anchorView.plainView.frame.width
- let x = -(width - anchorViewWidth)
-
- bottomOffset = CGPoint(x: x, y: 0)
- }
-
- if anchorView == nil {
- layout = computeLayoutBottomDisplay(window: window)
- direction = .any
- } else {
- switch direction {
- case .any:
- layout = computeLayoutBottomDisplay(window: window)
- direction = .bottom
-
- if layout.offscreenHeight > 0 {
- let topLayout = computeLayoutForTopDisplay(window: window)
-
- if topLayout.offscreenHeight < layout.offscreenHeight {
- layout = topLayout
- direction = .top
- }
- }
- case .bottom:
- layout = computeLayoutBottomDisplay(window: window)
- direction = .bottom
- case .top:
- layout = computeLayoutForTopDisplay(window: window)
- direction = .top
- }
- }
-
- constraintWidthToFittingSizeIfNecessary(layout: &layout)
- constraintWidthToBoundsIfNecessary(layout: &layout, in: window)
-
- let visibleHeight = tableHeight - layout.offscreenHeight
- let canBeDisplayed = visibleHeight >= minHeight
-
- return (layout.x, layout.y, layout.width, layout.offscreenHeight, visibleHeight, canBeDisplayed, direction)
- }
-
- fileprivate func computeLayoutBottomDisplay(window: UIWindow) -> ComputeLayoutTuple {
- var offscreenHeight: CGFloat = 0
-
- let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - bottomOffset.x
-
- let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? window.frame.midX - (width / 2)
- let anchorViewY = anchorView?.plainView.windowFrame?.minY ?? window.frame.midY - (tableHeight / 2)
-
- let x = anchorViewX + bottomOffset.x
- let y = anchorViewY + bottomOffset.y
-
- let maxY = y + tableHeight
- let windowMaxY = window.bounds.maxY - DPDConstant.UI.HeightPadding - offsetFromWindowBottom
-
- let keyboardListener = KeyboardListener.sharedInstance
- let keyboardMinY = keyboardListener.keyboardFrame.minY - DPDConstant.UI.HeightPadding
-
- if keyboardListener.isVisible && maxY > keyboardMinY {
- offscreenHeight = abs(maxY - keyboardMinY)
- } else if maxY > windowMaxY {
- offscreenHeight = abs(maxY - windowMaxY)
- }
-
- return (x, y, width, offscreenHeight)
- }
-
- fileprivate func computeLayoutForTopDisplay(window: UIWindow) -> ComputeLayoutTuple {
- var offscreenHeight: CGFloat = 0
-
- let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? 0
- let anchorViewMaxY = anchorView?.plainView.windowFrame?.maxY ?? 0
-
- let x = anchorViewX + topOffset.x
- var y = (anchorViewMaxY + topOffset.y) - tableHeight
-
- let windowY = window.bounds.minY + DPDConstant.UI.HeightPadding
-
- if y < windowY {
- offscreenHeight = abs(y - windowY)
- y = windowY
- }
-
- let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - topOffset.x
-
- return (x, y, width, offscreenHeight)
- }
-
- fileprivate func fittingWidth() -> CGFloat {
- if templateCell == nil {
- templateCell = (cellNib.instantiate(withOwner: nil, options: nil)[0] as! DropDownCell)
- }
-
- var maxWidth: CGFloat = 0
-
- for index in 0.. maxWidth {
- maxWidth = width
- }
- }
-
- return maxWidth
- }
-
- fileprivate func constraintWidthToBoundsIfNecessary(layout: inout ComputeLayoutTuple, in window: UIWindow) {
- let windowMaxX = window.bounds.maxX
- let maxX = layout.x + layout.width
-
- if maxX > windowMaxX {
- let delta = maxX - windowMaxX
- let newOrigin = layout.x - delta
-
- if newOrigin > 0 {
- layout.x = newOrigin
- } else {
- layout.x = 0
- layout.width += newOrigin // newOrigin is negative, so this operation is a substraction
- }
- }
- }
-
- fileprivate func constraintWidthToFittingSizeIfNecessary(layout: inout ComputeLayoutTuple) {
- guard width == nil else { return }
-
- if layout.width < fittingWidth() {
- layout.width = fittingWidth()
- }
- }
-
+ fileprivate func computeLayoutBottomDisplay(window: UIWindow) -> ComputeLayoutTuple {
+ var offscreenHeight: CGFloat = 0
+
+ let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - bottomOffset.x
+
+ let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? window.frame.midX - (width / 2)
+ let anchorViewY = anchorView?.plainView.windowFrame?.minY ?? window.frame.midY - (tableHeight / 2)
+
+ let x = anchorViewX + bottomOffset.x
+ let y = anchorViewY + bottomOffset.y
+
+ let maxY = y + tableHeight
+ let windowMaxY = window.bounds.maxY - DPDConstant.UI.HeightPadding - offsetFromWindowBottom
+
+ let keyboardListener = KeyboardListener.sharedInstance
+ let keyboardMinY = keyboardListener.keyboardFrame.minY - DPDConstant.UI.HeightPadding
+
+ if keyboardListener.isVisible && maxY > keyboardMinY {
+ offscreenHeight = abs(maxY - keyboardMinY)
+ } else if maxY > windowMaxY {
+ offscreenHeight = abs(maxY - windowMaxY)
+ }
+
+ return (x, y, width, offscreenHeight)
+ }
+
+ fileprivate func computeLayoutForTopDisplay(window: UIWindow) -> ComputeLayoutTuple {
+ var offscreenHeight: CGFloat = 0
+
+ let anchorViewX = anchorView?.plainView.windowFrame?.minX ?? 0
+ let anchorViewMaxY = anchorView?.plainView.windowFrame?.maxY ?? 0
+
+ let x = anchorViewX + topOffset.x
+ var y = (anchorViewMaxY + topOffset.y) - tableHeight
+
+ let windowY = window.bounds.minY + DPDConstant.UI.HeightPadding
+
+ if y < windowY {
+ offscreenHeight = abs(y - windowY)
+ y = windowY
+ }
+
+ let width = self.width ?? (anchorView?.plainView.bounds.width ?? fittingWidth()) - topOffset.x
+
+ return (x, y, width, offscreenHeight)
+ }
+
+ fileprivate func fittingWidth() -> CGFloat {
+ if templateCell == nil {
+ templateCell = (cellNib.instantiate(withOwner: nil, options: nil)[0] as! DropDownCell)
+ }
+
+ var maxWidth: CGFloat = 0
+
+ for index in 0.. maxWidth {
+ maxWidth = width
+ }
+ }
+
+ return maxWidth
+ }
+
+ fileprivate func constraintWidthToBoundsIfNecessary(layout: inout ComputeLayoutTuple, in window: UIWindow) {
+ let windowMaxX = window.bounds.maxX
+ let maxX = layout.x + layout.width
+
+ if maxX > windowMaxX {
+ let delta = maxX - windowMaxX
+ let newOrigin = layout.x - delta
+
+ if newOrigin > 0 {
+ layout.x = newOrigin
+ } else {
+ layout.x = 0
+ layout.width += newOrigin // newOrigin is negative, so this operation is a substraction
+ }
+ }
+ }
+
+ fileprivate func constraintWidthToFittingSizeIfNecessary(layout: inout ComputeLayoutTuple) {
+ guard width == nil else { return }
+
+ if layout.width < fittingWidth() {
+ layout.width = fittingWidth()
+ }
+ }
+
}
//MARK: - Actions
@@ -818,43 +847,43 @@ extension DropDown {
return NSDictionary(dictionary: info)
}
-
- /**
- Shows the drop down if enough height.
+
+ /**
+ Shows the drop down if enough height.
- - returns: Wether it succeed and how much height is needed to display all cells at once.
- */
- @discardableResult
+ - returns: Wether it succeed and how much height is needed to display all cells at once.
+ */
+ @discardableResult
public func show(onTopOf window: UIWindow? = nil, beforeTransform transform: CGAffineTransform? = nil, anchorPoint: CGPoint? = nil) -> (canBeDisplayed: Bool, offscreenHeight: CGFloat?) {
- if self == DropDown.VisibleDropDown && DropDown.VisibleDropDown?.isHidden == false { // added condition - DropDown.VisibleDropDown?.isHidden == false -> to resolve forever hiding dropdown issue when continuous taping on button - Kartik Patel - 2016-12-29
- return (true, 0)
- }
-
- if let visibleDropDown = DropDown.VisibleDropDown {
- visibleDropDown.cancel()
- }
+ if self == DropDown.VisibleDropDown && DropDown.VisibleDropDown?.isHidden == false { // added condition - DropDown.VisibleDropDown?.isHidden == false -> to resolve forever hiding dropdown issue when continuous taping on button - Kartik Patel - 2016-12-29
+ return (true, 0)
+ }
- willShowAction?()
+ if let visibleDropDown = DropDown.VisibleDropDown {
+ visibleDropDown.cancel()
+ }
+
+ willShowAction?()
- DropDown.VisibleDropDown = self
+ DropDown.VisibleDropDown = self
- setNeedsUpdateConstraints()
+ setNeedsUpdateConstraints()
- let visibleWindow = window != nil ? window : UIWindow.visibleWindow()
- visibleWindow?.addSubview(self)
- visibleWindow?.bringSubviewToFront(self)
+ let visibleWindow = window != nil ? window : UIWindow.visibleWindow()
+ visibleWindow?.addSubview(self)
+ visibleWindow?.bringSubviewToFront(self)
- self.translatesAutoresizingMaskIntoConstraints = false
- visibleWindow?.addUniversalConstraints(format: "|[dropDown]|", views: ["dropDown": self])
+ self.translatesAutoresizingMaskIntoConstraints = false
+ visibleWindow?.addUniversalConstraints(format: "|[dropDown]|", views: ["dropDown": self])
- let layout = computeLayout()
+ let layout = computeLayout()
- if !layout.canBeDisplayed {
- hide()
- return (layout.canBeDisplayed, layout.offscreenHeight)
- }
+ if !layout.canBeDisplayed {
+ hide()
+ return (layout.canBeDisplayed, layout.offscreenHeight)
+ }
- isHidden = false
+ isHidden = false
if anchorPoint != nil {
tableViewContainer.layer.anchorPoint = anchorPoint!
@@ -866,80 +895,80 @@ extension DropDown {
tableViewContainer.transform = downScaleTransform
}
- layoutIfNeeded()
-
- UIView.animate(
- withDuration: animationduration,
- delay: 0,
- options: animationEntranceOptions,
- animations: { [weak self] in
- self?.setShowedState()
- },
- completion: nil)
-
- accessibilityViewIsModal = true
- UIAccessibility.post(notification: .screenChanged, argument: self)
-
- //deselectRows(at: selectedRowIndices)
- selectRows(at: selectedRowIndices)
-
- return (layout.canBeDisplayed, layout.offscreenHeight)
- }
-
- public override func accessibilityPerformEscape() -> Bool {
- switch dismissMode {
- case .automatic, .onTap:
- cancel()
- return true
- case .manual:
- return false
- }
- }
-
- /// Hides the drop down.
- public func hide() {
- if self == DropDown.VisibleDropDown {
- /*
- If one drop down is showed and another one is not
- but we call `hide()` on the hidden one:
- we don't want it to set the `VisibleDropDown` to nil.
- */
- DropDown.VisibleDropDown = nil
- }
-
- if isHidden {
- return
- }
-
- UIView.animate(
- withDuration: animationduration,
- delay: 0,
- options: animationExitOptions,
- animations: { [weak self] in
- self?.setHiddentState()
- },
- completion: { [weak self] finished in
- guard let `self` = self else { return }
-
- self.isHidden = true
- self.removeFromSuperview()
- UIAccessibility.post(notification: .screenChanged, argument: nil)
- })
- }
-
- fileprivate func cancel() {
- hide()
- cancelAction?()
- }
-
- fileprivate func setHiddentState() {
- alpha = 0
- }
-
- fileprivate func setShowedState() {
- alpha = 1
- tableViewContainer.transform = CGAffineTransform.identity
- }
+ layoutIfNeeded()
+
+ UIView.animate(
+ withDuration: animationduration,
+ delay: 0,
+ options: animationEntranceOptions,
+ animations: { [weak self] in
+ self?.setShowedState()
+ },
+ completion: nil)
+
+ accessibilityViewIsModal = true
+ UIAccessibility.post(notification: .screenChanged, argument: self)
+
+ //deselectRows(at: selectedRowIndices)
+ selectRows(at: selectedRowIndices)
+
+ return (layout.canBeDisplayed, layout.offscreenHeight)
+ }
+
+ public override func accessibilityPerformEscape() -> Bool {
+ switch dismissMode {
+ case .automatic, .onTap:
+ cancel()
+ return true
+ case .manual:
+ return false
+ }
+ }
+
+ /// Hides the drop down.
+ public func hide() {
+ if self == DropDown.VisibleDropDown {
+ /*
+ If one drop down is showed and another one is not
+ but we call `hide()` on the hidden one:
+ we don't want it to set the `VisibleDropDown` to nil.
+ */
+ DropDown.VisibleDropDown = nil
+ }
+
+ if isHidden {
+ return
+ }
+
+ UIView.animate(
+ withDuration: animationduration,
+ delay: 0,
+ options: animationExitOptions,
+ animations: { [weak self] in
+ self?.setHiddentState()
+ },
+ completion: { [weak self] finished in
+ guard let `self` = self else { return }
+
+ self.isHidden = true
+ self.removeFromSuperview()
+ UIAccessibility.post(notification: .screenChanged, argument: nil)
+ })
+ }
+
+ fileprivate func cancel() {
+ hide()
+ cancelAction?()
+ }
+
+ fileprivate func setHiddentState() {
+ alpha = 0
+ }
+
+ fileprivate func setShowedState() {
+ alpha = 1
+ tableViewContainer.transform = CGAffineTransform.identity
+ }
}
@@ -947,32 +976,32 @@ extension DropDown {
extension DropDown {
- /**
- Reloads all the cells.
-
- It should not be necessary in most cases because each change to
- `dataSource`, `textColor`, `textFont`, `selectionBackgroundColor`
- and `cellConfiguration` implicitly calls `reloadAllComponents()`.
- */
- public func reloadAllComponents() {
- DispatchQueue.executeOnMainThread {
- self.tableView.reloadData()
- self.setNeedsUpdateConstraints()
- }
- }
-
- /// (Pre)selects a row at a certain index.
- public func selectRow(at index: Index?, scrollPosition: UITableView.ScrollPosition = .none) {
- if let index = index {
+ /**
+ Reloads all the cells.
+
+ It should not be necessary in most cases because each change to
+ `dataSource`, `textColor`, `textFont`, `selectionBackgroundColor`
+ and `cellConfiguration` implicitly calls `reloadAllComponents()`.
+ */
+ public func reloadAllComponents() {
+ DispatchQueue.executeOnMainThread {
+ self.tableView.reloadData()
+ self.setNeedsUpdateConstraints()
+ }
+ }
+
+ /// (Pre)selects a row at a certain index.
+ public func selectRow(at index: Index?, scrollPosition: UITableView.ScrollPosition = .none) {
+ if let index = index {
tableView.selectRow(
at: IndexPath(row: index, section: 0), animated: true, scrollPosition: scrollPosition
)
selectedRowIndices.insert(index)
- } else {
- deselectRows(at: selectedRowIndices)
+ } else {
+ deselectRows(at: selectedRowIndices)
selectedRowIndices.removeAll()
- }
- }
+ }
+ }
public func selectRows(at indices: Set?) {
indices?.forEach {
@@ -985,18 +1014,18 @@ extension DropDown {
}
}
- public func deselectRow(at index: Index?) {
- guard let index = index
- , index >= 0
- else { return }
+ public func deselectRow(at index: Index?) {
+ guard let index = index
+ , index >= 0
+ else { return }
// remove from indices
if let selectedRowIndex = selectedRowIndices.firstIndex(where: { $0 == index }) {
selectedRowIndices.remove(at: selectedRowIndex)
}
- tableView.deselectRow(at: IndexPath(row: index, section: 0), animated: true)
- }
+ tableView.deselectRow(at: IndexPath(row: index, section: 0), animated: true)
+ }
// de-selects the rows at the indices provided
public func deselectRows(at indices: Set?) {
@@ -1005,25 +1034,30 @@ extension DropDown {
}
}
- /// Returns the index of the selected row.
- public var indexForSelectedRow: Index? {
- return (tableView.indexPathForSelectedRow as NSIndexPath?)?.row
- }
+ /// Returns the index of the selected row.
+ public var indexForSelectedRow: Index? {
+ return (tableView.indexPathForSelectedRow as NSIndexPath?)?.row
+ }
- /// Returns the selected item.
- public var selectedItem: String? {
- guard let row = (tableView.indexPathForSelectedRow as NSIndexPath?)?.row else { return nil }
+ /// Returns the selected item.
+ public var selectedItem: String? {
+ guard let row = (tableView.indexPathForSelectedRow as NSIndexPath?)?.row else { return nil }
- return dataSource[row]
- }
+ return dataSource[row]
+ }
- /// Returns the height needed to display all cells.
- fileprivate var tableHeight: CGFloat {
- return tableView.rowHeight * CGFloat(dataSource.count)
- }
+ /// Returns the height needed to display all cells.
+ fileprivate var tableHeight: CGFloat {
+
+ if shortListEnabled {
+ return tableView.rowHeight * CGFloat(sortListMaxEntries + 1)
+ }else {
+ return tableView.rowHeight * CGFloat(dataSource.count)
+ }
+ }
//MARK: Objective-C methods for converting the Swift type Index
- @objc public func selectRow(_ index: Int, scrollPosition: UITableView.ScrollPosition = .none) {
+ @objc public func selectRow(_ index: Int, scrollPosition: UITableView.ScrollPosition = .none) {
self.selectRow(at:Index(index), scrollPosition: scrollPosition)
}
@@ -1044,46 +1078,91 @@ extension DropDown {
extension DropDown: UITableViewDataSource, UITableViewDelegate {
- public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
- return dataSource.count
- }
-
- public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
- let cell = tableView.dequeueReusableCell(withIdentifier: DPDConstant.ReusableIdentifier.DropDownCell, for: indexPath) as! DropDownCell
- let index = (indexPath as NSIndexPath).row
-
- configureCell(cell, at: index)
-
- return cell
- }
-
- fileprivate func configureCell(_ cell: DropDownCell, at index: Int) {
- if index >= 0 && index < localizationKeysDataSource.count {
- cell.accessibilityIdentifier = localizationKeysDataSource[index]
- }
-
- cell.optionLabel.textColor = textColor
- cell.optionLabel.font = textFont
- cell.selectedBackgroundColor = selectionBackgroundColor
+ public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
+
+ if shortListEnabled,
+ sortListMaxEntries > 0,
+ dataSource.count > sortListMaxEntries {
+ return sortListMaxEntries + 1
+ } else {
+ return dataSource.count
+ }
+ }
+
+ public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
+
+ let index = (indexPath as NSIndexPath).row
+
+ if shortListEnabled && index >= sortListMaxEntries {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: DPDConstant.ReusableIdentifier.MoreDropDownCell, for: indexPath) as! MoreDropDownCell
+
+ configureCell(cell, at: index)
+
+ return cell
+ } else {
+
+ let cell = tableView.dequeueReusableCell(withIdentifier: DPDConstant.ReusableIdentifier.DropDownCell, for: indexPath) as! DropDownCell
+
+ configureCell(cell, at: index)
+
+ return cell
+ }
+ }
+
+ fileprivate func configureCell(_ cell: MoreDropDownCell, at index: Int) {
+
+ if index >= 0 && index < localizationKeysDataSource.count {
+ cell.accessibilityIdentifier = localizationKeysDataSource[index]
+ }
+
+ cell.optionLabel.textColor = textColor
+ cell.optionLabel.font = textFont
+ cell.selectedBackgroundColor = selectionBackgroundColor
+ cell.highlightTextColor = selectedTextColor
+ cell.normalTextColor = textColor
+
+ if let cellConfiguration = cellConfiguration {
+ cell.optionLabel.text = cellConfiguration(index, "dropdown_more")
+ } else {
+ cell.optionLabel.text = "dropdown_more"
+ }
+
+ customMoreCellConfiguration?(cell)
+ }
+
+ fileprivate func configureCell(_ cell: DropDownCell, at index: Int) {
+ if index >= 0 && index < localizationKeysDataSource.count {
+ cell.accessibilityIdentifier = localizationKeysDataSource[index]
+ }
+
+ cell.optionLabel.textColor = textColor
+ cell.optionLabel.font = textFont
+ cell.selectedBackgroundColor = selectionBackgroundColor
cell.highlightTextColor = selectedTextColor
cell.normalTextColor = textColor
-
- if let cellConfiguration = cellConfiguration {
- cell.optionLabel.text = cellConfiguration(index, dataSource[index])
- } else {
- cell.optionLabel.text = dataSource[index]
- }
-
- customCellConfiguration?(index, dataSource[index], cell)
- }
-
- public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
+
+ if let cellConfiguration = cellConfiguration {
+ cell.optionLabel.text = cellConfiguration(index, dataSource[index])
+ } else {
+ cell.optionLabel.text = dataSource[index]
+ }
+
+ customCellConfiguration?(index, dataSource[index], cell)
+ }
+
+ public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.isSelected = selectedRowIndices.first{ $0 == (indexPath as NSIndexPath).row } != nil
- }
+ }
- public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
- let selectedRowIndex = (indexPath as NSIndexPath).row
+ public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
+ let selectedRowIndex = (indexPath as NSIndexPath).row
+ if shortListEnabled, selectedRowIndex == sortListMaxEntries {
+ shortListEnabled = false
+ reloadAllComponents()
+ return
+ }
// are we in multi-selection mode?
if let multiSelectionCallback = multiSelectionAction {
@@ -1091,7 +1170,7 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate {
if selectedRowIndices.first(where: { $0 == selectedRowIndex}) != nil {
deselectRow(at: selectedRowIndex)
- let selectedRowIndicesArray = Array(selectedRowIndices)
+ let selectedRowIndicesArray = Array(selectedRowIndices)
let selectedRows = selectedRowIndicesArray.map { dataSource[$0] }
multiSelectionCallback(selectedRowIndicesArray, selectedRows)
return
@@ -1099,8 +1178,8 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate {
else {
selectedRowIndices.insert(selectedRowIndex)
- let selectedRowIndicesArray = Array(selectedRowIndices)
- let selectedRows = selectedRowIndicesArray.map { dataSource[$0] }
+ let selectedRowIndicesArray = Array(selectedRowIndices)
+ let selectedRows = selectedRowIndicesArray.map { dataSource[$0] }
selectionAction?(selectedRowIndex, dataSource[selectedRowIndex])
multiSelectionCallback(selectedRowIndicesArray, selectedRows)
@@ -1121,7 +1200,7 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate {
hide()
- }
+ }
}
@@ -1129,21 +1208,21 @@ extension DropDown: UITableViewDataSource, UITableViewDelegate {
extension DropDown {
- public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
- let view = super.hitTest(point, with: event)
+ public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
+ let view = super.hitTest(point, with: event)
- if dismissMode == .automatic && view === dismissableView {
- cancel()
- return nil
- } else {
- return view
- }
- }
+ if dismissMode == .automatic && view === dismissableView {
+ cancel()
+ return nil
+ } else {
+ return view
+ }
+ }
- @objc
- fileprivate func dismissableViewTapped() {
- cancel()
- }
+ @objc
+ fileprivate func dismissableViewTapped() {
+ cancel()
+ }
}
@@ -1151,46 +1230,46 @@ extension DropDown {
extension DropDown {
- /**
- Starts listening to keyboard events.
- Allows the drop down to display correctly when keyboard is showed.
- */
- @objc public static func startListeningToKeyboard() {
- KeyboardListener.sharedInstance.startListeningToKeyboard()
- }
-
- fileprivate func startListeningToKeyboard() {
- KeyboardListener.sharedInstance.startListeningToKeyboard()
-
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(keyboardUpdate),
- name: UIResponder.keyboardWillShowNotification,
- object: nil)
- NotificationCenter.default.addObserver(
- self,
- selector: #selector(keyboardUpdate),
- name: UIResponder.keyboardWillHideNotification,
- object: nil)
- }
-
- fileprivate func stopListeningToNotifications() {
- NotificationCenter.default.removeObserver(self)
- }
-
- @objc
- fileprivate func keyboardUpdate() {
- self.setNeedsUpdateConstraints()
- }
+ /**
+ Starts listening to keyboard events.
+ Allows the drop down to display correctly when keyboard is showed.
+ */
+ @objc public static func startListeningToKeyboard() {
+ KeyboardListener.sharedInstance.startListeningToKeyboard()
+ }
+
+ fileprivate func startListeningToKeyboard() {
+ KeyboardListener.sharedInstance.startListeningToKeyboard()
+
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(keyboardUpdate),
+ name: UIResponder.keyboardWillShowNotification,
+ object: nil)
+ NotificationCenter.default.addObserver(
+ self,
+ selector: #selector(keyboardUpdate),
+ name: UIResponder.keyboardWillHideNotification,
+ object: nil)
+ }
+
+ fileprivate func stopListeningToNotifications() {
+ NotificationCenter.default.removeObserver(self)
+ }
+
+ @objc
+ fileprivate func keyboardUpdate() {
+ self.setNeedsUpdateConstraints()
+ }
}
private extension DispatchQueue {
- static func executeOnMainThread(_ closure: @escaping Closure) {
- if Thread.isMainThread {
- closure()
- } else {
- main.async(execute: closure)
- }
- }
+ static func executeOnMainThread(_ closure: @escaping Closure) {
+ if Thread.isMainThread {
+ closure()
+ } else {
+ main.async(execute: closure)
+ }
+ }
}
diff --git a/DropDown/src/DropDownCell.swift b/DropDown/src/DropDownCell.swift
index 7a90efc..88a4621 100644
--- a/DropDown/src/DropDownCell.swift
+++ b/DropDown/src/DropDownCell.swift
@@ -13,9 +13,9 @@ open class DropDownCell: UITableViewCell {
//UI
@IBOutlet open weak var optionLabel: UILabel!
- var selectedBackgroundColor: UIColor?
- var highlightTextColor: UIColor?
- var normalTextColor: UIColor?
+ open var selectedBackgroundColor: UIColor?
+ open var highlightTextColor: UIColor?
+ open var normalTextColor: UIColor?
}
diff --git a/DropDown/src/MoreDropDownCell.swift b/DropDown/src/MoreDropDownCell.swift
new file mode 100644
index 0000000..31eca3e
--- /dev/null
+++ b/DropDown/src/MoreDropDownCell.swift
@@ -0,0 +1,73 @@
+//
+// MoreDropDownCell.swift
+// DropDown
+//
+// Created by Ignazio Altomare on 22/05/2020.
+//
+
+import UIKit
+
+open class MoreDropDownCell: UITableViewCell {
+
+ //UI
+ @IBOutlet open weak var optionLabel: UILabel!
+
+ open var selectedBackgroundColor: UIColor?
+ open var highlightTextColor: UIColor?
+ open var normalTextColor: UIColor?
+
+}
+
+//MARK: - UI
+
+extension MoreDropDownCell {
+
+ override open func awakeFromNib() {
+ super.awakeFromNib()
+
+ backgroundColor = .clear
+ }
+
+ override open var isSelected: Bool {
+ willSet {
+ setSelected(newValue, animated: false)
+ }
+ }
+
+ override open var isHighlighted: Bool {
+ willSet {
+ setSelected(newValue, animated: false)
+ }
+ }
+
+ override open func setHighlighted(_ highlighted: Bool, animated: Bool) {
+ setSelected(highlighted, animated: animated)
+ }
+
+ override open func setSelected(_ selected: Bool, animated: Bool) {
+ let executeSelection: () -> Void = { [weak self] in
+ guard let `self` = self else { return }
+
+ if let selectedBackgroundColor = self.selectedBackgroundColor {
+ if selected {
+ self.backgroundColor = selectedBackgroundColor
+ self.optionLabel.textColor = self.highlightTextColor
+ } else {
+ self.backgroundColor = .clear
+ self.optionLabel.textColor = self.normalTextColor
+ }
+ }
+ }
+
+ if animated {
+ UIView.animate(withDuration: 0.3, animations: {
+ executeSelection()
+ })
+ } else {
+ executeSelection()
+ }
+
+ accessibilityTraits = selected ? .selected : .none
+ }
+
+}